tools for qml item properties

This commit is contained in:
2026-07-07 20:31:55 +08:00
parent 47da444d74
commit 316d52bfe5
3 changed files with 174 additions and 6 deletions

View File

@@ -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
{