tools for qml item properties
This commit is contained in:
@@ -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)") },
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user