mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Type chooser: fix composite type changes (struct, pointer, array modifiers)
This commit is contained in:
@@ -336,6 +336,25 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Embedded struct with refId but no child nodes: expand referenced struct's
|
||||||
|
// children at this node's offset (single instance, like array with count=1)
|
||||||
|
if (node.kind == NodeKind::Struct && children.isEmpty() && node.refId != 0) {
|
||||||
|
int refIdx = tree.indexOfId(node.refId);
|
||||||
|
if (refIdx >= 0) {
|
||||||
|
QVector<int> refChildren = state.childMap.value(node.refId);
|
||||||
|
std::sort(refChildren.begin(), refChildren.end(), [&](int a, int b) {
|
||||||
|
return tree.nodes[a].offset < tree.nodes[b].offset;
|
||||||
|
});
|
||||||
|
for (int childIdx : refChildren) {
|
||||||
|
// Skip self-referential children (e.g. struct Ball has a field of type Ball)
|
||||||
|
if (state.visiting.contains(tree.nodes[childIdx].id))
|
||||||
|
continue;
|
||||||
|
composeNode(state, tree, prov, childIdx, childDepth,
|
||||||
|
absAddr, node.refId, false, node.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For arrays, render children as condensed (no header/footer for struct elements)
|
// For arrays, render children as condensed (no header/footer for struct elements)
|
||||||
bool childrenAreArrayElements = (node.kind == NodeKind::Array);
|
bool childrenAreArrayElements = (node.kind == NodeKind::Array);
|
||||||
int elementIdx = 0;
|
int elementIdx = 0;
|
||||||
|
|||||||
@@ -1703,6 +1703,63 @@ void RcxController::applyTypePopupResult(TypePopupMode mode, int nodeIdx,
|
|||||||
if (entry.entryKind == TypeEntry::Primitive) {
|
if (entry.entryKind == TypeEntry::Primitive) {
|
||||||
if (entry.primitiveKind != node.kind)
|
if (entry.primitiveKind != node.kind)
|
||||||
changeNodeKind(nodeIdx, entry.primitiveKind);
|
changeNodeKind(nodeIdx, entry.primitiveKind);
|
||||||
|
} else if (entry.entryKind == TypeEntry::Composite) {
|
||||||
|
bool wasSuppressed = m_suppressRefresh;
|
||||||
|
m_suppressRefresh = true;
|
||||||
|
m_doc->undoStack.beginMacro(QStringLiteral("Change to composite type"));
|
||||||
|
|
||||||
|
if (spec.isPointer) {
|
||||||
|
// Pointer modifier: e.g. "Material*" → Pointer64 + refId
|
||||||
|
if (node.kind != NodeKind::Pointer64)
|
||||||
|
changeNodeKind(nodeIdx, NodeKind::Pointer64);
|
||||||
|
int idx = m_doc->tree.indexOfId(node.id);
|
||||||
|
if (idx >= 0 && m_doc->tree.nodes[idx].refId != entry.structId)
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangePointerRef{node.id, m_doc->tree.nodes[idx].refId, entry.structId}));
|
||||||
|
|
||||||
|
} else if (spec.arrayCount > 0) {
|
||||||
|
// Array modifier: e.g. "Material[10]" → Array + Struct element
|
||||||
|
if (node.kind != NodeKind::Array)
|
||||||
|
changeNodeKind(nodeIdx, NodeKind::Array);
|
||||||
|
int idx = m_doc->tree.indexOfId(node.id);
|
||||||
|
if (idx >= 0) {
|
||||||
|
auto& n = m_doc->tree.nodes[idx];
|
||||||
|
if (n.elementKind != NodeKind::Struct || n.arrayLen != spec.arrayCount)
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangeArrayMeta{node.id, n.elementKind, NodeKind::Struct,
|
||||||
|
n.arrayLen, spec.arrayCount}));
|
||||||
|
if (n.refId != entry.structId)
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangePointerRef{node.id, n.refId, entry.structId}));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Plain struct: e.g. "Material" → Struct + structTypeName + refId + collapsed
|
||||||
|
if (node.kind != NodeKind::Struct)
|
||||||
|
changeNodeKind(nodeIdx, NodeKind::Struct);
|
||||||
|
int idx = m_doc->tree.indexOfId(node.id);
|
||||||
|
if (idx >= 0) {
|
||||||
|
int refIdx = m_doc->tree.indexOfId(entry.structId);
|
||||||
|
QString targetName;
|
||||||
|
if (refIdx >= 0) {
|
||||||
|
const Node& ref = m_doc->tree.nodes[refIdx];
|
||||||
|
targetName = ref.structTypeName.isEmpty() ? ref.name : ref.structTypeName;
|
||||||
|
}
|
||||||
|
QString oldTypeName = m_doc->tree.nodes[idx].structTypeName;
|
||||||
|
if (oldTypeName != targetName)
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangeStructTypeName{node.id, oldTypeName, targetName}));
|
||||||
|
// Set refId so compose can expand the referenced struct's children
|
||||||
|
if (m_doc->tree.nodes[idx].refId != entry.structId)
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangePointerRef{node.id, m_doc->tree.nodes[idx].refId, entry.structId}));
|
||||||
|
// ChangePointerRef auto-sets collapsed=true when refId != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_doc->undoStack.endMacro();
|
||||||
|
m_suppressRefresh = wasSuppressed;
|
||||||
|
if (!m_suppressRefresh) refresh();
|
||||||
}
|
}
|
||||||
} else if (mode == TypePopupMode::ArrayElement) {
|
} else if (mode == TypePopupMode::ArrayElement) {
|
||||||
if (entry.entryKind == TypeEntry::Primitive) {
|
if (entry.entryKind == TypeEntry::Primitive) {
|
||||||
|
|||||||
@@ -366,6 +366,10 @@ struct NodeTree {
|
|||||||
if (end > maxEnd) maxEnd = end;
|
if (end > maxEnd) maxEnd = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Embedded struct reference: no own children but refId points to a struct definition
|
||||||
|
if (kids.isEmpty() && node.kind == NodeKind::Struct && node.refId != 0)
|
||||||
|
maxEnd = qMax(maxEnd, structSpan(node.refId, childMap, visited));
|
||||||
|
|
||||||
return qMax(declaredSize, maxEnd);
|
return qMax(declaredSize, maxEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -405,6 +405,186 @@ private slots:
|
|||||||
QCOMPARE(spec.arrayCount, 0);
|
QCOMPARE(spec.arrayCount, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── FieldType popup: selecting a composite (struct) type changes node kind + structTypeName + collapsed ──
|
||||||
|
|
||||||
|
void testFieldTypeCompositeChangesNodeToStruct() {
|
||||||
|
auto* doc = new RcxDocument();
|
||||||
|
buildTwoRootTree(doc->tree);
|
||||||
|
doc->provider = std::make_unique<BufferProvider>(makeBuffer());
|
||||||
|
|
||||||
|
auto* splitter = new QSplitter();
|
||||||
|
auto* ctrl = new RcxController(doc, nullptr);
|
||||||
|
ctrl->addSplitEditor(splitter);
|
||||||
|
|
||||||
|
splitter->resize(800, 600);
|
||||||
|
splitter->show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(splitter));
|
||||||
|
ctrl->refresh();
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
// Find the "x" field (Int32) inside Alpha struct, and Bravo struct id
|
||||||
|
int xIdx = -1;
|
||||||
|
uint64_t bravoId = 0;
|
||||||
|
for (int i = 0; i < doc->tree.nodes.size(); i++) {
|
||||||
|
const auto& n = doc->tree.nodes[i];
|
||||||
|
if (n.name == "x" && n.kind == NodeKind::Int32) xIdx = i;
|
||||||
|
if (n.name == "Bravo" && n.kind == NodeKind::Struct) bravoId = n.id;
|
||||||
|
}
|
||||||
|
QVERIFY(xIdx >= 0);
|
||||||
|
QVERIFY(bravoId != 0);
|
||||||
|
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].kind, NodeKind::Int32);
|
||||||
|
QVERIFY(!doc->tree.nodes[xIdx].collapsed);
|
||||||
|
uint64_t xNodeId = doc->tree.nodes[xIdx].id;
|
||||||
|
|
||||||
|
// Simulate the plain-struct path of applyTypePopupResult:
|
||||||
|
// beginMacro → changeNodeKind(Struct) → ChangeStructTypeName → ChangePointerRef → endMacro
|
||||||
|
doc->undoStack.beginMacro(QStringLiteral("Change to composite type"));
|
||||||
|
ctrl->changeNodeKind(xIdx, NodeKind::Struct);
|
||||||
|
|
||||||
|
xIdx = doc->tree.indexOfId(xNodeId);
|
||||||
|
QVERIFY(xIdx >= 0);
|
||||||
|
|
||||||
|
int bravoIdx = doc->tree.indexOfId(bravoId);
|
||||||
|
QVERIFY(bravoIdx >= 0);
|
||||||
|
QString targetName = doc->tree.nodes[bravoIdx].structTypeName;
|
||||||
|
|
||||||
|
doc->undoStack.push(new RcxCommand(ctrl,
|
||||||
|
cmd::ChangeStructTypeName{xNodeId, doc->tree.nodes[xIdx].structTypeName, targetName}));
|
||||||
|
|
||||||
|
// Set refId so compose can expand referenced struct children (auto-collapses)
|
||||||
|
doc->undoStack.push(new RcxCommand(ctrl,
|
||||||
|
cmd::ChangePointerRef{xNodeId, 0, bravoId}));
|
||||||
|
|
||||||
|
doc->undoStack.endMacro();
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
// Verify: Struct with correct name, refId, AND collapsed
|
||||||
|
xIdx = doc->tree.indexOfId(xNodeId);
|
||||||
|
QVERIFY(xIdx >= 0);
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].kind, NodeKind::Struct);
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].structTypeName, QString("Bravo"));
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].refId, bravoId);
|
||||||
|
QVERIFY(doc->tree.nodes[xIdx].collapsed);
|
||||||
|
|
||||||
|
// Single undo reverses the entire macro
|
||||||
|
doc->undoStack.undo();
|
||||||
|
QApplication::processEvents();
|
||||||
|
xIdx = doc->tree.indexOfId(xNodeId);
|
||||||
|
QVERIFY(xIdx >= 0);
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].kind, NodeKind::Int32);
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].refId, uint64_t(0));
|
||||||
|
QVERIFY(doc->tree.nodes[xIdx].structTypeName.isEmpty());
|
||||||
|
|
||||||
|
delete ctrl;
|
||||||
|
delete splitter;
|
||||||
|
delete doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── FieldType popup: selecting a composite with * modifier creates Pointer64 + refId ──
|
||||||
|
|
||||||
|
void testFieldTypeCompositeWithPointerModifier() {
|
||||||
|
auto* doc = new RcxDocument();
|
||||||
|
buildTwoRootTree(doc->tree);
|
||||||
|
doc->provider = std::make_unique<BufferProvider>(makeBuffer());
|
||||||
|
|
||||||
|
auto* splitter = new QSplitter();
|
||||||
|
auto* ctrl = new RcxController(doc, nullptr);
|
||||||
|
ctrl->addSplitEditor(splitter);
|
||||||
|
|
||||||
|
splitter->resize(800, 600);
|
||||||
|
splitter->show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(splitter));
|
||||||
|
ctrl->refresh();
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
// Find the "x" field (Int32) and Bravo struct
|
||||||
|
int xIdx = -1;
|
||||||
|
uint64_t bravoId = 0;
|
||||||
|
for (int i = 0; i < doc->tree.nodes.size(); i++) {
|
||||||
|
const auto& n = doc->tree.nodes[i];
|
||||||
|
if (n.name == "x" && n.kind == NodeKind::Int32) xIdx = i;
|
||||||
|
if (n.name == "Bravo" && n.kind == NodeKind::Struct) bravoId = n.id;
|
||||||
|
}
|
||||||
|
QVERIFY(xIdx >= 0);
|
||||||
|
QVERIFY(bravoId != 0);
|
||||||
|
|
||||||
|
uint64_t xNodeId = doc->tree.nodes[xIdx].id;
|
||||||
|
|
||||||
|
// Simulate the pointer path of applyTypePopupResult:
|
||||||
|
// beginMacro → changeNodeKind(Pointer64) → ChangePointerRef → endMacro
|
||||||
|
doc->undoStack.beginMacro(QStringLiteral("Change to composite type"));
|
||||||
|
ctrl->changeNodeKind(xIdx, NodeKind::Pointer64);
|
||||||
|
|
||||||
|
xIdx = doc->tree.indexOfId(xNodeId);
|
||||||
|
QVERIFY(xIdx >= 0);
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].kind, NodeKind::Pointer64);
|
||||||
|
|
||||||
|
doc->undoStack.push(new RcxCommand(ctrl,
|
||||||
|
cmd::ChangePointerRef{xNodeId, 0, bravoId}));
|
||||||
|
doc->undoStack.endMacro();
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
// Verify: Pointer64 with refId pointing to Bravo, auto-collapsed
|
||||||
|
xIdx = doc->tree.indexOfId(xNodeId);
|
||||||
|
QVERIFY(xIdx >= 0);
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].kind, NodeKind::Pointer64);
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].refId, bravoId);
|
||||||
|
QVERIFY(doc->tree.nodes[xIdx].collapsed);
|
||||||
|
|
||||||
|
// Single undo reverses the entire macro
|
||||||
|
doc->undoStack.undo();
|
||||||
|
QApplication::processEvents();
|
||||||
|
xIdx = doc->tree.indexOfId(xNodeId);
|
||||||
|
QVERIFY(xIdx >= 0);
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].kind, NodeKind::Int32);
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].refId, uint64_t(0));
|
||||||
|
|
||||||
|
delete ctrl;
|
||||||
|
delete splitter;
|
||||||
|
delete doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── FieldType popup: selecting a primitive type still works ──
|
||||||
|
|
||||||
|
void testFieldTypePrimitiveStillWorks() {
|
||||||
|
auto* doc = new RcxDocument();
|
||||||
|
buildTwoRootTree(doc->tree);
|
||||||
|
doc->provider = std::make_unique<BufferProvider>(makeBuffer());
|
||||||
|
|
||||||
|
auto* splitter = new QSplitter();
|
||||||
|
auto* ctrl = new RcxController(doc, nullptr);
|
||||||
|
ctrl->addSplitEditor(splitter);
|
||||||
|
|
||||||
|
splitter->resize(800, 600);
|
||||||
|
splitter->show();
|
||||||
|
QVERIFY(QTest::qWaitForWindowExposed(splitter));
|
||||||
|
ctrl->refresh();
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
// Find the "x" field (Int32)
|
||||||
|
int xIdx = -1;
|
||||||
|
for (int i = 0; i < doc->tree.nodes.size(); i++) {
|
||||||
|
if (doc->tree.nodes[i].name == "x") { xIdx = i; break; }
|
||||||
|
}
|
||||||
|
QVERIFY(xIdx >= 0);
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].kind, NodeKind::Int32);
|
||||||
|
|
||||||
|
// Change to Float via changeNodeKind (same path as primitive TypeEntry)
|
||||||
|
ctrl->changeNodeKind(xIdx, NodeKind::Float);
|
||||||
|
QApplication::processEvents();
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].kind, NodeKind::Float);
|
||||||
|
|
||||||
|
// Undo
|
||||||
|
doc->undoStack.undo();
|
||||||
|
QApplication::processEvents();
|
||||||
|
QCOMPARE(doc->tree.nodes[xIdx].kind, NodeKind::Int32);
|
||||||
|
|
||||||
|
delete ctrl;
|
||||||
|
delete splitter;
|
||||||
|
delete doc;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Section headers in filtered list ──
|
// ── Section headers in filtered list ──
|
||||||
|
|
||||||
void testSectionHeadersPresent() {
|
void testSectionHeadersPresent() {
|
||||||
|
|||||||
Reference in New Issue
Block a user