use uv to run test, more fixes

This commit is contained in:
2026-07-07 23:43:48 +08:00
parent f7d6a51220
commit 268483577e
16 changed files with 705 additions and 127 deletions

View File

@@ -780,8 +780,17 @@ QString SceneGraphTools::ensureItemSelected(const QString &address) const
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); }
// Wait for the property model to populate. The RemoteModel's lazy-fetch
// mechanism requests data from the probe on the first rowCount() call.
// After the wait, the data should have arrived.
if (propModel) {
propModel->rowCount();
// Give the model time to settle. Use a longer wait since the
// AggregatedPropertyModel has nested groups that need multiple
// fetch rounds. primeAndWait() is avoided here because it triggers
// a GammaRay RemoteModel use-after-free in primeTree().
settle(loop, 1500);
}
return {};
}
@@ -795,26 +804,28 @@ QString SceneGraphTools::selectQuickItem(const QString &address) const
// 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();
QJsonObject out;
if (!itemModel) {
out.insert(QStringLiteral("error"), QStringLiteral("QuickItemModel gone"));
return QString::fromUtf8(QJsonDocument(out).toJson(QJsonDocument::Compact));
}
const QModelIndex idx = findNodeByAddress(itemModel, address);
if (!idx.isValid()) {
out.insert(QStringLiteral("error"), QStringLiteral("address %1 not found after selection").arg(address));
return QString::fromUtf8(QJsonDocument(out).toJson(QJsonDocument::Compact));
}
const QModelIndex typeIdx = itemModel->index(idx.row(), 1, idx.parent());
const QString type = itemModel->data(typeIdx, Qt::DisplayRole).toString();
const int flags = itemModel->data(idx, kItemFlagsRole).toInt();
int propertyCount = 0;
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 },
};
out.insert(QStringLiteral("selected"), address);
out.insert(QStringLiteral("type"), type);
out.insert(QStringLiteral("flags"), flags);
out.insert(QStringLiteral("propertyCount"), propertyCount);
return QString::fromUtf8(QJsonDocument(out).toJson(QJsonDocument::Compact));
}
@@ -839,6 +850,30 @@ QString SceneGraphTools::getItemProperties(const QString &address) const
QStringLiteral("no properties available (did you select a valid QML item?)")))
.toJson(QJsonDocument::Compact));
// The RemoteModel uses lazy-fetch: data() triggers a fetch and returns
// "Loading..." until the fetch response arrives on the event loop.
// Poll until column 0 data arrives for at least one row.
{
QEventLoop loop;
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
bool gotData = false;
while (std::chrono::steady_clock::now() < deadline && !gotData) {
for (int r = 0; r < rows; ++r) {
const QString val = m->data(m->index(r, 0), Qt::DisplayRole).toString();
if (!val.isEmpty() && val != QStringLiteral("Loading...")) {
gotData = true;
break;
}
}
if (!gotData) {
QTimer::singleShot(200, &loop, &QEventLoop::quit);
loop.exec();
}
}
}
// Re-read rowCount after polling (more rows may have arrived)
const int actualRows = m->rowCount();
const int cols = m->columnCount();
QJsonObject out;
@@ -846,34 +881,76 @@ QString SceneGraphTools::getItemProperties(const QString &address) const
for (int c = 0; c < cols; ++c)
headers.append(m->headerData(c, Qt::Horizontal, Qt::DisplayRole).toString());
// Pre-fetch children for grouped properties. RemoteModel uses lazy-fetch:
// rowCount(parent) triggers an async request and returns 0 until the reply
// arrives on the event loop. After polling, hasChildren() works correctly.
for (int r = 0; r < actualRows; ++r) {
m->rowCount(m->index(r, 0));
}
// Poll to let row count responses arrive
if (actualRows > 0) {
QEventLoop l2;
QTimer::singleShot(500, &l2, &QEventLoop::quit);
l2.exec();
}
// Second pass: ensure child data is fetched for rows that have children
for (int r = 0; r < actualRows; ++r) {
const QModelIndex idx0 = m->index(r, 0);
if (m->hasChildren(idx0) && m->canFetchMore(idx0))
m->fetchMore(idx0);
}
if (actualRows > 0) {
QEventLoop l2b;
QTimer::singleShot(500, &l2b, &QEventLoop::quit);
l2b.exec();
}
// 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) {
for (int r = 0; r < actualRows; ++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))) {
const QModelIndex idx0 = m->index(r, 0);
if (m->hasChildren(idx0)) {
// 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);
// Children also use lazy-fetch, so poll if needed.
const int childRows = m->rowCount(idx0);
if (childRows > 0) {
// Trigger child data fetches first
for (int cr = 0; cr < childRows; ++cr) {
m->data(m->index(cr, 0, idx0), Qt::DisplayRole);
m->data(m->index(cr, 1, idx0), Qt::DisplayRole);
}
// Poll to let child data arrive
{
QEventLoop l3;
QTimer::singleShot(500, &l3, &QEventLoop::quit);
l3.exec();
}
// Read actual child data
QJsonObject children;
for (int cr = 0; cr < childRows; ++cr) {
const QString cname = m->data(m->index(cr, 0, idx0), Qt::DisplayRole).toString();
const QString cval = m->data(m->index(cr, 1, idx0), Qt::DisplayRole).toString();
const QString ctype = cols > 2 ? m->data(m->index(cr, 2, idx0), Qt::DisplayRole).toString() : QString();
if (cname.isEmpty() || cname == QStringLiteral("Loading..."))
continue;
QJsonObject child;
child.insert(QStringLiteral("value"), cval);
child.insert(QStringLiteral("type"), ctype);
children.insert(cname, child);
}
if (!children.isEmpty())
prop.insert(QStringLiteral("children"), children);
}
if (!children.isEmpty())
prop.insert(QStringLiteral("children"), children);
groups.insert(name, prop);
} else {
simpleProps.insert(name, prop);