tools for qml item properties
This commit is contained in:
@@ -93,6 +93,11 @@ int main(int argc, char **argv)
|
|||||||
// SG node selection
|
// SG node selection
|
||||||
{ QStringLiteral("selectScenegraphNode"), QStringLiteral("Select a SG node by address (from listScenegraphNodes) so geometry/material sub-models populate for it") },
|
{ 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)") },
|
{ 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
|
// Geometry
|
||||||
{ QStringLiteral("getNodeVertices"), QStringLiteral("Get vertex data for a GeometryNode (vertices, attributes, isCoordinate flags)") },
|
{ QStringLiteral("getNodeVertices"), QStringLiteral("Get vertex data for a GeometryNode (vertices, attributes, isCoordinate flags)") },
|
||||||
{ QStringLiteral("getNodeVertices/address"), QStringLiteral("Node address (must be a GeometryNode)") },
|
{ 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 *kQuickItemModel = "com.kdab.GammaRay.QuickItemModel";
|
||||||
static const char *kQuickSceneGraphModel = "com.kdab.GammaRay.QuickSceneGraphModel";
|
static const char *kQuickSceneGraphModel = "com.kdab.GammaRay.QuickSceneGraphModel";
|
||||||
static const char *kBaseName = "com.kdab.GammaRay.QuickSceneGraph";
|
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 *kMaterialIface = "com.kdab.GammaRay.MaterialExtensionInterface";
|
||||||
static const char *kQuickInspectorIface = "com.kdab.GammaRay.QuickInspectorInterface/1.0";
|
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));
|
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
|
QString SceneGraphTools::setRenderMode(const QString &mode) const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -58,12 +58,18 @@ public:
|
|||||||
// --- SceneGraph navigation ---
|
// --- SceneGraph navigation ---
|
||||||
Q_INVOKABLE QString listScenegraphNodes() const;
|
Q_INVOKABLE QString listScenegraphNodes() const;
|
||||||
// Find a node by address (e.g. "0x56396e6d7400") in the SG tree and select it
|
// 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
|
// via the selection model client path. Returns JSON with the node's type and
|
||||||
// which sub-models have data. Must be called before geometry/material tools can
|
// which sub-models have data.
|
||||||
// return data for that node. Selecting a non-GeometryNode clears the
|
|
||||||
// geometry/material models (they only populate for QSGGeometryNode).
|
|
||||||
Q_INVOKABLE QString selectScenegraphNode(const QString &address) const;
|
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) ---
|
// --- Geometry (requires a GeometryNode to be selected) ---
|
||||||
// Returns vertex data: rows = vertices, columns = attributes.
|
// Returns vertex data: rows = vertices, columns = attributes.
|
||||||
// Each cell is the display string (e.g. "0.5, 0.5, 0, 1"); headers are
|
// Each cell is the display string (e.g. "0.5, 0.5, 0, 1"); headers are
|
||||||
@@ -91,7 +97,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
GammaRaySession *m_session;
|
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;
|
QString ensureSession() const;
|
||||||
@@ -101,6 +108,9 @@ private:
|
|||||||
// Ensure the given SG node is selected (select it if not already). Returns
|
// Ensure the given SG node is selected (select it if not already). Returns
|
||||||
// empty string on success, or a JSON error string.
|
// empty string on success, or a JSON error string.
|
||||||
QString ensureNodeSelected(const QString &address) const;
|
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).
|
// Wait for a model to have rows (polling with event loop, up to timeoutMs).
|
||||||
void waitForRows(QAbstractItemModel *m, int timeoutMs) const;
|
void waitForRows(QAbstractItemModel *m, int timeoutMs) const;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user