1
0
Fork 0

Command / property bridges for QML / QtQuick

This commit is contained in:
James Turner 2017-10-05 12:38:27 +01:00
parent 2eab935dff
commit e022e4fed5
7 changed files with 673 additions and 0 deletions

View file

@ -163,6 +163,12 @@ if (HAVE_QT)
QQuickDrawable.hxx
QtQuickFGCanvasItem.cxx
QtQuickFGCanvasItem.hxx
PropertyItemModel.cxx
PropertyItemModel.hxx
FGQmlInstance.cxx
FGQmlInstance.hxx
FGQmlPropertyNode.cxx
FGQmlPropertyNode.hxx
)
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)

106
src/GUI/FGQmlInstance.cxx Normal file
View 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
View 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

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

View 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

View 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"

View 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