From d71fd151c5b802c66479ad4fae6c960fc7b7891c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 13 Nov 2017 16:45:27 -0800 Subject: [PATCH] Fixing button model exposure --- interface/resources/qml/hifi/Desktop.qml | 4 +- .../resources/qml/hifi/toolbars/Toolbar.qml | 127 +++-------- .../qml/hifi/toolbars/ToolbarButton.qml | 3 +- interface/src/Application.cpp | 4 + libraries/ui/src/OffscreenUi.cpp | 3 + .../ui/src/ui/TabletScriptingInterface.cpp | 204 ++++++------------ .../ui/src/ui/TabletScriptingInterface.h | 66 ++++-- tests/shared/src/PathUtilsTests.cpp | 1 - 8 files changed, 149 insertions(+), 263 deletions(-) diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index ea9ec2f6c9..960d3e0649 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -45,11 +45,13 @@ OriginalDesktop.Desktop { Toolbar { id: sysToolbar; objectName: "com.highfidelity.interface.toolbar.system"; + property var tablet: Tablets.getTablet("com.highfidelity.interface.tablet.system"); anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined; // Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained. x: sysToolbar.x y: 50 - shown: true + buttonModel: tablet.buttons; + shown: tablet.toolbarMode; } Settings { diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index 01aa29f665..ff7e835690 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -11,6 +11,8 @@ Window { horizontalSpacers: horizontal verticalSpacers: !horizontal } + property var tabletProxy; + property var buttonModel: ListModel {} hideBackground: true resizable: false destroyOnCloseButton: false @@ -23,24 +25,32 @@ Window { activator: Item {} property bool horizontal: true property real buttonSize: 50; - property var buttons: [] - property var container: horizontal ? row : column Settings { category: "toolbar/" + window.objectName property alias x: window.x property alias y: window.y } + + Component { + id: buttonComponent + ToolbarButton { + id: toolbarButton + property var proxy: modelData; + onClicked: proxy.clicked() + Component.onCompleted: updateProperties() - onHorizontalChanged: { - var newParent = horizontal ? row : column; - for (var i in buttons) { - var child = buttons[i]; - child.parent = newParent; - if (horizontal) { - child.y = 0 - } else { - child.x = 0 + Connections { + target: proxy; + onPropertiesChanged: updateProperties(); + } + + function updateProperties() { + Object.keys(proxy.properties).forEach(function (key) { + if (toolbarButton[key] !== proxy.properties[key]) { + toolbarButton[key] = proxy.properties[key]; + } + }); } } } @@ -52,97 +62,22 @@ Window { Row { id: row + visible: window.horizontal spacing: 6 + Repeater { + model: buttonModel + delegate: buttonComponent + } } - + Column { id: column + visible: !window.horizontal spacing: 6 - } - - Component { id: toolbarButtonBuilder; ToolbarButton { } } - } - - - function findButtonIndex(name) { - if (!name) { - return -1; - } - - for (var i in buttons) { - var child = buttons[i]; - if (child.objectName === name) { - return i; + Repeater { + model: buttonModel + delegate: buttonComponent } } - return -1; - } - - function findButton(name) { - var index = findButtonIndex(name); - if (index < 0) { - return; - } - return buttons[index]; - } - - function sortButtons() { - var children = []; - for (var i = 0; i < container.children.length; i++) { - children[i] = container.children[i]; - } - - children.sort(function (a, b) { - if (a.sortOrder === b.sortOrder) { - // subsort by stableOrder, because JS sort is not stable in qml. - return a.stableOrder - b.stableOrder; - } else { - return a.sortOrder - b.sortOrder; - } - }); - - container.children = children; - } - - function addButton(properties) { - properties = properties || {} - - // If a name is specified, then check if there's an existing button with that name - // and return it if so. This will allow multiple clients to listen to a single button, - // and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded - var result = findButton(properties.objectName); - if (result) { - for (var property in properties) { - result[property] = properties[property]; - } - return result; - } - properties.toolbar = this; - properties.opacity = 0; - result = toolbarButtonBuilder.createObject(container, properties); - buttons.push(result); - - result.opacity = 1; - - sortButtons(); - - fadeIn(null); - - return result; - } - - function removeButton(name) { - var index = findButtonIndex(name); - if (index < -1) { - console.warn("Tried to remove non-existent button " + name); - return; - } - - buttons[index].destroy(); - buttons.splice(index, 1); - - if (buttons.length === 0) { - fadeOut(null); - } } } diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index 63149ad23b..3c64d4bf2e 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -25,7 +25,8 @@ StateImage { property string activeHoverIcon: button.activeIcon property int sortOrder: 100 - property int stableSortOrder: 0 + property int stableOrder: 0 + property var uuid; signal clicked() diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d388128709..2d7ae29379 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2234,6 +2234,10 @@ void Application::initializeUi() { qmlRegisterType("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType("Hifi", 1, 0, "Preference"); + { + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->getTablet(SYSTEM_TABLET); + } auto offscreenUi = DependencyManager::get(); offscreenUi->create(); diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 252c08a1f0..d8a2c2c3cb 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -106,6 +106,9 @@ void OffscreenUi::create() { myContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags()); myContext->setContextProperty("fileDialogHelper", new FileDialogHelper()); auto tabletScriptingInterface = DependencyManager::get(); + qRegisterMetaType(); + qRegisterMetaType(); + myContext->setContextProperty("Tablets", tabletScriptingInterface.data()); TabletProxy* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); myContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 93d493539e..f006547e2b 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -32,6 +32,14 @@ const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; const QString TabletScriptingInterface::QML = "hifi/tablet/TabletRoot.qml"; +static QString getUsername() { + QString username = "Unknown user"; + auto accountManager = DependencyManager::get(); + if (accountManager->isLoggedIn()) { + username = accountManager->getAccountInfo().getUsername(); + } + return username; +} static Setting::Handle tabletSoundsButtonClick("TabletSounds", QStringList { "/sounds/Button06.wav", "/sounds/Button04.wav", @@ -39,6 +47,51 @@ static Setting::Handle tabletSoundsButtonClick("TabletSounds", QStr "/sounds/Tab01.wav", "/sounds/Tab02.wav" }); +TabletButtonListModel::TabletButtonListModel() { + +} + +TabletButtonListModel::~TabletButtonListModel() { + +} + +enum ButtonDeviceRole { + ButtonProxyRole = Qt::UserRole, +}; + +QHash TabletButtonListModel::_roles{ + { ButtonProxyRole, "buttonProxy" }, +}; + +Qt::ItemFlags TabletButtonListModel::_flags{ Qt::ItemIsSelectable | Qt::ItemIsEnabled }; + +QVariant TabletButtonListModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || index.row() >= rowCount() || role != ButtonProxyRole) { + return QVariant(); + } + + return QVariant::fromValue(_buttons.at(index.row()).data()); +} + +TabletButtonProxy* TabletButtonListModel::addButton(const QVariant& properties) { + auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); + beginResetModel(); + _buttons.push_back(tabletButtonProxy); + endResetModel(); + return tabletButtonProxy.data(); +} + +void TabletButtonListModel::removeButton(TabletButtonProxy* button) { + auto itr = std::find(_buttons.begin(), _buttons.end(), button); + if (itr == _buttons.end()) { + qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << button; + return; + } + beginResetModel(); + _buttons.erase(itr); + endResetModel(); +} + TabletScriptingInterface::TabletScriptingInterface() { qmlRegisterType("TabletScriptingInterface", 1, 0, "TabletEnums"); } @@ -232,15 +285,6 @@ TabletProxy::~TabletProxy() { disconnect(this, &TabletProxy::tabletShownChanged, this, &TabletProxy::onTabletShown); } -QVariant TabletProxy::getButtons() { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - QVariantList result; - for (const auto& button : _tabletButtonProxies) { - result.push_back(QVariant::fromValue(button.data())); - } - return result; -} - void TabletProxy::setToolbarMode(bool toolbarMode) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setToolbarMode", Q_ARG(bool, toolbarMode)); @@ -256,8 +300,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { auto offscreenUi = DependencyManager::get(); if (toolbarMode) { - addButtonsToToolbar(); - // create new desktop window auto tabletRootWindow = new TabletRootWindow(); tabletRootWindow->initQml(QVariantMap()); @@ -272,11 +314,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { // forward qml surface events to interface js connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml); } else { - removeButtonsFromToolbar(); - - if (_currentPathLoaded == TABLET_HOME_SOURCE_URL) { - // Tablet QML now pulls buttons from Tablet proxy - } else { + if (_currentPathLoaded != TABLET_HOME_SOURCE_URL) { loadHomeScreen(true); } //check if running scripts window opened and save it for reopen in Tablet @@ -290,44 +328,8 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { _desktopWindow = nullptr; } } -} -#if 0 -static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - if (buttonProxy == NULL){ - qCCritical(uiLogging) << __FUNCTION__ << "buttonProxy is NULL"; - return; - } - - QVariant resultVar; - qCDebug(uiLogging) << "QQQ" << __FUNCTION__ << "adding button " << buttonProxy; - bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", Qt::DirectConnection, - Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties())); - if (!hasResult) { - qCWarning(uiLogging) << __FUNCTION__ << " has no result"; - return; - } - - QObject* qmlButton = qvariant_cast(resultVar); - if (!qmlButton) { - qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject"; - return; - } - QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot())); - buttonProxy->setQmlButton(qobject_cast(qmlButton)); -} -#endif - - -static QString getUsername() { - QString username = "Unknown user"; - auto accountManager = DependencyManager::get(); - if (accountManager->isLoggedIn()) { - return accountManager->getAccountInfo().getUsername(); - } else { - return "Unknown user"; - } + emit toolbarModeChanged(); } void TabletProxy::initialScreen(const QVariant& url) { @@ -702,20 +704,7 @@ TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) { return result; } - auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); - _tabletButtonProxies.push_back(tabletButtonProxy); - if (!_toolbarMode && _qmlTabletRoot) { - // Tablet now pulls buttons from the tablet proxy - // FIXME emit a signal so that the tablet can refresh buttons if they change - } else if (_toolbarMode) { - auto toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - if (toolbarProxy) { - // copy properties from tablet button proxy to toolbar button proxy. - auto toolbarButtonProxy = toolbarProxy->addButton(tabletButtonProxy->getProperties()); - tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy); - } - } - return tabletButtonProxy.data(); + return _buttons.addButton(properties); } bool TabletProxy::onHomeScreen() { @@ -734,35 +723,7 @@ void TabletProxy::removeButton(TabletButtonProxy* tabletButtonProxy) { return; } - auto tablet = getQmlTablet(); - if (!tablet) { - qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml"; - } - - QSharedPointer buttonProxy; - { - auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); - if (iter == _tabletButtonProxies.end()) { - qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy; - return; - } - buttonProxy = *iter; - _tabletButtonProxies.erase(iter); - } - - if (!_toolbarMode && _qmlTabletRoot) { - buttonProxy->setQmlButton(nullptr); - if (tablet) { - QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties())); - } - } else if (_toolbarMode) { - auto toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - // remove button from toolbarProxy - if (toolbarProxy) { - toolbarProxy->removeButton(buttonProxy->getUuid().toString()); - buttonProxy->setToolbarButtonProxy(nullptr); - } - } + _buttons.removeButton(tabletButtonProxy); } void TabletProxy::emitScriptEvent(const QVariant& msg) { @@ -801,27 +762,6 @@ void TabletProxy::desktopWindowClosed() { gotoHomeScreen(); } -void TabletProxy::addButtonsToToolbar() { - Q_ASSERT(QThread::currentThread() == thread()); - ToolbarProxy* toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - for (auto& buttonProxy : _tabletButtonProxies) { - // copy properties from tablet button proxy to toolbar button proxy. - buttonProxy->setToolbarButtonProxy(toolbarProxy->addButton(buttonProxy->getProperties())); - } - - // make the toolbar visible - toolbarProxy->writeProperty("visible", QVariant(true)); -} - -void TabletProxy::removeButtonsFromToolbar() { - Q_ASSERT(QThread::currentThread() == thread()); - ToolbarProxy* toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - for (auto& buttonProxy : _tabletButtonProxies) { - // remove button from toolbarProxy - toolbarProxy->removeButton(buttonProxy->getUuid().toString()); - buttonProxy->setToolbarButtonProxy(nullptr); - } -} QQuickItem* TabletProxy::getQmlTablet() const { if (!_qmlTabletRoot) { @@ -891,25 +831,6 @@ TabletButtonProxy::~TabletButtonProxy() { } } -void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - if (_qmlButton) { - QObject::disconnect(_qmlButton, &QQuickItem::destroyed, this, nullptr); - } - _qmlButton = qmlButton; - if (_qmlButton) { - QObject::connect(_qmlButton, &QQuickItem::destroyed, this, [this] { _qmlButton = nullptr; }); - } -} - -void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) { - Q_ASSERT(QThread::currentThread() == thread()); - _toolbarButtonProxy = toolbarButtonProxy; - if (_toolbarButtonProxy) { - QObject::connect(_toolbarButtonProxy, SIGNAL(clicked()), this, SLOT(clickedSlot())); - } -} - QVariantMap TabletButtonProxy::getProperties() { if (QThread::currentThread() != thread()) { QVariantMap result; @@ -926,20 +847,19 @@ void TabletButtonProxy::editProperties(const QVariantMap& properties) { return; } + bool changed = false; QVariantMap::const_iterator iter = properties.constBegin(); while (iter != properties.constEnd()) { const auto& key = iter.key(); const auto& value = iter.value(); if (!_properties.contains(key) || _properties[key] != value) { - _properties[iter.key()] = iter.value(); - if (_qmlButton) { - QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection, Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value())); - } + _properties[key] = value; + changed = true; } ++iter; } - if (_toolbarButtonProxy) { - QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariantMap, properties)); + if (changed) { + emit propertiesChanged(); } } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index e7dc5ede1f..718d5123db 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -12,13 +12,16 @@ #include #include -#include -#include +#include +#include +#include +#include + #include -#include -#include -#include -#include +#include +#include + +#include #include #include @@ -90,6 +93,31 @@ protected: bool _toolbarMode { false }; }; +class TabletButtonListModel : public QAbstractListModel { + Q_OBJECT + +public: + TabletButtonListModel(); + ~TabletButtonListModel(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override { Q_UNUSED(parent); return (int)_buttons.size(); } + QHash roleNames() const override { return _roles; } + Qt::ItemFlags flags(const QModelIndex& index) const override { return _flags; } + QVariant data(const QModelIndex& index, int role) const override; + + +protected: + friend class TabletProxy; + TabletButtonProxy* addButton(const QVariant& properties); + void removeButton(TabletButtonProxy* button); + using List = std::list>; + static QHash _roles; + static Qt::ItemFlags _flags; + std::vector> _buttons; +}; + +Q_DECLARE_METATYPE(TabletButtonListModel*); + /**jsdoc * @class TabletProxy * @property name {string} READ_ONLY: name of this tablet @@ -99,9 +127,10 @@ protected: class TabletProxy : public QObject { Q_OBJECT Q_PROPERTY(QString name READ getName) - Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode) + Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode NOTIFY toolbarModeChanged) Q_PROPERTY(bool landscape READ getLandscape WRITE setLandscape) Q_PROPERTY(bool tabletShown MEMBER _tabletShown NOTIFY tabletShownChanged) + Q_PROPERTY(TabletButtonListModel* buttons READ getButtons CONSTANT) public: TabletProxy(QObject* parent, const QString& name); ~TabletProxy(); @@ -196,8 +225,6 @@ public: Q_INVOKABLE bool isPathLoaded(const QVariant& path); - Q_INVOKABLE QVariant getButtons(); - QQuickItem* getTabletRoot() const { return _qmlTabletRoot; } OffscreenQmlSurface* getTabletSurface(); @@ -206,6 +233,7 @@ public: QQuickItem* getQmlMenu() const; + TabletButtonListModel* getButtons() { return &_buttons; } signals: /**jsdoc * Signaled when this tablet receives an event from the html/js embedded in the tablet @@ -238,20 +266,20 @@ signals: */ void tabletShownChanged(); + void toolbarModeChanged(); + protected slots: void desktopWindowClosed(); void emitWebEvent(const QVariant& msg); void onTabletShown(); + protected: void loadHomeScreen(bool forceOntoHomeScreen); - void addButtonsToToolbar(); - void removeButtonsFromToolbar(); bool _initialScreen { false }; QVariant _initialPath { "" }; QVariant _currentPathLoaded { "" }; QString _name; - std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; OffscreenQmlSurface* _qmlOffscreenSurface { nullptr }; QmlWindowClass* _desktopWindow { nullptr }; @@ -262,6 +290,8 @@ protected: State _state { State::Uninitialized }; bool _landscape { false }; bool _showRunningScripts { false }; + + TabletButtonListModel _buttons; }; Q_DECLARE_METATYPE(TabletProxy*); @@ -273,15 +303,11 @@ Q_DECLARE_METATYPE(TabletProxy*); class TabletButtonProxy : public QObject { Q_OBJECT Q_PROPERTY(QUuid uuid READ getUuid) + Q_PROPERTY(QVariantMap properties READ getProperties NOTIFY propertiesChanged) public: TabletButtonProxy(const QVariantMap& properties); ~TabletButtonProxy(); - - Q_INVOKABLE void setQmlButton(QQuickItem* qmlButton); - - void setToolbarButtonProxy(QObject* toolbarButtonProxy); - QUuid getUuid() const { return _uuid; } /**jsdoc @@ -298,9 +324,6 @@ public: */ Q_INVOKABLE void editProperties(const QVariantMap& properties); -public slots: - void clickedSlot() { emit clicked(); } - signals: /**jsdoc * Signaled when this button has been clicked on by the user. @@ -308,12 +331,11 @@ signals: * @returns {Signal} */ void clicked(); + void propertiesChanged(); protected: QUuid _uuid; int _stableOrder; - QQuickItem* _qmlButton { nullptr }; - QObject* _toolbarButtonProxy { nullptr }; QVariantMap _properties; }; diff --git a/tests/shared/src/PathUtilsTests.cpp b/tests/shared/src/PathUtilsTests.cpp index fc31d475d0..1c445908f7 100644 --- a/tests/shared/src/PathUtilsTests.cpp +++ b/tests/shared/src/PathUtilsTests.cpp @@ -13,7 +13,6 @@ #include - QTEST_MAIN(PathUtilsTests) void PathUtilsTests::testPathUtils() {