From 7dc71617eb3a762a7769a8442348af59e324895b Mon Sep 17 00:00:00 2001 From: Andreas Hartmetz Date: Wed, 5 Oct 2016 22:57:29 +0200 Subject: 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 --- CMakeLists.txt | 4 +- src/CMakeLists.txt | 3 +- src/qtivipropertymodel.cpp | 521 +++++++++++++++++++++++++++++++++-------- src/qtivipropertymodel.h | 87 ++++--- src/qtivipropertyoverrider.cpp | 199 ++++++++++++++++ src/qtivipropertyoverrider.h | 68 ++++++ src/qtivisupport.cpp | 49 ++-- src/qtivisupport.h | 3 - src/qtivisupportwidget.cpp | 3 + 9 files changed, 779 insertions(+), 158 deletions(-) create mode 100644 src/qtivipropertyoverrider.cpp create mode 100644 src/qtivipropertyoverrider.h 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 +#include #include #include +#include #include +#include #include #include +#include +#include #include #include +#include + +//#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(); // 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(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(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 *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(sourceIndex.data(ObjectModel::ObjectRole).value()); + QHash::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(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(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(sourceIndex.data(ObjectModel::ObjectRole).value()); + const int parentRow = static_cast(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(sourceIndex.data(ObjectModel::ObjectRole).value()); + if (value.userType() == qMetaTypeId()) { + QVariant typeReference = iviProperty->value->value(); + if (typeReference.type() == QVariant::Int) { + iviProperty->overrider.setOverrideValue(value.value().value()); + } else { + *(static_cast(typeReference.data())) = value.value().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(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(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(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(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 -#include +#include "qtivipropertyoverrider.h" -class QtIVIPropertyModel : public QAbstractTableModel -{ - Q_OBJECT +#include +#include +#include -public: - enum Column { - AddressColumn, - BackendValueColumn, - OverrideValueColumn, - OverrideColumn, - /** Mark column count */ - ColumnCount - }; +#include - 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> m_pending_insertions; - QAbstractItemModel *m_sourceModel; + int indexOfPropertyCarrier(const QObject *carrier) const; + + struct IviPropertyCarrier + { + QObject *carrier; + std::vector iviProperties; + int indexOfProperty(const QIviProperty *property) const; + }; + + /// property tree model + // "property carriers" are objects that have QIviProperty (static Qt meta) properties + + std::vector 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 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 + +#include +#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(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(this_)->m_overrider; + const QMetaType::Type mt = static_cast(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(this_); + break; + case Call: { + QtIviPropertyOverrider *const overrider = static_cast(this_)->m_overrider; + const QMetaType::Type mt = static_cast(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(pPriv->m_valueSetter)->m_overrider = this; + static_cast(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 +#include + +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 #include #include +#include #include #include @@ -41,34 +42,8 @@ #include using namespace GammaRay; -using PropertyFilterModel = ObjectTypeFilterProxyModel; -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(); @@ -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 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 #include +#include #include #include @@ -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)); } -- cgit v1.2.3