summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Hartmetz <andreas.hartmetz@kdab.com>2016-10-05 22:57:29 +0200
committerVolker Krause <volker.krause@kdab.com>2016-11-08 08:53:54 +0000
commit7dc71617eb3a762a7769a8442348af59e324895b (patch)
tree99741dd298cb0ee8caca004df8d3adda96a46d6e
parenta227115d0e8a4cfe017bae75b3fba6f3f9935311 (diff)
QIviProperty model with editing feature
It has been tested to work with QIviClimateControl in the neptune-ui demo. Change-Id: Ia40d990d1fd6ed0550d5693d1f108c27f2f5258e Reviewed-by: Volker Krause <volker.krause@kdab.com>
-rw-r--r--CMakeLists.txt4
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/qtivipropertymodel.cpp521
-rw-r--r--src/qtivipropertymodel.h87
-rw-r--r--src/qtivipropertyoverrider.cpp199
-rw-r--r--src/qtivipropertyoverrider.h68
-rw-r--r--src/qtivisupport.cpp49
-rw-r--r--src/qtivisupport.h3
-rw-r--r--src/qtivisupportwidget.cpp3
9 files changed, 779 insertions, 158 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fb679ab..efcc898 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,11 +23,13 @@ find_package(Qt5Qml REQUIRED)
find_package(Qt5IviCore REQUIRED)
find_package(Qt5IviVehicleFunctions REQUIRED)
-# more aggressive linker flags (for testing the GammaRay-provided development files for completeness)
if(CMAKE_SYSTEM_NAME MATCHES Linux OR CMAKE_SYSTEM_NAME STREQUAL GNU)
if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+ # more aggressive linker flags (for testing the GammaRay-provided development files for completeness)
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--fatal-warnings -Wl,--no-undefined -lc ${CMAKE_SHARED_LINKER_FLAGS}")
set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--fatal-warnings -Wl,--no-undefined -lc ${CMAKE_MODULE_LINKER_FLAGS}")
+ # always a good idea
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
endif()
endif()
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index dc4262c..eb2599b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,5 +1,6 @@
# probe plugin
-gammaray_add_plugin(gammaray_qtivi JSON gammaray_qtivi.json SOURCES qtivisupport.cpp qtivipropertymodel.cpp)
+gammaray_add_plugin(gammaray_qtivi JSON gammaray_qtivi.json SOURCES qtivisupport.cpp qtivipropertymodel.cpp
+ qtivipropertyoverrider.cpp)
target_link_libraries(gammaray_qtivi gammaray_core Qt5::IviCore Qt5::IviVehicleFunctions)
# ui part
diff --git a/src/qtivipropertymodel.cpp b/src/qtivipropertymodel.cpp
index eaf1f74..75d3c79 100644
--- a/src/qtivipropertymodel.cpp
+++ b/src/qtivipropertymodel.cpp
@@ -28,168 +28,495 @@
#include "qtivipropertymodel.h"
+#include <core/enumrepositoryserver.h>
+#include <core/enumutil.h>
#include <core/probe.h>
#include <core/util.h>
+#include <core/varianthandler.h>
#include <common/objectmodel.h>
+#include <QIviAbstractZonedFeature>
#include <QIviProperty>
#include <QThread>
+#include <QMetaObject>
+#include <QMetaProperty>
#include <QMutexLocker>
#include <QSignalMapper>
+#include <iostream>
+
+//#define IF_DEBUG(x) (x)
+#define IF_DEBUG(x)
+
using namespace GammaRay;
-QtIVIPropertyModel::QtIVIPropertyModel(QObject *parent)
- : QAbstractTableModel(parent)
+enum Columns
+{
+ NameColumn = 0,
+ ValueColumn,
+ OverrideColumn,
+ TypeColumn,
+ ColumnCount
+};
+
+QtIviPropertyModel::QtIviPropertyModel(Probe *probe)
+ : QAbstractItemModel(probe)
{
+ connect(probe, SIGNAL(objectCreated(QObject*)), this, SLOT(objectAdded(QObject*)));
+ connect(probe, SIGNAL(objectDestroyed(QObject*)), this, SLOT(objectRemoved(QObject*)));
+ connect(probe, SIGNAL(objectReparented(QObject*)), this, SLOT(objectReparented(QObject*)));
}
-QtIVIPropertyModel::~QtIVIPropertyModel()
+QtIviPropertyModel::IviProperty::IviProperty(QIviProperty *_value, const QMetaProperty &metaProperty)
+ : name(QString::fromUtf8(metaProperty.name())),
+ value(_value),
+ overrider(_value, metaProperty.isWritable())
{
}
-int QtIVIPropertyModel::columnCount(const QModelIndex &parent) const
+QtIviPropertyModel::IviProperty::IviProperty()
+ : value(nullptr)
{
- Q_UNUSED(parent);
- return ColumnCount;
}
-int QtIVIPropertyModel::rowCount(const QModelIndex& parent) const
+// The out-argument is the easiest way to allow for the case that no matching QMetaProperty was found
+static bool addIviProperty(const QObject *carrier, const QIviProperty *property,
+ QtIviPropertyModel::IviProperty *modelProperty)
{
- if (parent.isValid())
- return 0;
+ //int qtIviPropertyType = qMetaTypeId<QIviProperty *>(); // TODO register the metatype in QtIvi
+ QByteArray qtIviPropertyTypeName("QIviProperty*");
+ const QMetaObject *mo = carrier->metaObject();
+ static const int count = mo->propertyCount();
+ for (int i = 0; i < count; i++) {
+ const QMetaProperty mp = mo->property(i);
+ //if (mp.userType() == qtIviPropertyType) {
+ if (qtIviPropertyTypeName == mp.typeName()) {
+ QIviProperty *value = *reinterpret_cast<QIviProperty **>(mp.read(carrier).data());
+ if (value == property) {
+ *modelProperty = QtIviPropertyModel::IviProperty(value, mp);
+ return true;
+ }
+ }
+ }
+ return false;
+}
- return m_sourceModel->rowCount();
+int QtIviPropertyModel::indexOfPropertyCarrier(const QObject *carrier) const
+{
+ for (uint i = 0; i < m_propertyCarriers.size(); i++) {
+ if (m_propertyCarriers.at(i).carrier == carrier) {
+ return i;
+ }
+ }
+ return -1;
}
-QVariant QtIVIPropertyModel::headerData(int section, Qt::Orientation orientation, int role) const
+int QtIviPropertyModel::IviPropertyCarrier::indexOfProperty(const QIviProperty *property) const
{
- Q_ASSERT(section >= 0);
+ for (uint i = 0; i < iviProperties.size(); i++) {
+ if (iviProperties.at(i).value == property) {
+ return i;
+ }
+ }
+ return -1;
+}
- if (role == Qt::DisplayRole) {
- switch (section) {
- case AddressColumn:
- return tr("Address");
- case BackendValueColumn:
- return tr("Backend Value");
- case OverrideValueColumn:
- return tr("Override Value");
- case OverrideColumn:
- return tr("Override");
- default:
- return QVariant();
+void QtIviPropertyModel::objectAdded(QObject *obj)
+{
+ // We see a property carriers in a half-constructed state here, let's take QIviClimateControl as
+ // an example. The QMetaObject::className() is "QIviClimateControl", but the list of properties
+ // is that of the superclass QIviAbstractFeature. So we need to link property carriers with
+ // their properties later than immediately upon on creation of the carrier. We could:
+ // - enqueue all created objects internally and batch-process them at the next return to event
+ // loop, using a zero-timeout timer or an event
+ // - add the property carrier (parent) on demand when a child property is observed
+ // None of them is very good because both rely on behavior that isn't really guaranteed.
+ // For now, we're using the second option.
+
+ IF_DEBUG(std::cout << "QtIviPropertyModel::objectAdded() " << obj << std::endl);
+ // see Probe::objectCreated, that promises a valid object in the main thread here
+ Q_ASSERT(thread() == QThread::currentThread());
+ Q_ASSERT(Probe::instance()->isValidObject(obj));
+ Q_ASSERT(!m_seenObjects.contains(obj));
+
+ if (QIviProperty *property = qobject_cast<QIviProperty*>(obj)) {
+ QObject *carrier = property->parent();
+ // Things that turned out to be true in practice, check that they stay true
+ Q_ASSERT(carrier);
+ // Q_ASSERT(m_seenObjects.contains(carrier)); ...
+ // should always be true except for one problem: Our gammaray_qtivi.json file declares that
+ // this plugin should only be loaded when instances of certain classes appear for the first
+ // time (a Gammaray mechanism to reduce overhead); QObject is not in that list because we
+ // only *really* care about a few special QObjects - QIviProperties and their parents.
+ // The parents don't trigger loading of the plugin so shortly after being loaded we will
+ // see objects with not yet known parents.
+ IF_DEBUG(std::cout << "QtIviPropertyModel::objectAdded() - parent is a carrier "
+ << carrier << std::endl);
+
+ const bool wasKnownCarrier = m_seenObjects.value(carrier);
+ int carrierRow = -1;
+ std::vector<IviProperty> *iviProperties = nullptr;
+
+ if (!wasKnownCarrier) {
+ m_seenObjects.insert(carrier, true);
+
+ IviPropertyCarrier carrierStruct;
+ carrierStruct.carrier = carrier;
+
+ beginInsertRows(QModelIndex(), m_propertyCarriers.size(), m_propertyCarriers.size());
+ m_propertyCarriers.push_back(std::move(carrierStruct));
+ endInsertRows();
+
+ carrierRow = m_propertyCarriers.size() - 1;
+ iviProperties = &m_propertyCarriers.back().iviProperties;
+ } else {
+ carrierRow = indexOfPropertyCarrier(carrier);
+ iviProperties = &m_propertyCarriers[carrierRow].iviProperties;
+ Q_ASSERT(carrierRow != -1);
}
+
+ QtIviPropertyModel::IviProperty iviProperty;
+ if (addIviProperty(carrier, property, &iviProperty)) {
+ beginInsertRows(createIndex(carrierRow, 0, -1),
+ iviProperties->size(), iviProperties->size());
+ iviProperties->push_back(std::move(iviProperty));
+ endInsertRows();
+ } else {
+ std::cout << "QtIviPropertyModel::objectAdded() - add property FAILED " << property << std::endl;
+ }
+
+ connect(property, &QIviProperty::valueChanged, this, &QtIviPropertyModel::propertyValueChanged);
}
- return QAbstractTableModel::headerData(section, orientation, role);
+ m_seenObjects.insert(obj, false);
}
-QVariant QtIVIPropertyModel::data(const QModelIndex &index, int role) const
+void QtIviPropertyModel::objectRemoved(QObject *obj)
{
- const int column = index.column();
+ // slot, hence should always land in main thread due to auto connection
+ Q_ASSERT(thread() == QThread::currentThread());
- const QModelIndex sourceIndex = m_sourceModel->index(index.row(), 0);
- auto const property = qobject_cast<QIviProperty*>(sourceIndex.data(ObjectModel::ObjectRole).value<QObject *>());
+ QHash<QObject *, bool>::Iterator objIt = m_seenObjects.find(obj);
+ if (objIt == m_seenObjects.end()) {
+ IF_DEBUG(std::cout << "QtIviPropertyModel::objectRemoved(): we don't know this. "
+ << obj << std::endl);
+ return;
+ }
+ if (!objIt.value()) {
+ // HACK: assuming that the parent will be removed before anyone looks at the data and therefore
+ // the current object; it might be better to go to the other extreme and remove the parent and
+ // all siblings instead.
+ // Removing object by object is also possible if we really have to. But currently all QIviProperties
+ // seem to be children of the objects that have them as properties, so they are most likely
+ // destroyed together. That parent-child connection is also assumed in the rest of this class.
- if (role == Qt::DisplayRole) {
- switch (column) {
- case AddressColumn:
- return Util::addressToString(property);
- case BackendValueColumn:
- return property->value();
- case OverrideValueColumn:
- return QVariant();
- case OverrideColumn:
- return QVariant();
- default:
- return QVariant();
+ // An important part of this hack is not tipping off the model's clients about *any* changes
+ // while properties are being removed. Only remove their parent and then notify about it.
+ IF_DEBUG(std::cout << "QtIviPropertyModel::objectRemoved(): not a QIviProperty carrier. "
+ << obj << std::endl);
+ m_seenObjects.erase(objIt);
+ return;
+ }
+
+ int carrierIndex = -1;
+ for (uint i = 0; i < m_propertyCarriers.size(); i++) {
+ if (m_propertyCarriers.at(i).carrier == obj) {
+ carrierIndex = i;
+ break;
}
}
+ Q_ASSERT(carrierIndex != -1); // inconsistent with m_seenObjects
- return QVariant();
+ IF_DEBUG(std::cout << "QtIviPropertyModel::objectRemoved(): removing a QIviProperty carrier. "
+ << obj << std::endl);
+
+ // ### Do we need to notify about removing the children? It should be obvious that they're
+ // removed with the parent...
+ beginRemoveRows(QModelIndex(), carrierIndex, carrierIndex);
+ m_seenObjects.erase(objIt);
+ m_propertyCarriers.erase(m_propertyCarriers.begin() + carrierIndex);
+ endRemoveRows();
}
-void QtIVIPropertyModel::setSourceModel(QAbstractItemModel *sourceModel)
+void QtIviPropertyModel::objectReparented(QObject *obj)
{
- beginResetModel();
- m_sourceModel = sourceModel;
-
- connect(m_sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
- this, SLOT(slotBeginInsertRows(QModelIndex,int,int)));
- connect(m_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
- this, SLOT(slotEndInsertRows()));
- connect(m_sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
- this, SLOT(slotBeginRemoveRows(QModelIndex,int,int)));
- connect(m_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
- this, SLOT(slotEndRemoveRows()));
- connect(m_sourceModel, SIGNAL(modelAboutToBeReset()),
- this, SLOT(slotBeginReset()));
- connect(m_sourceModel, SIGNAL(modelReset()),
- this, SLOT(slotEndReset()));
- connect(m_sourceModel, SIGNAL(layoutAboutToBeChanged()),
- this, SLOT(slotBeginReset()));
- connect(m_sourceModel, SIGNAL(layoutChanged()),
- this, SLOT(slotEndReset()));
+ // slot, hence should always land in main thread due to auto connection
+ Q_ASSERT(thread() == QThread::currentThread());
+ if (m_seenObjects.contains(obj)) {
+ IF_DEBUG(std::cout << "QtIviPropertyModel::objectReparented() " << obj << std::endl);
+ // TODO do we need to handle this? If we may assume that the parent-child
+ // connection between property carriers and properties always exists, then probably not.
+ }
+}
- endResetModel();
+QVariant QtIviPropertyModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+ switch (section) {
+ case NameColumn:
+ return tr("Name");
+ case ValueColumn:
+ return tr("Value");
+ case OverrideColumn:
+ return tr("Override");
+ case TypeColumn:
+ return tr("Type");
+ }
+ }
+ return QAbstractItemModel::headerData(section, orientation, role);
}
-void QtIVIPropertyModel::slotBeginRemoveRows(const QModelIndex &parent, int start, int end)
+Qt::ItemFlags QtIviPropertyModel::flags(const QModelIndex &index) const
{
- Q_UNUSED(parent);
- beginRemoveRows(QModelIndex(), start, end);
- qDebug() << "remove row: " << start << " to " << end;
+ Qt::ItemFlags flags = QAbstractItemModel::flags(index);
+ if (index.isValid() && index.internalId() >= 0) {
+ if (index.column() == ValueColumn) {
+ // TODO: check if the *data type* is editable? Maybe belongs more into the delegate...
+ flags |= Qt::ItemIsEditable;
+ } else if (index.column() == OverrideColumn) {
+ flags |= Qt::ItemIsUserCheckable | Qt::ItemIsEditable;
+ }
+ }
+ return flags;
}
-void QtIVIPropertyModel::slotEndRemoveRows()
+static QString buildCarrierObjectName(const QObject *carrier)
{
- endRemoveRows();
+ QString name = carrier->objectName();
+ if (name.isEmpty()) {
+ if (const QIviAbstractZonedFeature *zoned = qobject_cast<const QIviAbstractZonedFeature *>(carrier)) {
+ name = QLatin1String("Zone: ") + // not translated; the zone API is fixed to English, too
+ zoned->zone();
+ }
+ }
+ if (name.isEmpty()) {
+ name = QString::fromLatin1(carrier->metaObject()->className()) +
+ QLatin1String(" 0x") + QString::number(quintptr(carrier), 16);
+ }
+ return name;
}
-void QtIVIPropertyModel::slotBeginInsertRows(const QModelIndex &parent, int start, int end)
+QVariant QtIviPropertyModel::data(const QModelIndex &index, int role) const
{
- Q_UNUSED(parent);
- beginInsertRows(QModelIndex(), start, end);
- m_pending_insertions.push_back(qMakePair(start, end));
+ if (!index.isValid()) {
+ return QVariant();
+ }
+ const int parentRow = static_cast<int>(index.internalId());
+ if (parentRow == -1 && role == Qt::DisplayRole) {
+ // property carrier
+ if (index.row() >= 0 && uint(index.row()) < m_propertyCarriers.size()) {
+ const IviPropertyCarrier &propCarrier = m_propertyCarriers.at(index.row());
+ // The columns are a bit awkward here. They are assigned that way for compatibility
+ // with the header data meant for the properties.
+ switch (index.column()) {
+ case NameColumn:
+ return buildCarrierObjectName(propCarrier.carrier);
+ case TypeColumn:
+ return QString::fromLatin1(propCarrier.carrier->metaObject()->className());
+ default:
+ break;
+ }
+ }
+ } else {
+ // property
+ if (parentRow >= 0 && uint(parentRow) < m_propertyCarriers.size()) {
+ const IviPropertyCarrier &propCarrier = m_propertyCarriers.at(parentRow);
+ if (index.row() >= 0 && uint(index.row()) < propCarrier.iviProperties.size()) {
+ const IviProperty &iviProperty = propCarrier.iviProperties.at(index.row());
+ switch (index.column()) {
+ case NameColumn:
+ if (role == Qt::DisplayRole) {
+ return iviProperty.name;
+ }
+ break;
+ case ValueColumn: {
+ if (role == Qt::DisplayRole) {
+ const QVariant value = iviProperty.value->value();
+ const QMetaObject *const mo = QMetaType::metaObjectForType(value.userType());
+ const QString enumStr = EnumUtil::enumToString(value, nullptr, mo);
+ if (!enumStr.isEmpty())
+ return enumStr;
+ return VariantHandler::displayString(value);
+ return value;
+ } else if (role == Qt::EditRole) {
+ const QVariant value = iviProperty.value->value();
+ const QMetaObject *const mo = QMetaType::metaObjectForType(value.userType());
+ const QMetaEnum me = EnumUtil::metaEnum(value, nullptr, mo);
+ if (me.isValid()) {
+ const int num = EnumUtil::enumToInt(value, me);
+ return QVariant::fromValue(EnumRepositoryServer::valueFromMetaEnum(num, me));
+ }
+ return VariantHandler::serializableVariant(value);
+ }
+ break;
+ }
+ case OverrideColumn:
+ if (role == Qt::CheckStateRole) {
+ return iviProperty.overrider.overrideEnabled() ? Qt::Checked : Qt::Unchecked;
+ }
+ break;
+ case TypeColumn:
+ if (role == Qt::DisplayRole) {
+ QVariant value = iviProperty.value->value();
+ const int metatype = value.userType();
+ return QString::fromLatin1(QMetaType::typeName(metatype));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ return QVariant();
}
-void QtIVIPropertyModel::slotEndInsertRows()
+bool QtIviPropertyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
- for (auto& range : m_pending_insertions)
- {
- for (int i = range.first; i <= range.second; ++i)
- {
- const QModelIndex sourceIndex = m_sourceModel->index(i, 0);
- auto const property = qobject_cast<QIviProperty*>(sourceIndex.data(ObjectModel::ObjectRole).value<QObject *>());
+ const int parentRow = static_cast<int>(index.internalId());
+ if (!index.isValid() || (index.column() != ValueColumn && index.column() != OverrideColumn)
+ || index.internalId() < 0) {
+ return false;
+ }
+ if (parentRow >= 0 && uint(parentRow) < m_propertyCarriers.size()) {
+ IviPropertyCarrier *propCarrier = &m_propertyCarriers[parentRow];
+ if (index.row() >= 0 && uint(index.row()) < propCarrier->iviProperties.size()) {
+ IviProperty *iviProperty = &propCarrier->iviProperties[index.row()];
+ if (index.column() == ValueColumn) {
+ if (role == Qt::DisplayRole || role == Qt::EditRole) {
+ const bool wasOverrideEnabled = iviProperty->overrider.overrideEnabled();
+ if (!wasOverrideEnabled) {
+ // Don't receive valueChanged signals we caused ourselves. We emit the
+ // dataChanged() signal manually instead, which has less potential for
+ // "interesting" side effects.
+ disconnect(iviProperty->value, &QIviProperty::valueChanged,
+ this, &QtIviPropertyModel::propertyValueChanged);
+ }
- connect(property, &QIviProperty::valueChanged, [this, property](const QVariant& value)
- {
- for (auto i = 0; i < rowCount(); ++i)
- {
- const QModelIndex sourceIndex = m_sourceModel->index(i, 0);
- auto const model_property = qobject_cast<QIviProperty*>(sourceIndex.data(ObjectModel::ObjectRole).value<QObject *>());
+ if (value.userType() == qMetaTypeId<EnumValue>()) {
+ QVariant typeReference = iviProperty->value->value();
+ if (typeReference.type() == QVariant::Int) {
+ iviProperty->overrider.setOverrideValue(value.value<EnumValue>().value());
+ } else {
+ *(static_cast<int*>(typeReference.data())) = value.value<EnumValue>().value();
+ iviProperty->overrider.setOverrideValue(typeReference);
+ }
+ } else {
+ iviProperty->overrider.setOverrideValue(value);
+ }
- if (property == model_property)
- {
- emit dataChanged(createIndex(i, BackendValueColumn), createIndex(i, BackendValueColumn));
- break;
+ emit iviProperty->value->valueChanged(value);
+ if (wasOverrideEnabled) {
+ emit dataChanged(index, index);
+ } else {
+ emit dataChanged(index, index.sibling(index.row(), OverrideColumn));
}
+ return true;
}
- });
+ } else if (index.column() == OverrideColumn) {
+ if (role == Qt::CheckStateRole) {
+ const bool wasOverrideEnabled = iviProperty->overrider.overrideEnabled();
+ const bool overrideEnabled = value.toBool();
+ if (overrideEnabled != wasOverrideEnabled) {
+ if (overrideEnabled) {
+ disconnect(iviProperty->value, &QIviProperty::valueChanged,
+ this, &QtIviPropertyModel::propertyValueChanged);
+ iviProperty->overrider.setOverrideValue(iviProperty->value->value());
+ emit iviProperty->value->valueChanged(value);
+ } else {
+ iviProperty->overrider.disableOverride();
+ emit iviProperty->value->valueChanged(value);
+ connect(iviProperty->value, &QIviProperty::valueChanged,
+ this, &QtIviPropertyModel::propertyValueChanged);
+ }
+ emit dataChanged(index.sibling(index.row(), ValueColumn), index);
+ return true;
+ }
+ }
+ }
}
}
+ return false;
+}
- m_pending_insertions.clear();
- endInsertRows();
+void QtIviPropertyModel::propertyValueChanged(const QVariant &value)
+{
+ QIviProperty *property = qobject_cast<QIviProperty *>(sender());
+ if (!property) {
+ return;
+ }
+ const int carrierIndex = indexOfPropertyCarrier(property->parent());
+ if (carrierIndex < 0) {
+ return;
+ }
+ const IviPropertyCarrier &carrier = m_propertyCarriers.at(carrierIndex);
+ const int propertyIndex = carrier.indexOfProperty(property);
+ if (propertyIndex < 0) {
+ return;
+ }
+ const QModelIndex index = createIndex(propertyIndex, ValueColumn, carrierIndex);
+ emit dataChanged(index, index);
+}
+
+int QtIviPropertyModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.column() >= 1) {
+ return 0;
+ }
+
+ if (parent.isValid()) {
+ const int grandparentRow = static_cast<int>(parent.internalId());
+ if (grandparentRow == -1 /* only property carriers have another level of children */) {
+ const IviPropertyCarrier &propCarrier = m_propertyCarriers.at(parent.row());
+ return propCarrier.iviProperties.size();
+ }
+ return 0;
+ } else {
+ return m_propertyCarriers.size();
+ }
}
-void QtIVIPropertyModel::slotBeginReset()
+int QtIviPropertyModel::columnCount(const QModelIndex &) const
{
- beginResetModel();
+ return ColumnCount;
}
-void QtIVIPropertyModel::slotEndReset()
+QModelIndex QtIviPropertyModel::parent(const QModelIndex &child) const
{
- endResetModel();
+ if (child.isValid()) {
+ const int parentRow = static_cast<int>(child.internalId());
+ if (parentRow != -1 /* -1 is already toplevel */) {
+ return createIndex(parentRow, 0, -1);
+ }
+ }
+ return QModelIndex();
+}
+
+/*
+ Usage of QModelIndex::internalId() aka internalPointer():
+ - toplevel (property carrier): (int) -1
+ - second level (property): (int) index of property carrier (parent); >= 0
+ */
+QModelIndex QtIviPropertyModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (column >= 0 && column < ColumnCount) {
+ if (parent.isValid()) {
+ // create an index to a property
+ const int grandparentRow = static_cast<int>(parent.internalId());
+ if (grandparentRow == -1 && /* only property carriers have another level of children */
+ parent.row() >= 0 && uint(parent.row()) < m_propertyCarriers.size()) {
+ const IviPropertyCarrier &propCarrier = m_propertyCarriers.at(parent.row());
+ if (row >= 0 && uint(row) < propCarrier.iviProperties.size()) {
+ return createIndex(row, column, parent.row());
+ }
+ }
+ } else {
+ // create an index to a property carrier
+ if (row >= 0 && uint(row) < m_propertyCarriers.size()) {
+ return createIndex(row, column, -1);
+ }
+ }
+ }
+ return QModelIndex();
}
diff --git a/src/qtivipropertymodel.h b/src/qtivipropertymodel.h
index 4efbbc2..3d0b22d 100644
--- a/src/qtivipropertymodel.h
+++ b/src/qtivipropertymodel.h
@@ -29,44 +29,73 @@
#ifndef GAMMARAY_QTIVI_QTIVIPROPERTYMODEL_H
#define GAMMARAY_QTIVI_QTIVIPROPERTYMODEL_H
-#include <QAbstractTableModel>
-#include <QVector>
+#include "qtivipropertyoverrider.h"
-class QtIVIPropertyModel : public QAbstractTableModel
-{
- Q_OBJECT
+#include <QAbstractItemModel>
+#include <QHash>
+#include <QString>
-public:
- enum Column {
- AddressColumn,
- BackendValueColumn,
- OverrideValueColumn,
- OverrideColumn,
- /** Mark column count */
- ColumnCount
- };
+#include <vector>
- explicit QtIVIPropertyModel(QObject *parent = 0);
- ~QtIVIPropertyModel();
+class QIviProperty;
- int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
- int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE;
- QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
- QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+namespace GammaRay {
- void setSourceModel(QAbstractItemModel *sourceModel);
+class Probe;
+
+class QtIviPropertyModel : public QAbstractItemModel
+{
+ Q_OBJECT
+public:
+ explicit QtIviPropertyModel(Probe *probe);
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ bool setData(const QModelIndex &index, const QVariant &value,
+ int role = Qt::EditRole) Q_DECL_OVERRIDE;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ QModelIndex parent(const QModelIndex &child) const Q_DECL_OVERRIDE;
+ QModelIndex index(int row, int column,
+ const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
private slots:
- void slotBeginRemoveRows(const QModelIndex &parent, int start, int end);
- void slotEndRemoveRows();
- void slotBeginInsertRows(const QModelIndex &parent, int start, int end);
- void slotEndInsertRows();
- void slotBeginReset();
- void slotEndReset();
+ void objectAdded(QObject *obj);
+ void objectRemoved(QObject *obj);
+ void objectReparented(QObject *obj);
+ void propertyValueChanged(const QVariant &value);
+
+public:
+ struct IviProperty // public so we can have a free function in the .cpp file dealing with it
+ {
+ explicit IviProperty(QIviProperty *value, const QMetaProperty &metaProperty);
+ IviProperty();
+ QString name;
+ QIviProperty *value;
+ QtIviPropertyOverrider overrider;
+ };
private:
- QVector<QPair<int, int>> m_pending_insertions;
- QAbstractItemModel *m_sourceModel;
+ int indexOfPropertyCarrier(const QObject *carrier) const;
+
+ struct IviPropertyCarrier
+ {
+ QObject *carrier;
+ std::vector<IviProperty> iviProperties;
+ int indexOfProperty(const QIviProperty *property) const;
+ };
+
+ /// property tree model
+ // "property carriers" are objects that have QIviProperty (static Qt meta) properties
+
+ std::vector<IviPropertyCarrier> m_propertyCarriers;
+ // m_seenObjects is not strictly needed currently but it helps debugging and will become more useful
+ // when some of the current simplifying assumptions about object relationships must be discarded.
+ QHash<QObject *, bool> m_seenObjects; // bool meaning: whether it has properties of type QtIVIProperty *
};
+}
+
#endif
diff --git a/src/qtivipropertyoverrider.cpp b/src/qtivipropertyoverrider.cpp
new file mode 100644
index 0000000..c9f70d4
--- /dev/null
+++ b/src/qtivipropertyoverrider.cpp
@@ -0,0 +1,199 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Klaralvdalens Datakonsult AB, a KDAB Group company.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtIvi plug-in for GammaRay.
+**
+** $QT_BEGIN_LICENSE:GPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtivipropertyoverrider.h"
+
+#include <QObject>
+
+#include <QIviProperty>
+#include <1.0.0/QtIviCore/private/qiviproperty_p.h>
+
+/*
+ For the purpose of overriding, there is a big difference between read-only and read-write properties.
+ Read-write properties are effectively settings for lower layers from higher layers, with reading them
+ back as a convenience function.
+ Read-only properties are information about lower layers for higher layers.
+ So the information flow is exactly opposite for the two kinds of properties. Thus, overriding should
+ work as follows for them:
+ - Read-write properties: Overriding allows the overrider to set the value, with the same effects as
+ setting the value from the UI. The regular setter will not change the value anymore, but stash away
+ the value for later when override is disabled.
+ - Readonly properties: Overriding allows the overrider to set the value, with the same effects as
+ setting the value from the backend.
+
+ Unfortunately, QIviProperties cannot currently be explicitly readonly - they can only ignore
+ setValue() calls...
+ */
+
+// Note: Those subclasses of QSlotObjectBase are not invoked through the ReadProperty / WriteProperty
+// "opcodes" of the metaobject system, they are just regular methods, for which the calling convention
+// is that a[0] points to the return value, a[1] to the first argument, a[2] to the second etc.
+
+class OverrideValueSetter : public QtPrivate::QSlotObjectBase
+{
+ static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret)
+ {
+ switch (which) {
+ case Destroy:
+ delete static_cast<OverrideValueSetter*>(this_);
+ break;
+ case Call: {
+#if 0 // If we actually wanted to propagate writes coming from the real backend - which would
+ // undo the override... It seems better to ignore writes from the real backend.
+ QtIviPropertyOverrider *const overrider = static_cast<OverrideValueSetter*>(this_)->m_overrider;
+ const QMetaType::Type mt = static_cast<QMetaType::Type>(overrider->m_overrideValue.userType());
+ void *const firstArgument = a[1];
+ overrider->m_overrideValue = QVariant(mt, firstArgument);
+#endif
+ break;
+ }
+ case Compare:
+ *ret = false; // not implemented
+ break;
+ case NumOperations: ;
+ }
+ }
+public:
+ QtIviPropertyOverrider *m_overrider;
+ explicit OverrideValueSetter(QtIviPropertyOverrider *overrider)
+ : QSlotObjectBase(&impl), m_overrider(overrider) {}
+};
+
+class OverrideValueGetter : public QtPrivate::QSlotObjectBase
+{
+ static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret)
+ {
+ Q_UNUSED(r)
+ switch (which) {
+ case Destroy:
+ delete static_cast<OverrideValueGetter*>(this_);
+ break;
+ case Call: {
+ QtIviPropertyOverrider *const overrider = static_cast<OverrideValueGetter*>(this_)->m_overrider;
+ const QMetaType::Type mt = static_cast<QMetaType::Type>(overrider->m_overrideValue.userType());
+ void *const returnValue = a[0];
+ // There is no QMetaType::assign(), but we can still avoid re-allocating the storage.
+ // No reallocation is a hard requirement in case the instance is not heap-allocated.
+ QMetaType::destruct(mt, returnValue);
+ QMetaType::construct(mt, returnValue, overrider->m_overrideValue.constData());
+ break;
+ }
+ case Compare:
+ *ret = false; // not implemented
+ break;
+ case NumOperations: ;
+ }
+ }
+public:
+ QtIviPropertyOverrider *m_overrider;
+ explicit OverrideValueGetter(QtIviPropertyOverrider *overrider)
+ : QSlotObjectBase(&impl), m_overrider(overrider) {}
+};
+
+QtIviPropertyOverrider::QtIviPropertyOverrider(QIviProperty *overriddenProperty, bool userWritable)
+ : m_prop(overriddenProperty),
+ m_overrideEnabled(false),
+ m_userWritable(userWritable)
+{
+ // access to m_overrideValue[G|S]etter is guarded by m_overrideEnabled so they don't need to be
+ // initialized
+}
+
+QtIviPropertyOverrider::QtIviPropertyOverrider()
+ : m_prop(nullptr),
+ m_overrideEnabled(false),
+ m_userWritable(false)
+{
+}
+
+QtIviPropertyOverrider::QtIviPropertyOverrider(QtIviPropertyOverrider &&other)
+{
+ *this = std::move(other);
+}
+
+QtIviPropertyOverrider &QtIviPropertyOverrider::operator=(QtIviPropertyOverrider &&other)
+{
+ m_prop = other.m_prop;
+ m_overrideValue = other.m_overrideValue;
+ m_overrideEnabled = other.m_overrideEnabled;
+ m_originalValueSetter = other.m_originalValueSetter;
+ m_originalValueGetter = other.m_originalValueGetter;
+
+ if (m_overrideEnabled) {
+ // fix the backlinks
+ QIviPropertyPrivate *pPriv = QIviPropertyPrivate::get(m_prop);
+ static_cast<OverrideValueSetter *>(pPriv->m_valueSetter)->m_overrider = this;
+ static_cast<OverrideValueGetter *>(pPriv->m_valueGetter)->m_overrider = this;
+
+ // this is enough to make destroying the other one harmless
+ other.m_overrideEnabled = false;
+ }
+ return *this;
+}
+
+QtIviPropertyOverrider::~QtIviPropertyOverrider()
+{
+ disableOverride();
+}
+
+void QtIviPropertyOverrider::setOverrideValue(const QVariant &value)
+{
+ if (!m_overrideEnabled) {
+ m_overrideEnabled = true;
+ QIviPropertyPrivate *pPriv = QIviPropertyPrivate::get(m_prop);
+ m_originalValueGetter = pPriv->m_valueGetter;
+ m_originalValueSetter = pPriv->m_valueSetter;
+
+ pPriv->m_valueGetter = new OverrideValueGetter(this);
+ pPriv->m_valueSetter = new OverrideValueSetter(this);
+ }
+ m_overrideValue = value;
+}
+
+void QtIviPropertyOverrider::disableOverride()
+{
+ if (m_overrideEnabled) {
+ m_overrideEnabled = false;
+ QIviPropertyPrivate *pPriv = QIviPropertyPrivate::get(m_prop);
+ // ### should we really call destroyIfLastRef()?
+ pPriv->m_valueGetter->destroyIfLastRef();
+ pPriv->m_valueSetter->destroyIfLastRef();
+ pPriv->m_valueGetter = m_originalValueGetter;
+ pPriv->m_valueSetter = m_originalValueSetter;
+ }
+}
+
+bool QtIviPropertyOverrider::overrideEnabled() const
+{
+ return m_overrideEnabled;
+}
+
+QVariant QtIviPropertyOverrider::value() const
+{
+ return m_prop->value();
+}
diff --git a/src/qtivipropertyoverrider.h b/src/qtivipropertyoverrider.h
new file mode 100644
index 0000000..249ed6f
--- /dev/null
+++ b/src/qtivipropertyoverrider.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Klaralvdalens Datakonsult AB, a KDAB Group company.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtIvi plug-in for GammaRay.
+**
+** $QT_BEGIN_LICENSE:GPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef GAMMARAY_QTIVIPROPERTYOVERRIDER_H
+#define GAMMARAY_QTIVIPROPERTYOVERRIDER_H
+
+#include <QObject>
+#include <QVariant>
+
+class QIviProperty;
+class OverrideValueGetter;
+class OverrideValueSetter;
+
+class QtIviPropertyOverrider
+{
+public:
+ QtIviPropertyOverrider(QIviProperty *property, bool userWritable = false);
+ QtIviPropertyOverrider();
+ QtIviPropertyOverrider(const QtIviPropertyOverrider &other) = delete;
+ QtIviPropertyOverrider &operator=(const QtIviPropertyOverrider &other) = delete;
+ QtIviPropertyOverrider(QtIviPropertyOverrider &&other);
+ QtIviPropertyOverrider &operator=(QtIviPropertyOverrider &&other);
+ ~QtIviPropertyOverrider();
+
+ void setOverrideValue(const QVariant &value);
+ void disableOverride();
+ bool overrideEnabled() const;
+
+ QVariant value() const;
+
+private:
+ friend class OverrideValueSetter;
+ friend class OverrideValueGetter;
+
+ QIviProperty *m_prop;
+ QVariant m_overrideValue;
+ bool m_overrideEnabled : 1;
+ bool m_userWritable : 1;
+ QtPrivate::QSlotObjectBase *m_originalValueSetter;
+ QtPrivate::QSlotObjectBase *m_originalValueGetter;
+};
+
+#endif // GAMMARAY_QTIVIPROPERTYOVERRIDER_H
diff --git a/src/qtivisupport.cpp b/src/qtivisupport.cpp
index ae2b233..293cbcb 100644
--- a/src/qtivisupport.cpp
+++ b/src/qtivisupport.cpp
@@ -32,6 +32,7 @@
#include <core/metaobject.h>
#include <core/metaobjectrepository.h>
#include <core/objecttypefilterproxymodel.h>
+#include <core/probe.h>
#include <QIviAbstractFeature>
#include <QIviServiceObject>
@@ -41,34 +42,8 @@
#include <QDebug>
using namespace GammaRay;
-using PropertyFilterModel = ObjectTypeFilterProxyModel<QIviProperty>;
-QtIviSupport::QtIviSupport(ProbeInterface* probe, QObject* parent) :
- QObject(parent)
-{
- Q_UNUSED(probe);
- registerMetaTypes();
-
- auto const filterModel = new PropertyFilterModel(this);
- filterModel->setDynamicSortFilter(true);
- filterModel->setSourceModel(probe->objectListModel());
-
- auto propertyModel = new QtIVIPropertyModel(this);
- propertyModel->setSourceModel(filterModel);
-
- connect(propertyModel, &QtIVIPropertyModel::dataChanged, [propertyModel](const QModelIndex& tl, const QModelIndex& br)
- {
- qDebug() << "index tl: " << tl;
- qDebug() << "index br: " << br;
- qDebug() << "got data changed event";
- qDebug() << propertyModel->data(tl);
- });
-
- probe->registerModel(QStringLiteral("com.kdab.GammaRay.PropertyModel"), propertyModel);
- //m_selectionModel = ObjectBroker::selectionModel(filterModel);
-}
-
-void QtIviSupport::registerMetaTypes()
+static void registerMetaTypes()
{
qRegisterMetaType<QIviServiceObject*>();
@@ -84,3 +59,23 @@ void QtIviSupport::registerMetaTypes()
MO_ADD_METAOBJECT1(QIviZonedFeatureInterface, QObject);
MO_ADD_PROPERTY_RO(QIviZonedFeatureInterface, QStringList, availableZones);
}
+
+QtIviSupport::QtIviSupport(ProbeInterface* probe, QObject* parent)
+ : QObject(parent)
+{
+ Q_UNUSED(probe);
+ registerMetaTypes();
+
+ auto propertyModel = new QtIviPropertyModel(Probe::instance());
+
+ connect(propertyModel, &QtIviPropertyModel::dataChanged,
+ [propertyModel](const QModelIndex& tl, const QModelIndex& br)
+ {
+ qDebug() << "data changed from top left:" << tl << "to bottom right:" << br
+ << "with new data at top left" << propertyModel->data(tl);
+ }
+ );
+
+ probe->registerModel(QStringLiteral("com.kdab.GammaRay.PropertyModel"), propertyModel);
+ //m_selectionModel = ObjectBroker::selectionModel(filterModel);
+}
diff --git a/src/qtivisupport.h b/src/qtivisupport.h
index 4e48b11..8901046 100644
--- a/src/qtivisupport.h
+++ b/src/qtivisupport.h
@@ -40,9 +40,6 @@ class QtIviSupport : public QObject
Q_OBJECT
public:
explicit QtIviSupport(ProbeInterface *probe, QObject *parent = 0);
-
-private:
- void registerMetaTypes();
};
class QtIviSupportFactory : public QObject, public StandardToolFactory<QIviProperty, QtIviSupport>
diff --git a/src/qtivisupportwidget.cpp b/src/qtivisupportwidget.cpp
index d537a43..6a395b7 100644
--- a/src/qtivisupportwidget.cpp
+++ b/src/qtivisupportwidget.cpp
@@ -31,6 +31,7 @@
#include <common/objectbroker.h>
#include <common/endpoint.h>
+#include <ui/propertyeditordelegate.h>
#include <QTreeView>
#include <QHBoxLayout>
@@ -53,5 +54,7 @@ QtIVIWidget::QtIVIWidget(QWidget *parent)
vbox->addWidget(objectTreeView);
QItemSelectionModel *selectionModel = ObjectBroker::selectionModel(propertyModel);
+ Q_UNUSED(selectionModel); // it *is* used just by having a QAIM as parent
objectTreeView->setModel(propertyModel);
+ objectTreeView->setItemDelegateForColumn(1, new PropertyEditorDelegate(this));
}