From 316d52bfe5c2667cf67edf08bd1830cfe9a9444a Mon Sep 17 00:00:00 2001 From: Wang Zichong Date: Tue, 7 Jul 2026 20:31:55 +0800 Subject: [PATCH] tools for qml item properties --- bridge/src/main.cpp | 5 ++ bridge/src/scenegraph_tools.cpp | 155 +++++++++++++++++++++++++++++++- bridge/src/scenegraph_tools.h | 20 +++-- 3 files changed, 174 insertions(+), 6 deletions(-) diff --git a/bridge/src/main.cpp b/bridge/src/main.cpp index fda9a42..2f4ac8e 100644 --- a/bridge/src/main.cpp +++ b/bridge/src/main.cpp @@ -93,6 +93,11 @@ int main(int argc, char **argv) // SG node selection { QStringLiteral("selectScenegraphNode"), QStringLiteral("Select a SG node by address (from listScenegraphNodes) so geometry/material sub-models populate for it") }, { QStringLiteral("selectScenegraphNode/address"), QStringLiteral("Node address (e.g. 0x56396e6d7400, from listScenegraphNodes)") }, + // QML item selection + properties + { QStringLiteral("selectQuickItem"), QStringLiteral("Select a QML item by address (from listQuickItems) so item properties populate for it") }, + { QStringLiteral("selectQuickItem/address"), QStringLiteral("Item address (e.g. 0x55e451db7200, from listQuickItems)") }, + { QStringLiteral("getItemProperties"), QStringLiteral("Get all Q_PROPERTY values (x, y, width, height, opacity, visible, z, anchors, etc.) for a QML item") }, + { QStringLiteral("getItemProperties/address"), QStringLiteral("Item address (from listQuickItems)") }, // Geometry { QStringLiteral("getNodeVertices"), QStringLiteral("Get vertex data for a GeometryNode (vertices, attributes, isCoordinate flags)") }, { QStringLiteral("getNodeVertices/address"), QStringLiteral("Node address (must be a GeometryNode)") }, diff --git a/bridge/src/scenegraph_tools.cpp b/bridge/src/scenegraph_tools.cpp index 18a6766..efaa620 100644 --- a/bridge/src/scenegraph_tools.cpp +++ b/bridge/src/scenegraph_tools.cpp @@ -31,6 +31,7 @@ static const char *kQuickWindowModel = "com.kdab.GammaRay.QuickWindowModel"; static const char *kQuickItemModel = "com.kdab.GammaRay.QuickItemModel"; static const char *kQuickSceneGraphModel = "com.kdab.GammaRay.QuickSceneGraphModel"; static const char *kBaseName = "com.kdab.GammaRay.QuickSceneGraph"; +static const char *kItemBaseName = "com.kdab.GammaRay.QuickItem"; static const char *kMaterialIface = "com.kdab.GammaRay.MaterialExtensionInterface"; static const char *kQuickInspectorIface = "com.kdab.GammaRay.QuickInspectorInterface/1.0"; @@ -732,7 +733,159 @@ QString SceneGraphTools::getMaterialProperties(const QString &address) const return QString::fromUtf8(QJsonDocument(out).toJson(QJsonDocument::Compact)); } -// --- Rendering visualization --- +// --- QML Item selection + properties --- + +QString SceneGraphTools::ensureItemSelected(const QString &address) const +{ + if (const QString e = ensureSession(); !e.isEmpty()) + return e; + + if (address.isEmpty()) + return QString::fromUtf8(QJsonDocument(errorJson( + QStringLiteral("address is required"))).toJson(QJsonDocument::Compact)); + + // If already selected, nothing to do. + if (address == m_selectedItemAddress) + return {}; + + auto *itemModel = m_session->model(QString::fromUtf8(kQuickItemModel)); + if (!itemModel) + return QString::fromUtf8(QJsonDocument(errorJson( + QStringLiteral("QuickItemModel not available"))).toJson(QJsonDocument::Compact)); + + const QModelIndex idx = findNodeByAddress(itemModel, address); + if (!idx.isValid()) + return QString::fromUtf8(QJsonDocument(errorJson( + QStringLiteral("item %1 not found in QML item tree (still loading?)").arg(address))) + .toJson(QJsonDocument::Compact)); + + // Create the selection model client BEFORE selecting. + auto *selModel = m_session->selectionModel(itemModel); + + // CRITICAL: Create the item properties RemoteModel BEFORE sending the selection, + // so the server is already monitoring when the property model resets/populates. + auto *propModel = m_session->model(QStringLiteral("%1.properties").arg(QString::fromUtf8(kItemBaseName))); + + // Wait for the SelectionModelClient's 125ms default-selection timer to fire + // and settle before our override. + QEventLoop loop; + settle(loop, 300); + + // Select the item via client-side selection model. + if (selModel && idx.isValid()) + selModel->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current); + + // Wait for the selection to be processed and property data to arrive. + settle(loop, 2000); + + m_selectedItemAddress = address; + + // Prime the property model to fetch its row count and cell data. + if (propModel) { propModel->rowCount(); primeAndWait(propModel, 4, 300, 500, 1500); } + + return {}; +} + +QString SceneGraphTools::selectQuickItem(const QString &address) const +{ + m_selectedItemAddress.clear(); // force reselect + const QString e = ensureItemSelected(address); + if (!e.isEmpty()) + return e; + + // Report what we know about the selected item. + auto *itemModel = m_session->model(QString::fromUtf8(kQuickItemModel)); + const QModelIndex idx = findNodeByAddress(itemModel, address); + QString type; + int flags = 0; + int propertyCount = 0; + if (idx.isValid()) { + const QModelIndex typeIdx = itemModel->index(idx.row(), 1, idx.parent()); + type = itemModel->data(typeIdx, Qt::DisplayRole).toString(); + flags = itemModel->data(idx, kItemFlagsRole).toInt(); + } + + auto *propModel = m_session->model(QStringLiteral("%1.properties").arg(QString::fromUtf8(kItemBaseName))); + if (propModel) + propertyCount = propModel->rowCount(); + + QJsonObject out = { + { QStringLiteral("address"), address }, + { QStringLiteral("type"), type }, + { QStringLiteral("flags"), flagsToJson(flags) }, + { QStringLiteral("propertyCount"), propertyCount }, + }; + return QString::fromUtf8(QJsonDocument(out).toJson(QJsonDocument::Compact)); +} + +QString SceneGraphTools::getItemProperties(const QString &address) const +{ + const QString e = ensureItemSelected(address); + if (!e.isEmpty()) + return e; + + auto *m = m_session->model(QStringLiteral("%1.properties").arg(QString::fromUtf8(kItemBaseName))); + if (!m) + return QString::fromUtf8(QJsonDocument(errorJson( + QStringLiteral("item property model not available"))).toJson(QJsonDocument::Compact)); + + // Item property properties model has 4 columns: + // 0 = property name, 1 = value (display string), 2 = type name, 3 = class name + waitForRows(m, 3000); + + const int rows = m->rowCount(); + if (rows == 0) + return QString::fromUtf8(QJsonDocument(errorJson( + QStringLiteral("no properties available (did you select a valid QML item?)"))) + .toJson(QJsonDocument::Compact)); + + const int cols = m->columnCount(); + + QJsonObject out; + QStringList headers; + for (int c = 0; c < cols; ++c) + headers.append(m->headerData(c, Qt::Horizontal, Qt::DisplayRole).toString()); + + // Top-level properties (simple Q_PROPERTY values like x, y, width, height...) + QJsonObject simpleProps; + // Nested property groups (anchors, transform, etc.) will have children. + QJsonObject groups; + for (int r = 0; r < rows; ++r) { + const QString name = m->data(m->index(r, 0), Qt::DisplayRole).toString(); + const QString val = m->data(m->index(r, 1), Qt::DisplayRole).toString(); + const QString ptype = cols > 2 ? m->data(m->index(r, 2), Qt::DisplayRole).toString() : QString(); + + // Check if this property has children (nested sub-properties). + QJsonObject prop; + prop.insert(QStringLiteral("value"), val); + prop.insert(QStringLiteral("type"), ptype); + + if (m->hasChildren(m->index(r, 0))) { + // For grouped properties (anchors, transform, etc.), collect children. + QJsonObject children; + for (int cr = 0; cr < m->rowCount(m->index(r, 0)); ++cr) { + const QString cname = m->data(m->index(cr, 0, m->index(r, 0)), Qt::DisplayRole).toString(); + const QString cval = m->data(m->index(cr, 1, m->index(r, 0)), Qt::DisplayRole).toString(); + const QString ctype = cols > 2 ? m->data(m->index(cr, 2, m->index(r, 0)), Qt::DisplayRole).toString() : QString(); + QJsonObject child; + child.insert(QStringLiteral("value"), cval); + child.insert(QStringLiteral("type"), ctype); + children.insert(cname, child); + } + if (!children.isEmpty()) + prop.insert(QStringLiteral("children"), children); + groups.insert(name, prop); + } else { + simpleProps.insert(name, prop); + } + } + + out.insert(QStringLiteral("properties"), simpleProps); + if (!groups.isEmpty()) + out.insert(QStringLiteral("groups"), groups); + + return QString::fromUtf8(QJsonDocument(out).toJson(QJsonDocument::Compact)); +} QString SceneGraphTools::setRenderMode(const QString &mode) const { diff --git a/bridge/src/scenegraph_tools.h b/bridge/src/scenegraph_tools.h index 9c92eb5..71c20fb 100644 --- a/bridge/src/scenegraph_tools.h +++ b/bridge/src/scenegraph_tools.h @@ -58,12 +58,18 @@ public: // --- SceneGraph navigation --- Q_INVOKABLE QString listScenegraphNodes() const; // Find a node by address (e.g. "0x56396e6d7400") in the SG tree and select it - // via the probe-side Q_INVOKABLE method. Returns JSON with the node's type and - // which sub-models have data. Must be called before geometry/material tools can - // return data for that node. Selecting a non-GeometryNode clears the - // geometry/material models (they only populate for QSGGeometryNode). + // via the selection model client path. Returns JSON with the node's type and + // which sub-models have data. Q_INVOKABLE QString selectScenegraphNode(const QString &address) const; + // --- QML Item selection + properties --- + // Select a QQuickItem by address (from listQuickItems output). + // Triggers the item property controller, populating the item properties model. + Q_INVOKABLE QString selectQuickItem(const QString &address) const; + // Get all Q_PROPERTY values (x, y, width, height, opacity, visible, z, etc.) + // for a QML item. Selects the item first if not already selected. + Q_INVOKABLE QString getItemProperties(const QString &address) const; + // --- Geometry (requires a GeometryNode to be selected) --- // Returns vertex data: rows = vertices, columns = attributes. // Each cell is the display string (e.g. "0.5, 0.5, 0, 1"); headers are @@ -91,7 +97,8 @@ public: private: GammaRaySession *m_session; - mutable QString m_selectedAddress; // last selected node address (for sub-model tools) + mutable QString m_selectedAddress; // last selected SG node address (for sub-model tools) + mutable QString m_selectedItemAddress; // last selected QuickItem address (for property tools) QString ensureSession() const; @@ -101,6 +108,9 @@ private: // Ensure the given SG node is selected (select it if not already). Returns // empty string on success, or a JSON error string. QString ensureNodeSelected(const QString &address) const; + // Ensure the given QuickItem is selected (select it if not already). Returns + // empty string on success, or a JSON error string. + QString ensureItemSelected(const QString &address) const; // Wait for a model to have rows (polling with event loop, up to timeoutMs). void waitForRows(QAbstractItemModel *m, int timeoutMs) const; };