From faa070307dc9d1b9531606be7600eede81a9f6d5 Mon Sep 17 00:00:00 2001 From: James Turner Date: Sat, 22 Jun 2019 06:07:52 +0200 Subject: [PATCH] Add property Qt item-model Allows exposing a set of property nodes as a model in combo boxes, lists, etc. --- src/GUI/CMakeLists.txt | 2 + src/GUI/FGQmlPropertyNode.cxx | 61 +++++---- src/GUI/FGQmlPropertyNode.hxx | 6 +- src/GUI/QmlPropertyModel.cxx | 240 ++++++++++++++++++++++++++++++++++ src/GUI/QmlPropertyModel.hxx | 55 ++++++++ 5 files changed, 336 insertions(+), 28 deletions(-) create mode 100644 src/GUI/QmlPropertyModel.cxx create mode 100644 src/GUI/QmlPropertyModel.hxx diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index 89720fcdf..038d1d7a6 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -158,6 +158,8 @@ if (HAVE_QT) ThumbnailImageItem.hxx PopupWindowTracker.cxx PopupWindowTracker.hxx + QmlPropertyModel.hxx + QmlPropertyModel.cxx QmlPositioned.hxx QmlPositioned.cxx QmlNavCacheWrapper.hxx diff --git a/src/GUI/FGQmlPropertyNode.cxx b/src/GUI/FGQmlPropertyNode.cxx index eae1f7f51..5a07e6c89 100644 --- a/src/GUI/FGQmlPropertyNode.cxx +++ b/src/GUI/FGQmlPropertyNode.cxx @@ -1,4 +1,6 @@ -// Copyright (C) 2020 James Turner +// FGQmlPropertyNode.cxx - expose SGPropertyNode to QML +// +// Copyright (C) 2019 James Turner // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -74,32 +76,7 @@ bool FGQmlPropertyNode::set(QVariant newValue) QVariant FGQmlPropertyNode::value() const { - if (!_prop) - return {}; - - switch (_prop->getType()) { - case simgear::props::INT: - case simgear::props::LONG: - return _prop->getIntValue(); - case simgear::props::BOOL: return _prop->getBoolValue(); - case simgear::props::DOUBLE: return _prop->getDoubleValue(); - case simgear::props::FLOAT: return _prop->getFloatValue(); - case simgear::props::STRING: return QString::fromStdString(_prop->getStringValue()); - - case simgear::props::VEC3D: { - const SGVec3d v3 = _prop->getValue(); - return QVariant::fromValue(QVector3D(v3.x(), v3.y(), v3.z())); - } - - case simgear::props::VEC4D: { - const SGVec4d v4 = _prop->getValue(); - return QVariant::fromValue(QVector4D(v4.x(), v4.y(), v4.z(), v4.w())); - } - default: - break; - } - - return {}; // null qvariant + return propertyValueAsVariant(_prop); } QString FGQmlPropertyNode::path() const @@ -146,6 +123,36 @@ SGPropertyNode_ptr FGQmlPropertyNode::node() const return _prop; } +QVariant FGQmlPropertyNode::propertyValueAsVariant(SGPropertyNode* p) +{ + if (!p) + return {}; + + switch (p->getType()) { + case simgear::props::INT: + case simgear::props::LONG: + return p->getIntValue(); + case simgear::props::BOOL: return p->getBoolValue(); + case simgear::props::DOUBLE: return p->getDoubleValue(); + case simgear::props::FLOAT: return p->getFloatValue(); + case simgear::props::STRING: return QString::fromStdString(p->getStringValue()); + + case simgear::props::VEC3D: { + const SGVec3d v3 = p->getValue(); + return QVariant::fromValue(QVector3D(v3.x(), v3.y(), v3.z())); + } + + case simgear::props::VEC4D: { + const SGVec4d v4 = p->getValue(); + return QVariant::fromValue(QVector4D(v4.x(), v4.y(), v4.z(), v4.w())); + } + default: + break; + } + + return {}; // null qvariant +} + void FGQmlPropertyNode::valueChanged(SGPropertyNode *node) { if (node != _prop) { diff --git a/src/GUI/FGQmlPropertyNode.hxx b/src/GUI/FGQmlPropertyNode.hxx index 5f7877eed..ebdbe22ec 100644 --- a/src/GUI/FGQmlPropertyNode.hxx +++ b/src/GUI/FGQmlPropertyNode.hxx @@ -1,4 +1,6 @@ -// Copyright (C) 2020 James Turner +// FGQmlPropertyNode.hxx - expose SGPropertyNode to QML +// +// Copyright (C) 2019 James Turner // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -65,6 +67,8 @@ public: int childCount() const; FGQmlPropertyNode* childAt(int index) const; + static QVariant propertyValueAsVariant(SGPropertyNode* p); + protected: // SGPropertyChangeListener API diff --git a/src/GUI/QmlPropertyModel.cxx b/src/GUI/QmlPropertyModel.cxx new file mode 100644 index 000000000..846934536 --- /dev/null +++ b/src/GUI/QmlPropertyModel.cxx @@ -0,0 +1,240 @@ +// Copyright (C) 2020 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#include "config.h" + +#include "QmlPropertyModel.hxx" + +#include + +#include +#include + +#include +#include +#include
+#include + +class FGQmlPropertyModel::PropertyModelPrivate : public SGPropertyChangeListener +{ +public: + void clear() + { + if (_props) { + _props->removeChangeListener(this); + _props.clear(); + _roles.clear(); + } + } + + void computeProps() + { + _props = fgGetNode(_rootPath.toStdString()); + if (!_props) { + qWarning() << "Passed non-existant path to QmlPropertyModel:" << _rootPath; + } + + _props->addChangeListener(this); + + if (_childName.isEmpty()) { + // all children, no filtering by name + const auto sz = _props->nChildren(); + _directChildren.resize(sz); + for (auto i = 0; i < sz; ++i) { + _directChildren[i] = _props->getChild(i); + } + } else { + _directChildren = _props->getChildren(_childName.toStdString()); + } + + cacheRoleNames(); + } + + void cacheRoleNames() + { + auto oldRoles = _roles; + _roles.clear(); + for (auto p : _directChildren) { + const auto nc = p->nChildren(); + for (int i = 0; i < nc; ++i) { + roleForNode(p->getChild(i)->getNameString()); + } + } + + if (_roles != oldRoles) { + // we need to model-reset to tell QML about new names + p->beginResetModel(); + p->endResetModel(); + } + } + + int roleForNode(const std::string& s) + { + auto it = std::find(_roles.begin(), _roles.end(), s); + if (it != _roles.end()) { + return static_cast(std::distance(_roles.begin(), it)) + Qt::UserRole; + } + + qDebug() << Q_FUNC_INFO << "adding" << QString::fromStdString(s); + _roles.push_back(s); + return Qt::UserRole + static_cast(_roles.size() - 1); + } + + void valueChanged(SGPropertyNode* node) override + { + auto it = std::find(_directChildren.begin(), _directChildren.end(), node); + if (it == _directChildren.end()) + return; + + doDataChanged(it, node); + } + + void doDataChanged(const simgear::PropertyList::iterator& it, SGPropertyNode* node) + { + QVector roles; + roles.append(roleForNode(node->getNameString())); + QModelIndex m = p->index(std::distance(_directChildren.begin(), it)); + p->dataChanged(m, m, roles); + } + + void childAdded(SGPropertyNode* parent, SGPropertyNode* child) override + { + if (parent == _props) { + if (!_childName.isEmpty() && (child->getNameString() != _childName.toStdString())) { + // doesn't pass name filter, don't care + return; + } + + int insertRow = child->getIndex(); + if (!_childName.isEmpty()) { + // always an append + insertRow = _directChildren.size(); + } + + p->beginInsertRows(QModelIndex{}, insertRow, insertRow); + _directChildren.push_back(child); + p->endInsertRows(); + return; + } + + auto it = std::find(_directChildren.begin(), _directChildren.end(), parent); + if (it == _directChildren.end()) + return; + + doDataChanged(it, child); + } + + void childRemoved(SGPropertyNode* parent, SGPropertyNode* child) override + { + if (parent == _props) { + if (!_childName.isEmpty() && (child->getNameString() != _childName.toStdString())) { + // doesn't pass name filter, don't care + return; + } + + auto it = std::find(_directChildren.begin(), _directChildren.end(), child); + if (it == _directChildren.end()) { + SG_LOG(SG_GUI, SG_DEV_ALERT, "Bug in QmlPropertyModel - child not found when removing:" << parent->getPath() << " - " << child->getName()); + return; + } + + int row = static_cast(std::distance(_directChildren.begin(), it)); + p->beginRemoveRows(QModelIndex{}, row, row); + _directChildren.erase(it); + p->endInsertRows(); + return; + } + + auto it = std::find(_directChildren.begin(), _directChildren.end(), parent); + if (it == _directChildren.end()) + return; + + // actually the value will be null now + doDataChanged(it, child); + } + + QString _rootPath; + QString _childName; + + FGQmlPropertyModel* p; + SGPropertyNode_ptr _props; + simgear::PropertyList _directChildren; + std::vector _roles; +}; + +FGQmlPropertyModel::~FGQmlPropertyModel() +{ + d->clear(); +} + +QString FGQmlPropertyModel::rootPath() const +{ + return d->_rootPath; +} + +QString FGQmlPropertyModel::childName() const +{ + return d->_childName; +} + +QHash FGQmlPropertyModel::roleNames() const +{ + QHash r; + for (int i = 0; i < d->_roles.size(); ++i) { + r[i] = QByteArray::fromStdString(d->_roles.at(i)); + } + return r; +} + +QVariant FGQmlPropertyModel::data(const QModelIndex& m, int role) const +{ + auto node = d->_directChildren.at(m.row()); + const int r = role - Qt::UserRole; + assert(r < d->_roles.size()); + const auto& propName = d->_roles.at(r); + const auto prop = node->getChild(propName); + if (!prop) + return {}; // no data for role + return FGQmlPropertyNode::propertyValueAsVariant(prop); +} + +void FGQmlPropertyModel::setRootPath(QString rootPath) +{ + if (d->_rootPath == rootPath) + return; + + beginResetModel(); + d->_rootPath = rootPath; + d->clear(); + d->computeProps(); + endResetModel(); + + emit rootPathChanged(d->_rootPath); +} + +void FGQmlPropertyModel::setChildName(QString childName) +{ + if (d->_childName == childName) + return; + + beginResetModel(); + d->_childName = childName; + d->clear(); + d->computeProps(); + endResetModel(); + + emit childNameChanged(d->_childName); +} diff --git a/src/GUI/QmlPropertyModel.hxx b/src/GUI/QmlPropertyModel.hxx new file mode 100644 index 000000000..3bb4652f5 --- /dev/null +++ b/src/GUI/QmlPropertyModel.hxx @@ -0,0 +1,55 @@ +// Copyright (C) 2020 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#ifndef QmlPropertyModel_hpp +#define QmlPropertyModel_hpp + +#include +#include + +class FGQmlPropertyModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QString rootPath READ rootPath WRITE setRootPath NOTIFY rootPathChanged); + Q_PROPERTY(QString childName READ childName WRITE setChildName NOTIFY childNameChanged); + +public: + FGQmlPropertyModel(QObject* parent = nullptr); + ~FGQmlPropertyModel() override; + QString rootPath() const; + + QString childName() const; + + QHash roleNames() const override; + + QVariant data(const QModelIndex& m, int role) const override; +public slots: + void setRootPath(QString rootPath); + + void setChildName(QString childName); + +signals: + void rootPathChanged(QString rootPath); + + void childNameChanged(QString childName); + +private: + class PropertyModelPrivate; + std::unique_ptr d; +}; + +#endif /* QmlPropertyModel_hpp */