1
0
Fork 0

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:
James Turner 2019-06-22 06:07:52 +02:00
parent c2c3bc17f6
commit faa070307d
5 changed files with 336 additions and 28 deletions

View file

@ -158,6 +158,8 @@ if (HAVE_QT)
ThumbnailImageItem.hxx ThumbnailImageItem.hxx
PopupWindowTracker.cxx PopupWindowTracker.cxx
PopupWindowTracker.hxx PopupWindowTracker.hxx
QmlPropertyModel.hxx
QmlPropertyModel.cxx
QmlPositioned.hxx QmlPositioned.hxx
QmlPositioned.cxx QmlPositioned.cxx
QmlNavCacheWrapper.hxx QmlNavCacheWrapper.hxx

View file

@ -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 // This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as // 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 QVariant FGQmlPropertyNode::value() const
{ {
if (!_prop) return propertyValueAsVariant(_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
} }
QString FGQmlPropertyNode::path() const QString FGQmlPropertyNode::path() const
@ -146,6 +123,36 @@ SGPropertyNode_ptr FGQmlPropertyNode::node() const
return _prop; 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) void FGQmlPropertyNode::valueChanged(SGPropertyNode *node)
{ {
if (node != _prop) { if (node != _prop) {

View file

@ -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 // This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as // modify it under the terms of the GNU General Public License as
@ -65,6 +67,8 @@ public:
int childCount() const; int childCount() const;
FGQmlPropertyNode* childAt(int index) const; FGQmlPropertyNode* childAt(int index) const;
static QVariant propertyValueAsVariant(SGPropertyNode* p);
protected: protected:
// SGPropertyChangeListener API // SGPropertyChangeListener API

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

View 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 */