Add property Qt item-model
Allows exposing a set of property nodes as a model in combo boxes, lists, etc.
This commit is contained in:
parent
c2c3bc17f6
commit
faa070307d
5 changed files with 336 additions and 28 deletions
|
@ -158,6 +158,8 @@ if (HAVE_QT)
|
|||
ThumbnailImageItem.hxx
|
||||
PopupWindowTracker.cxx
|
||||
PopupWindowTracker.hxx
|
||||
QmlPropertyModel.hxx
|
||||
QmlPropertyModel.cxx
|
||||
QmlPositioned.hxx
|
||||
QmlPositioned.cxx
|
||||
QmlNavCacheWrapper.hxx
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// Copyright (C) 2020 James Turner <james@flightgear.org>
|
||||
// FGQmlPropertyNode.cxx - expose SGPropertyNode to QML
|
||||
//
|
||||
// Copyright (C) 2019 James Turner <james@flightgear.org>
|
||||
//
|
||||
// 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<SGVec3d>();
|
||||
return QVariant::fromValue(QVector3D(v3.x(), v3.y(), v3.z()));
|
||||
}
|
||||
|
||||
case simgear::props::VEC4D: {
|
||||
const SGVec4d v4 = _prop->getValue<SGVec4d>();
|
||||
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<SGVec3d>();
|
||||
return QVariant::fromValue(QVector3D(v3.x(), v3.y(), v3.z()));
|
||||
}
|
||||
|
||||
case simgear::props::VEC4D: {
|
||||
const SGVec4d v4 = p->getValue<SGVec4d>();
|
||||
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) {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// Copyright (C) 2020 James Turner <james@flightgear.org>
|
||||
// FGQmlPropertyNode.hxx - expose SGPropertyNode to QML
|
||||
//
|
||||
// Copyright (C) 2019 James Turner <james@flightgear.org>
|
||||
//
|
||||
// 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
|
||||
|
||||
|
|
240
src/GUI/QmlPropertyModel.cxx
Normal file
240
src/GUI/QmlPropertyModel.cxx
Normal file
|
@ -0,0 +1,240 @@
|
|||
// Copyright (C) 2020 James Turner <james@flightgear.org>
|
||||
//
|
||||
// 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 <algorithm>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QVector>
|
||||
|
||||
#include <GUI/FGQmlPropertyNode.hxx>
|
||||
#include <GUI/QmlPropertyModel.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
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<int>(std::distance(_roles.begin(), it)) + Qt::UserRole;
|
||||
}
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "adding" << QString::fromStdString(s);
|
||||
_roles.push_back(s);
|
||||
return Qt::UserRole + static_cast<int>(_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<int> 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<int>(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<std::string> _roles;
|
||||
};
|
||||
|
||||
FGQmlPropertyModel::~FGQmlPropertyModel()
|
||||
{
|
||||
d->clear();
|
||||
}
|
||||
|
||||
QString FGQmlPropertyModel::rootPath() const
|
||||
{
|
||||
return d->_rootPath;
|
||||
}
|
||||
|
||||
QString FGQmlPropertyModel::childName() const
|
||||
{
|
||||
return d->_childName;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> FGQmlPropertyModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> 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);
|
||||
}
|
55
src/GUI/QmlPropertyModel.hxx
Normal file
55
src/GUI/QmlPropertyModel.hxx
Normal file
|
@ -0,0 +1,55 @@
|
|||
// Copyright (C) 2020 James Turner <james@flightgear.org>
|
||||
//
|
||||
// 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 <QAbstractListModel>
|
||||
#include <memory>
|
||||
|
||||
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<int, QByteArray> 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<PropertyModelPrivate> d;
|
||||
};
|
||||
|
||||
#endif /* QmlPropertyModel_hpp */
|
Loading…
Reference in a new issue