Fixing button model exposure

This commit is contained in:
Brad Davis 2017-11-13 16:45:27 -08:00
parent 69029b49f3
commit d71fd151c5
8 changed files with 149 additions and 263 deletions

View file

@ -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 {

View file

@ -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);
}
}
}

View file

@ -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()

View file

@ -2234,6 +2234,10 @@ void Application::initializeUi() {
qmlRegisterType<ResourceImageItem>("Hifi", 1, 0, "ResourceImageItem");
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
{
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
tabletScriptingInterface->getTablet(SYSTEM_TABLET);
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create();

View file

@ -106,6 +106,9 @@ void OffscreenUi::create() {
myContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
myContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
qRegisterMetaType<TabletProxy*>();
qRegisterMetaType<TabletButtonProxy*>();
myContext->setContextProperty("Tablets", tabletScriptingInterface.data());
TabletProxy* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");
myContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership);
}

View file

@ -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<AccountManager>();
if (accountManager->isLoggedIn()) {
username = accountManager->getAccountInfo().getUsername();
}
return username;
}
static Setting::Handle<QStringList> tabletSoundsButtonClick("TabletSounds", QStringList { "/sounds/Button06.wav",
"/sounds/Button04.wav",
@ -39,6 +47,51 @@ static Setting::Handle<QStringList> tabletSoundsButtonClick("TabletSounds", QStr
"/sounds/Tab01.wav",
"/sounds/Tab02.wav" });
TabletButtonListModel::TabletButtonListModel() {
}
TabletButtonListModel::~TabletButtonListModel() {
}
enum ButtonDeviceRole {
ButtonProxyRole = Qt::UserRole,
};
QHash<int, QByteArray> 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<TabletButtonProxy>(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>("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<OffscreenUi>();
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<QObject *>(resultVar);
if (!qmlButton) {
qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject";
return;
}
QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot()));
buttonProxy->setQmlButton(qobject_cast<QQuickItem*>(qmlButton));
}
#endif
static QString getUsername() {
QString username = "Unknown user";
auto accountManager = DependencyManager::get<AccountManager>();
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<TabletButtonProxy>(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<TabletScriptingInterface>()->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<TabletButtonProxy> 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<TabletScriptingInterface>()->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<TabletScriptingInterface>()->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<TabletScriptingInterface>()->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();
}
}

View file

@ -12,13 +12,16 @@
#include <mutex>
#include <atomic>
#include <QObject>
#include <QVariant>
#include <QtCore/QObject>
#include <QtCore/QUuid>
#include <QtCore/QVariant>
#include <QtCore/QAbstractListModel>
#include <QtScript/QScriptValue>
#include <QScriptEngine>
#include <QScriptValueIterator>
#include <QQuickItem>
#include <QUuid>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValueIterator>
#include <QtQuick/QQuickItem>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
@ -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<int, QByteArray> 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<QSharedPointer<TabletButtonProxy>>;
static QHash<int, QByteArray> _roles;
static Qt::ItemFlags _flags;
std::vector<QSharedPointer<TabletButtonProxy>> _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<QSharedPointer<TabletButtonProxy>> _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;
};

View file

@ -13,7 +13,6 @@
#include <PathUtils.h>
QTEST_MAIN(PathUtilsTests)
void PathUtilsTests::testPathUtils() {