Command / property bridges for QML / QtQuick
This commit is contained in:
parent
2eab935dff
commit
e022e4fed5
7 changed files with 673 additions and 0 deletions
|
@ -163,6 +163,12 @@ if (HAVE_QT)
|
||||||
QQuickDrawable.hxx
|
QQuickDrawable.hxx
|
||||||
QtQuickFGCanvasItem.cxx
|
QtQuickFGCanvasItem.cxx
|
||||||
QtQuickFGCanvasItem.hxx
|
QtQuickFGCanvasItem.hxx
|
||||||
|
PropertyItemModel.cxx
|
||||||
|
PropertyItemModel.hxx
|
||||||
|
FGQmlInstance.cxx
|
||||||
|
FGQmlInstance.hxx
|
||||||
|
FGQmlPropertyNode.cxx
|
||||||
|
FGQmlPropertyNode.hxx
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)
|
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)
|
||||||
|
|
106
src/GUI/FGQmlInstance.cxx
Normal file
106
src/GUI/FGQmlInstance.cxx
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
#include "FGQmlInstance.hxx"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QJSValueIterator>
|
||||||
|
|
||||||
|
#include "FGQmlPropertyNode.hxx"
|
||||||
|
|
||||||
|
#include <simgear/structure/commands.hxx>
|
||||||
|
|
||||||
|
#include <Main/fg_props.hxx>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void convertJSObjectToPropertyNode(QJSValue js, SGPropertyNode *node);
|
||||||
|
|
||||||
|
void convertSingleJSValue(QJSValue v, SGPropertyNode* node)
|
||||||
|
{
|
||||||
|
if (v.isNumber()) {
|
||||||
|
node->setDoubleValue(v.toNumber());
|
||||||
|
} else if (v.isBool()) {
|
||||||
|
node->setBoolValue(v.toBool());
|
||||||
|
} else if (v.isString()) {
|
||||||
|
node->setStringValue(v.toString().toStdString());
|
||||||
|
} else {
|
||||||
|
qWarning() << Q_FUNC_INFO << "unhandld JS type";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void convertJSObjectToPropertyNode(QJSValue js, SGPropertyNode* node)
|
||||||
|
{
|
||||||
|
QJSValueIterator it(js);
|
||||||
|
while (it.hasNext()) {
|
||||||
|
it.next();
|
||||||
|
const auto propName = it.name().toStdString();
|
||||||
|
QJSValue v = it.value();
|
||||||
|
if (v.isObject()) {
|
||||||
|
// recursion is fun :)
|
||||||
|
SGPropertyNode_ptr childNode = node->getChild(propName, true);
|
||||||
|
convertJSObjectToPropertyNode(v, childNode);
|
||||||
|
} else if (v.isArray()) {
|
||||||
|
// mapping arbitrary JS arrays is slightly uncomfortable, but
|
||||||
|
// this makes the common case work:
|
||||||
|
// foobar: [1,4, "apples", false]
|
||||||
|
// becomes foobar[0] = 1; foobar[1] = 4; foobar[2] = "apples";
|
||||||
|
// foobar[3] = false;
|
||||||
|
// not perfect but useful enough to be worth supporting.
|
||||||
|
QJSValueIterator arrayIt(v);
|
||||||
|
int propIndex = 0;
|
||||||
|
while (arrayIt.hasNext()) {
|
||||||
|
arrayIt.next();
|
||||||
|
SGPropertyNode_ptr childNode = node->getChild(propName, propIndex++, true);
|
||||||
|
if (arrayIt.value().isArray()) {
|
||||||
|
// there is no sensible mapping of this to SGPropertyNode
|
||||||
|
qWarning() << Q_FUNC_INFO <<"arrays of array not possible";
|
||||||
|
} else if (arrayIt.value().isObject()) {
|
||||||
|
convertJSObjectToPropertyNode(arrayIt.value(), childNode);
|
||||||
|
} else {
|
||||||
|
// simple case, just convert the value in place
|
||||||
|
convertSingleJSValue(arrayIt.value(), childNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
convertSingleJSValue(v, node->getChild(propName, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // of anonymous namespace
|
||||||
|
|
||||||
|
FGQmlInstance::FGQmlInstance(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGQmlInstance::command(QString name, QJSValue args)
|
||||||
|
{
|
||||||
|
const std::string cmdName = name.toStdString();
|
||||||
|
|
||||||
|
SGCommandMgr* mgr = SGCommandMgr::instance();
|
||||||
|
if (!mgr->getCommand(cmdName)) {
|
||||||
|
qWarning() << "No such command" << name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SGPropertyNode_ptr propArgs(new SGPropertyNode);
|
||||||
|
|
||||||
|
// convert JSValue args into a property tree.
|
||||||
|
if (args.isObject()) {
|
||||||
|
convertJSObjectToPropertyNode(args, propArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////
|
||||||
|
|
||||||
|
return mgr->execute(cmdName, propArgs, globals->get_props());
|
||||||
|
}
|
||||||
|
|
||||||
|
FGQmlPropertyNode *FGQmlInstance::property(QString path, bool create) const
|
||||||
|
{
|
||||||
|
SGPropertyNode_ptr node = fgGetNode(path.toStdString(), create);
|
||||||
|
if (!node)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
FGQmlPropertyNode* result = new FGQmlPropertyNode;
|
||||||
|
result->setNode(node);
|
||||||
|
return result;
|
||||||
|
}
|
28
src/GUI/FGQmlInstance.hxx
Normal file
28
src/GUI/FGQmlInstance.hxx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#ifndef FGQMLINSTANCE_HXX
|
||||||
|
#define FGQMLINSTANCE_HXX
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <QJSValue>
|
||||||
|
|
||||||
|
// forward decls
|
||||||
|
class FGQmlPropertyNode;
|
||||||
|
|
||||||
|
class FGQmlInstance : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit FGQmlInstance(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
Q_INVOKABLE bool command(QString name, QJSValue args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retrieve a property by its global path
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE FGQmlPropertyNode* property(QString path, bool create = false) const;
|
||||||
|
signals:
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FGQMLINSTANCE_HXX
|
143
src/GUI/FGQmlPropertyNode.cxx
Normal file
143
src/GUI/FGQmlPropertyNode.cxx
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
#include "FGQmlPropertyNode.hxx"
|
||||||
|
|
||||||
|
#include <QVector3D>
|
||||||
|
#include <QVector4D>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include <simgear/props/props.hxx>
|
||||||
|
#include <simgear/props/vectorPropTemplates.hxx>
|
||||||
|
#include <simgear/math/SGMath.hxx>
|
||||||
|
|
||||||
|
#include <Main/fg_props.hxx>
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
FGQmlPropertyNode::FGQmlPropertyNode(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGQmlPropertyNode::set(QVariant newValue)
|
||||||
|
{
|
||||||
|
if (!_prop || !_prop->getAttribute(SGPropertyNode::WRITE))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// we still need to short circuit setting
|
||||||
|
if (newValue == value()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (_prop->getType()) {
|
||||||
|
case simgear::props::INT:
|
||||||
|
case simgear::props::LONG:
|
||||||
|
_prop->setIntValue(newValue.toInt());
|
||||||
|
case simgear::props::BOOL: _prop->setBoolValue(newValue.toBool());
|
||||||
|
case simgear::props::DOUBLE: _prop->setDoubleValue(newValue.toDouble());
|
||||||
|
case simgear::props::FLOAT: _prop->setFloatValue(newValue.toFloat());
|
||||||
|
case simgear::props::STRING: _prop->setStringValue(newValue.toString().toStdString());
|
||||||
|
|
||||||
|
case simgear::props::VEC3D: {
|
||||||
|
QVector3D v = newValue.value<QVector3D>();
|
||||||
|
_prop->setValue(SGVec3d(v.x(), v.y(), v.z()));
|
||||||
|
}
|
||||||
|
|
||||||
|
case simgear::props::VEC4D: {
|
||||||
|
QVector4D v = newValue.value<QVector4D>();
|
||||||
|
_prop->setValue(SGVec4d(v.x(), v.y(), v.z(), v.w()));
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
qWarning() << Q_FUNC_INFO << "handle untyped property writes";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FGQmlPropertyNode::path() const
|
||||||
|
{
|
||||||
|
if (!_prop)
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
return QString::fromStdString(_prop->getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
FGQmlPropertyNode *FGQmlPropertyNode::parentProp() const
|
||||||
|
{
|
||||||
|
if (!_prop || !_prop->getParent())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto pp = new FGQmlPropertyNode;
|
||||||
|
pp->setNode(_prop->getParent());
|
||||||
|
return pp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGQmlPropertyNode::setNode(SGPropertyNode_ptr node)
|
||||||
|
{
|
||||||
|
if (node == _prop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_prop) {
|
||||||
|
_prop->removeChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
_prop = node;
|
||||||
|
if (_prop) {
|
||||||
|
_prop->addChangeListener(this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit parentPropChanged(parentProp());
|
||||||
|
emit pathChanged(path());
|
||||||
|
emit valueChangedNotify(value());
|
||||||
|
}
|
||||||
|
|
||||||
|
SGPropertyNode_ptr FGQmlPropertyNode::node() const
|
||||||
|
{
|
||||||
|
return _prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGQmlPropertyNode::valueChanged(SGPropertyNode *node)
|
||||||
|
{
|
||||||
|
if (node != _prop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit valueChangedNotify(value());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGQmlPropertyNode::setPath(QString path)
|
||||||
|
{
|
||||||
|
SGPropertyNode_ptr node = fgGetNode(path.toStdString(), false /* don't create */);
|
||||||
|
setNode(node);
|
||||||
|
}
|
||||||
|
|
54
src/GUI/FGQmlPropertyNode.hxx
Normal file
54
src/GUI/FGQmlPropertyNode.hxx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#ifndef FGQMLPROPERTYNODE_HXX
|
||||||
|
#define FGQMLPROPERTYNODE_HXX
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
#include <simgear/props/props.hxx>
|
||||||
|
|
||||||
|
class FGQmlPropertyNode : public QObject,
|
||||||
|
public SGPropertyChangeListener
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit FGQmlPropertyNode(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
Q_PROPERTY(QVariant value READ value NOTIFY valueChangedNotify)
|
||||||
|
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
||||||
|
Q_PROPERTY(FGQmlPropertyNode* parentProp READ parentProp NOTIFY parentPropChanged)
|
||||||
|
|
||||||
|
Q_INVOKABLE bool set(QVariant newValue);
|
||||||
|
|
||||||
|
// children accessor
|
||||||
|
QVariant value() const;
|
||||||
|
|
||||||
|
QString path() const;
|
||||||
|
|
||||||
|
FGQmlPropertyNode* parentProp() const;
|
||||||
|
|
||||||
|
// C++ API, not accessible from QML
|
||||||
|
void setNode(SGPropertyNode_ptr node);
|
||||||
|
|
||||||
|
SGPropertyNode_ptr node() const;
|
||||||
|
protected:
|
||||||
|
// SGPropertyChangeListener API
|
||||||
|
|
||||||
|
void valueChanged(SGPropertyNode * node) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
void valueChangedNotify(QVariant value);
|
||||||
|
|
||||||
|
void pathChanged(QString path);
|
||||||
|
|
||||||
|
void parentPropChanged(FGQmlPropertyNode* parentProp);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
void setPath(QString path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
SGPropertyNode_ptr _prop;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FGQMLPROPERTYNODE_HXX
|
257
src/GUI/PropertyItemModel.cxx
Normal file
257
src/GUI/PropertyItemModel.cxx
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
#include "PropertyItemModel.hxx"
|
||||||
|
|
||||||
|
#include <Main/fg_props.hxx>
|
||||||
|
|
||||||
|
#include "FGQmlPropertyNode.hxx"
|
||||||
|
|
||||||
|
class PropertyItemModelRoleMapping : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
||||||
|
Q_PROPERTY(QString roleName READ roleName WRITE setRoleName NOTIFY roleNameChanged)
|
||||||
|
public:
|
||||||
|
|
||||||
|
QString path() const
|
||||||
|
{
|
||||||
|
return QString::fromStdString(_propertyPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString roleName() const
|
||||||
|
{
|
||||||
|
return QString::fromLatin1(_roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setPath(QString path)
|
||||||
|
{
|
||||||
|
if (_propertyPath == path.toStdString());
|
||||||
|
return;
|
||||||
|
|
||||||
|
_propertyPath = path.toStdString();
|
||||||
|
emit pathChanged(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRoleName(QString roleName)
|
||||||
|
{
|
||||||
|
if (_roleName == roleName.toLatin1())
|
||||||
|
return;
|
||||||
|
|
||||||
|
_roleName = roleName.toLatin1();
|
||||||
|
emit roleNameChanged(roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void pathChanged(QString path);
|
||||||
|
|
||||||
|
void roleNameChanged(QString roleName);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class PropertyItemModel;
|
||||||
|
std::string _propertyPath;
|
||||||
|
QByteArray _roleName;
|
||||||
|
};
|
||||||
|
|
||||||
|
PropertyItemModel::PropertyItemModel()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int PropertyItemModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
return _nodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant PropertyItemModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (index.row() >= _nodes.size())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
SGPropertyNode_ptr n = _nodes.at(index.row());
|
||||||
|
Q_ASSERT(n);
|
||||||
|
int mappingIndex = role - Qt::UserRole;
|
||||||
|
if ((mappingIndex < 0) || (mappingIndex >= _roleMappings.size()))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const std::string& nodePath = _roleMappings.at(mappingIndex)->_propertyPath;
|
||||||
|
SGPropertyNode_ptr valueNode = n->getNode(nodePath);
|
||||||
|
|
||||||
|
// convert property value to a variant
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> PropertyItemModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> result;
|
||||||
|
int count = 0;
|
||||||
|
for (auto m : _roleMappings) {
|
||||||
|
result[Qt::UserRole + count++] = m->_roleName;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PropertyItemModel::rootPath() const
|
||||||
|
{
|
||||||
|
return QString::fromStdString(_modelRoot->getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PropertyItemModel::childNameFilter() const
|
||||||
|
{
|
||||||
|
return _childNameFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGQmlPropertyNode *PropertyItemModel::root() const
|
||||||
|
{
|
||||||
|
auto n = new FGQmlPropertyNode;
|
||||||
|
n->setNode(_modelRoot);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertyItemModel::setRootPath(QString rootPath)
|
||||||
|
{
|
||||||
|
if (_modelRoot->getPath() == rootPath.toStdString())
|
||||||
|
return;
|
||||||
|
|
||||||
|
innerSetRoot(fgGetNode(rootPath.toStdString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertyItemModel::setChildNameFilter(QString childNameFilter)
|
||||||
|
{
|
||||||
|
if (_childNameFilter == childNameFilter)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_childNameFilter = childNameFilter;
|
||||||
|
emit childNameFilterChanged(_childNameFilter);
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
rebuild();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertyItemModel::setRoot(FGQmlPropertyNode *root)
|
||||||
|
{
|
||||||
|
if (root->node() == _modelRoot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
innerSetRoot(root->node());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertyItemModel::valueChanged(SGPropertyNode *node)
|
||||||
|
{
|
||||||
|
// if node's parent is one of our nodes, emit data changed
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertyItemModel::childAdded(SGPropertyNode *parent, SGPropertyNode *child)
|
||||||
|
{
|
||||||
|
if (parent == _modelRoot) {
|
||||||
|
if (_childNameFilter.isEmpty() || (child->getNameString() == _childNameFilter.toStdString())) {
|
||||||
|
// insert
|
||||||
|
// need to find appropriate index based on position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if parent is one of our nodes, emit data changed for it
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertyItemModel::childRemoved(SGPropertyNode *parent, SGPropertyNode *child)
|
||||||
|
{
|
||||||
|
if (parent == _modelRoot) {
|
||||||
|
// remove row
|
||||||
|
}
|
||||||
|
|
||||||
|
// if parent is one of our nodes, emit data changed for it
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QQmlListProperty<PropertyItemModelRoleMapping> PropertyItemModel::mappings()
|
||||||
|
{
|
||||||
|
return QQmlListProperty<PropertyItemModelRoleMapping>(this, nullptr,
|
||||||
|
&PropertyItemModel::append_mapping,
|
||||||
|
&PropertyItemModel::count_mapping,
|
||||||
|
&PropertyItemModel::at_mapping,
|
||||||
|
&PropertyItemModel::clear_mapping
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertyItemModel::rebuild()
|
||||||
|
{
|
||||||
|
if (!_modelRoot) {
|
||||||
|
_nodes.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_childNameFilter.isEmpty()) {
|
||||||
|
// all children
|
||||||
|
_nodes.clear();
|
||||||
|
const int nChildren = _modelRoot->nChildren();
|
||||||
|
_nodes.resize(nChildren);
|
||||||
|
for (int c=0; c < nChildren; ++c) {
|
||||||
|
_nodes[c] = _modelRoot->getChild(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_nodes = _modelRoot->getChildren(_childNameFilter.toStdString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertyItemModel::innerSetRoot(SGPropertyNode_ptr root)
|
||||||
|
{
|
||||||
|
if (_modelRoot) {
|
||||||
|
_modelRoot->removeChangeListener(this);;
|
||||||
|
}
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
_modelRoot = root;
|
||||||
|
if (_modelRoot) {
|
||||||
|
_modelRoot->addChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuild();
|
||||||
|
emit rootChanged();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertyItemModel::append_mapping(QQmlListProperty<PropertyItemModelRoleMapping> *list,
|
||||||
|
PropertyItemModelRoleMapping *mapping)
|
||||||
|
{
|
||||||
|
PropertyItemModel *model = qobject_cast<PropertyItemModel *>(list->object);
|
||||||
|
if (model) {
|
||||||
|
model->_roleMappings.append(mapping);
|
||||||
|
model->mappingsChanged();
|
||||||
|
model->rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyItemModelRoleMapping* PropertyItemModel::at_mapping(QQmlListProperty<PropertyItemModelRoleMapping> *list, int index)
|
||||||
|
{
|
||||||
|
PropertyItemModel *model = qobject_cast<PropertyItemModel *>(list->object);
|
||||||
|
if (model) {
|
||||||
|
return model->_roleMappings.at(index);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PropertyItemModel::count_mapping(QQmlListProperty<PropertyItemModelRoleMapping> *list)
|
||||||
|
{
|
||||||
|
PropertyItemModel *model = qobject_cast<PropertyItemModel *>(list->object);
|
||||||
|
if (model) {
|
||||||
|
return model->_roleMappings.size();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertyItemModel::clear_mapping(QQmlListProperty<PropertyItemModelRoleMapping> *list)
|
||||||
|
{
|
||||||
|
PropertyItemModel *model = qobject_cast<PropertyItemModel *>(list->object);
|
||||||
|
if (model) {
|
||||||
|
model->_roleMappings.clear();
|
||||||
|
model->mappingsChanged();
|
||||||
|
model->rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "PropertyItemModel.moc"
|
79
src/GUI/PropertyItemModel.hxx
Normal file
79
src/GUI/PropertyItemModel.hxx
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
#ifndef PROPERTYITEMMODEL_HXX
|
||||||
|
#define PROPERTYITEMMODEL_HXX
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QQmlListProperty>
|
||||||
|
|
||||||
|
#include <simgear/props/props.hxx>
|
||||||
|
|
||||||
|
class PropertyItemModelRoleMapping;
|
||||||
|
class FGQmlPropertyNode;
|
||||||
|
|
||||||
|
class PropertyItemModel : public QAbstractListModel,
|
||||||
|
public SGPropertyChangeListener
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(QString rootPath READ rootPath WRITE setRootPath NOTIFY rootChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(FGQmlPropertyNode* root READ root WRITE setRoot NOTIFY rootChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(QString childNameFilter READ childNameFilter WRITE setChildNameFilter NOTIFY childNameFilterChanged)
|
||||||
|
Q_PROPERTY(QQmlListProperty<PropertyItemModelRoleMapping> mappings READ mappings NOTIFY mappingsChanged)
|
||||||
|
// list property storing child/role mapping
|
||||||
|
|
||||||
|
QQmlListProperty<PropertyItemModelRoleMapping> mappings();
|
||||||
|
public:
|
||||||
|
PropertyItemModel();
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent) const override;
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
QString rootPath() const;
|
||||||
|
|
||||||
|
QString childNameFilter() const;
|
||||||
|
|
||||||
|
FGQmlPropertyNode* root() const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setRootPath(QString rootPath);
|
||||||
|
|
||||||
|
void setChildNameFilter(QString childNameFilter);
|
||||||
|
|
||||||
|
void setRoot(FGQmlPropertyNode* root);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void rootChanged();
|
||||||
|
|
||||||
|
void childNameFilterChanged(QString childNameFilter);
|
||||||
|
|
||||||
|
void mappingsChanged();
|
||||||
|
protected:
|
||||||
|
// implement property-change listener to watch for changes to the
|
||||||
|
// underlying nodes
|
||||||
|
void valueChanged(SGPropertyNode *node) override;
|
||||||
|
void childAdded(SGPropertyNode *parent, SGPropertyNode *child) override;
|
||||||
|
void childRemoved(SGPropertyNode *parent, SGPropertyNode *child) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void rebuild();
|
||||||
|
|
||||||
|
void innerSetRoot(SGPropertyNode_ptr root);
|
||||||
|
|
||||||
|
static void append_mapping(QQmlListProperty<PropertyItemModelRoleMapping> *list,
|
||||||
|
PropertyItemModelRoleMapping *mapping);
|
||||||
|
static PropertyItemModelRoleMapping* at_mapping(QQmlListProperty<PropertyItemModelRoleMapping> *list, int index);
|
||||||
|
static int count_mapping(QQmlListProperty<PropertyItemModelRoleMapping> *list);
|
||||||
|
static void clear_mapping(QQmlListProperty<PropertyItemModelRoleMapping> *list);
|
||||||
|
|
||||||
|
SGPropertyNode_ptr _modelRoot;
|
||||||
|
QVector<PropertyItemModelRoleMapping*> _roleMappings;
|
||||||
|
QString _childNameFilter;
|
||||||
|
|
||||||
|
mutable std::vector<SGPropertyNode_ptr> _nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PROPERTYITEMMODEL_HXX
|
Loading…
Reference in a new issue