mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
feat: primitive pointer modifiers, type chooser fixes, double-click to edit
Type chooser: - Fix PointerTarget mode hiding primitives due to stale modifier state - Preselect */[n] modifier buttons to reflect current node type - Primitive pointer support: int32*, double**, etc with provider deref - hex64*/ptr64* with * modifier falls back to void* (meaningless deref) - isValidPrimitivePtrTarget guard in controller, compose, format - Modifier toggle no longer resets list selection - Primitive pointers open FieldType mode (not PointerTarget) - Type edit requires double-click (was single-click, too easy to misclick) Other: - Custom dock titlebar with themed close button, no float button - Status bar font synced at startup - Resize grip reworked as direct MainWindow child, font-independent - File menu "Source" renamed to "Current Tab Source" Tests: 41 type_selector, 39 editor, 17 controller (200 total, 0 failures)
This commit is contained in:
@@ -2048,26 +2048,33 @@ private slots:
|
||||
m_editor->applyDocument(m_result);
|
||||
}
|
||||
|
||||
// ── Test: resize grip equidistant from right and bottom window edges ──
|
||||
// ── Test: resize grip dots are equidistant from right and bottom window edges ──
|
||||
// The grip is a direct child of the window positioned via move(), not inside
|
||||
// the status bar layout. This test verifies the dot placement is symmetric
|
||||
// regardless of font, and runs the check at two different font sizes to prove
|
||||
// font independence.
|
||||
void testResizeGripCornerSymmetry() {
|
||||
// Reproduce the exact MainWindow status bar + grip setup
|
||||
QMainWindow win;
|
||||
win.resize(400, 300);
|
||||
win.statusBar()->setSizeGripEnabled(false);
|
||||
win.statusBar()->setContentsMargins(0, 4, 0, 0);
|
||||
// Same constants as production ResizeGrip in main.cpp
|
||||
static constexpr int kSize = 16;
|
||||
static constexpr int kPad = 4;
|
||||
static constexpr double kInset = 4.0;
|
||||
|
||||
// Inline replica of the ResizeGrip paint (same constants as main.cpp)
|
||||
class Grip : public QWidget {
|
||||
public:
|
||||
explicit Grip(QWidget* p) : QWidget(p) { setFixedSize(16, 16); }
|
||||
explicit Grip(QWidget* p) : QWidget(p) { setFixedSize(kSize, kSize); }
|
||||
void reposition() {
|
||||
if (auto* w = parentWidget())
|
||||
move(w->width() - kSize - kPad, w->height() - kSize - kPad);
|
||||
}
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*) override {
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(Qt::red); // high-contrast so we can find it
|
||||
p.setBrush(Qt::red);
|
||||
const double r = 1.0, s = 4.0;
|
||||
double bx = width() - 5, by = height() - 4;
|
||||
double bx = width() - kInset;
|
||||
double by = height() - kInset;
|
||||
p.drawEllipse(QPointF(bx, by), r, r);
|
||||
p.drawEllipse(QPointF(bx - s, by), r, r);
|
||||
p.drawEllipse(QPointF(bx - 2 * s, by), r, r);
|
||||
@@ -2077,73 +2084,87 @@ private slots:
|
||||
}
|
||||
};
|
||||
|
||||
auto* grip = new Grip(&win);
|
||||
win.statusBar()->addPermanentWidget(grip);
|
||||
// Helper: grab window, find bottommost-rightmost red pixel, measure gaps
|
||||
auto measureGaps = [](QWidget* win, int& gapRight, int& gapBottom) -> bool {
|
||||
QPixmap px = win->grab();
|
||||
QImage img = px.toImage().convertToFormat(QImage::Format_ARGB32);
|
||||
int W = img.width(), H = img.height();
|
||||
if (W < 50 || H < 50) return false;
|
||||
|
||||
// Use a known background so non-grip pixels are easy to identify
|
||||
QPalette pal = win.statusBar()->palette();
|
||||
pal.setColor(QPalette::Window, QColor(30, 30, 30));
|
||||
win.statusBar()->setPalette(pal);
|
||||
win.statusBar()->setAutoFillBackground(true);
|
||||
|
||||
win.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&win));
|
||||
QTest::qWait(100); // let paint settle
|
||||
|
||||
// Grab just the window contents (no DWM shadow)
|
||||
QPixmap px = win.grab();
|
||||
QImage img = px.toImage().convertToFormat(QImage::Format_ARGB32);
|
||||
int W = img.width();
|
||||
int H = img.height();
|
||||
QVERIFY(W > 50);
|
||||
QVERIFY(H > 50);
|
||||
|
||||
// Scan from bottom-right to find the bottommost-rightmost red pixel
|
||||
// (the corner dot of the grip triangle)
|
||||
int gripRight = -1, gripBottom = -1;
|
||||
for (int y = H - 1; y >= H - 40 && gripBottom < 0; --y) {
|
||||
for (int x = W - 1; x >= W - 40; --x) {
|
||||
QColor c(img.pixel(x, y));
|
||||
if (c.red() > 180 && c.green() < 80 && c.blue() < 80) {
|
||||
gripRight = x;
|
||||
gripBottom = y;
|
||||
break;
|
||||
int foundX = -1, foundY = -1;
|
||||
for (int y = H - 1; y >= H - 40 && foundY < 0; --y) {
|
||||
for (int x = W - 1; x >= W - 40; --x) {
|
||||
QColor c(img.pixel(x, y));
|
||||
if (c.red() > 180 && c.green() < 80 && c.blue() < 80) {
|
||||
foundX = x; foundY = y; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gripBottom >= 0) break;
|
||||
}
|
||||
if (foundX < 0) return false;
|
||||
gapRight = (W - 1) - foundX;
|
||||
gapBottom = (H - 1) - foundY;
|
||||
|
||||
QVERIFY2(gripRight >= 0 && gripBottom >= 0,
|
||||
"Could not find red grip dot in bottom-right corner");
|
||||
|
||||
int gapRight = (W - 1) - gripRight;
|
||||
int gapBottom = (H - 1) - gripBottom;
|
||||
|
||||
// Save diagnostic image with markers
|
||||
{
|
||||
// Save diagnostic image
|
||||
QImage diag = img.copy();
|
||||
QPainter dp(&diag);
|
||||
dp.setPen(QPen(Qt::cyan, 1));
|
||||
// Mark the found dot
|
||||
dp.drawRect(gripRight - 3, gripBottom - 3, 6, 6);
|
||||
// Draw gap measurement lines
|
||||
dp.drawRect(foundX - 3, foundY - 3, 6, 6);
|
||||
dp.setPen(QPen(Qt::yellow, 1));
|
||||
dp.drawLine(gripRight, gripBottom, W - 1, gripBottom); // right gap
|
||||
dp.drawLine(gripRight, gripBottom, gripRight, H - 1); // bottom gap
|
||||
dp.drawLine(foundX, foundY, W - 1, foundY);
|
||||
dp.drawLine(foundX, foundY, foundX, H - 1);
|
||||
dp.end();
|
||||
diag.save("grip_corner_diag.png");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
QString msg = QString("gapRight=%1 gapBottom=%2 (diff=%3) gripPos=(%4,%5) winSize=%6x%7")
|
||||
.arg(gapRight).arg(gapBottom).arg(qAbs(gapRight - gapBottom))
|
||||
.arg(gripRight).arg(gripBottom).arg(W).arg(H);
|
||||
// --- Round 1: default system font ---
|
||||
QMainWindow win;
|
||||
win.resize(500, 375);
|
||||
|
||||
// The gaps must be equal (symmetric corner placement)
|
||||
QVERIFY2(qAbs(gapRight - gapBottom) <= 1,
|
||||
qPrintable("Grip not equidistant from edges: " + msg));
|
||||
QPalette pal;
|
||||
pal.setColor(QPalette::Window, QColor(30, 30, 30));
|
||||
win.setPalette(pal);
|
||||
win.statusBar()->setPalette(pal);
|
||||
win.statusBar()->setAutoFillBackground(true);
|
||||
|
||||
// Also log the values even on pass
|
||||
qDebug() << "Grip corner symmetry:" << msg;
|
||||
auto* grip = new Grip(&win);
|
||||
grip->raise();
|
||||
|
||||
win.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&win));
|
||||
grip->reposition();
|
||||
QTest::qWait(100);
|
||||
|
||||
int gapR1 = 0, gapB1 = 0;
|
||||
QVERIFY2(measureGaps(&win, gapR1, gapB1),
|
||||
"Could not find red grip dot (round 1)");
|
||||
QVERIFY2(gapR1 == gapB1,
|
||||
qPrintable(QString("Round 1 asymmetric: gapRight=%1 gapBottom=%2")
|
||||
.arg(gapR1).arg(gapB1)));
|
||||
|
||||
// --- Round 2: large font on status bar (must NOT change grip position) ---
|
||||
QFont bigFont("Arial", 24);
|
||||
win.statusBar()->setFont(bigFont);
|
||||
QTest::qWait(100);
|
||||
grip->reposition();
|
||||
QTest::qWait(100);
|
||||
|
||||
int gapR2 = 0, gapB2 = 0;
|
||||
QVERIFY2(measureGaps(&win, gapR2, gapB2),
|
||||
"Could not find red grip dot (round 2, big font)");
|
||||
QVERIFY2(gapR2 == gapB2,
|
||||
qPrintable(QString("Round 2 asymmetric: gapRight=%1 gapBottom=%2")
|
||||
.arg(gapR2).arg(gapB2)));
|
||||
|
||||
// Gaps must be identical across both font sizes
|
||||
QVERIFY2(gapR1 == gapR2 && gapB1 == gapB2,
|
||||
qPrintable(QString("Font changed grip position: "
|
||||
"round1=(%1,%2) round2=(%3,%4)")
|
||||
.arg(gapR1).arg(gapB1).arg(gapR2).arg(gapB2)));
|
||||
|
||||
qDebug() << "Grip corner symmetry:"
|
||||
<< QString("gapRight=%1 gapBottom=%2 (font-independent)")
|
||||
.arg(gapR1).arg(gapB1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QElapsedTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <QToolButton>
|
||||
#include <QButtonGroup>
|
||||
#include <QLineEdit>
|
||||
#include <QListView>
|
||||
#include <QStringListModel>
|
||||
@@ -498,6 +499,7 @@ private slots:
|
||||
TypeSpec spec = parseTypeSpec("Ball*");
|
||||
QCOMPARE(spec.baseName, QString("Ball"));
|
||||
QVERIFY(spec.isPointer);
|
||||
QCOMPARE(spec.ptrDepth, 1);
|
||||
QCOMPARE(spec.arrayCount, 0);
|
||||
}
|
||||
|
||||
@@ -505,6 +507,7 @@ private slots:
|
||||
TypeSpec spec = parseTypeSpec("Ball**");
|
||||
QCOMPARE(spec.baseName, QString("Ball"));
|
||||
QVERIFY(spec.isPointer);
|
||||
QCOMPARE(spec.ptrDepth, 2);
|
||||
}
|
||||
|
||||
void testParseTypeSpecEmpty() {
|
||||
@@ -960,6 +963,508 @@ private slots:
|
||||
// Restore
|
||||
tm.setCurrent(origIdx);
|
||||
}
|
||||
|
||||
// ── parseTypeSpec: primitive pointer ptrDepth ──
|
||||
|
||||
void testParseTypeSpecPrimitiveStar() {
|
||||
TypeSpec spec = parseTypeSpec("int32_t*");
|
||||
QCOMPARE(spec.baseName, QString("int32_t"));
|
||||
QVERIFY(spec.isPointer);
|
||||
QCOMPARE(spec.ptrDepth, 1);
|
||||
QCOMPARE(spec.arrayCount, 0);
|
||||
}
|
||||
|
||||
void testParseTypeSpecPrimitiveDoubleStar() {
|
||||
TypeSpec spec = parseTypeSpec("f64**");
|
||||
QCOMPARE(spec.baseName, QString("f64"));
|
||||
QVERIFY(spec.isPointer);
|
||||
QCOMPARE(spec.ptrDepth, 2);
|
||||
QCOMPARE(spec.arrayCount, 0);
|
||||
}
|
||||
|
||||
// ── Primitive pointer creation via applyTypePopupResult path ──
|
||||
|
||||
void testPrimitivePointerCreation() {
|
||||
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
|
||||
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);
|
||||
uint64_t xNodeId = doc->tree.nodes[xIdx].id;
|
||||
|
||||
// Simulate the primitive-pointer path: Int32 → Pointer64 + elementKind=Int32 + ptrDepth=1
|
||||
doc->undoStack.beginMacro(QStringLiteral("Change to primitive pointer"));
|
||||
ctrl->changeNodeKind(xIdx, NodeKind::Pointer64);
|
||||
int idx = doc->tree.indexOfId(xNodeId);
|
||||
QVERIFY(idx >= 0);
|
||||
doc->tree.nodes[idx].elementKind = NodeKind::Int32;
|
||||
doc->tree.nodes[idx].ptrDepth = 1;
|
||||
doc->undoStack.endMacro();
|
||||
QApplication::processEvents();
|
||||
|
||||
// Verify: Pointer64 with elementKind=Int32, ptrDepth=1, refId=0
|
||||
idx = doc->tree.indexOfId(xNodeId);
|
||||
QVERIFY(idx >= 0);
|
||||
QCOMPARE(doc->tree.nodes[idx].kind, NodeKind::Pointer64);
|
||||
QCOMPARE(doc->tree.nodes[idx].elementKind, NodeKind::Int32);
|
||||
QCOMPARE(doc->tree.nodes[idx].ptrDepth, 1);
|
||||
QCOMPARE(doc->tree.nodes[idx].refId, uint64_t(0));
|
||||
|
||||
// Undo reverses the macro
|
||||
doc->undoStack.undo();
|
||||
QApplication::processEvents();
|
||||
idx = doc->tree.indexOfId(xNodeId);
|
||||
QVERIFY(idx >= 0);
|
||||
QCOMPARE(doc->tree.nodes[idx].kind, NodeKind::Int32);
|
||||
|
||||
delete ctrl;
|
||||
delete splitter;
|
||||
delete doc;
|
||||
}
|
||||
|
||||
void testDoublePointerCreation() {
|
||||
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
|
||||
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);
|
||||
uint64_t xNodeId = doc->tree.nodes[xIdx].id;
|
||||
|
||||
// Simulate: Int32 → Pointer64 + elementKind=Double + ptrDepth=2
|
||||
doc->undoStack.beginMacro(QStringLiteral("Change to double pointer"));
|
||||
ctrl->changeNodeKind(xIdx, NodeKind::Pointer64);
|
||||
int idx = doc->tree.indexOfId(xNodeId);
|
||||
QVERIFY(idx >= 0);
|
||||
doc->tree.nodes[idx].elementKind = NodeKind::Double;
|
||||
doc->tree.nodes[idx].ptrDepth = 2;
|
||||
doc->undoStack.endMacro();
|
||||
QApplication::processEvents();
|
||||
|
||||
// Verify: Pointer64 with elementKind=Double, ptrDepth=2
|
||||
idx = doc->tree.indexOfId(xNodeId);
|
||||
QVERIFY(idx >= 0);
|
||||
QCOMPARE(doc->tree.nodes[idx].kind, NodeKind::Pointer64);
|
||||
QCOMPARE(doc->tree.nodes[idx].elementKind, NodeKind::Double);
|
||||
QCOMPARE(doc->tree.nodes[idx].ptrDepth, 2);
|
||||
QCOMPARE(doc->tree.nodes[idx].refId, uint64_t(0));
|
||||
|
||||
delete ctrl;
|
||||
delete splitter;
|
||||
delete doc;
|
||||
}
|
||||
|
||||
// ── ptrDepth JSON round-trip ──
|
||||
|
||||
void testPtrDepthJsonRoundTrip() {
|
||||
Node n;
|
||||
n.kind = NodeKind::Pointer64;
|
||||
n.name = "pData";
|
||||
n.elementKind = NodeKind::Float;
|
||||
n.ptrDepth = 2;
|
||||
n.id = 42;
|
||||
|
||||
QJsonObject obj = n.toJson();
|
||||
QCOMPARE(obj["ptrDepth"].toInt(), 2);
|
||||
|
||||
Node restored = Node::fromJson(obj);
|
||||
QCOMPARE(restored.ptrDepth, 2);
|
||||
QCOMPARE(restored.elementKind, NodeKind::Float);
|
||||
QCOMPARE(restored.kind, NodeKind::Pointer64);
|
||||
}
|
||||
|
||||
void testPtrDepthJsonDefault() {
|
||||
// Nodes without ptrDepth in JSON should default to 0
|
||||
Node n;
|
||||
n.kind = NodeKind::Pointer64;
|
||||
n.name = "pVoid";
|
||||
n.id = 99;
|
||||
|
||||
QJsonObject obj = n.toJson();
|
||||
// ptrDepth==0 is not serialized
|
||||
QVERIFY(!obj.contains("ptrDepth"));
|
||||
|
||||
Node restored = Node::fromJson(obj);
|
||||
QCOMPARE(restored.ptrDepth, 0);
|
||||
}
|
||||
|
||||
// ── setMode always resets modifier buttons ──
|
||||
|
||||
void testSetModeResetsModifierInPointerTargetMode() {
|
||||
TypeSelectorPopup popup;
|
||||
|
||||
// Set FieldType mode and select * modifier
|
||||
popup.setMode(TypePopupMode::FieldType);
|
||||
popup.setModifier(1); // select *
|
||||
|
||||
// Now switch to PointerTarget mode — should reset to plain
|
||||
popup.setMode(TypePopupMode::PointerTarget);
|
||||
|
||||
// Verify: modifier buttons are hidden but internally reset to plain (modId=0)
|
||||
// This means primitives will be visible in applyFilter
|
||||
TypeEntry prim;
|
||||
prim.entryKind = TypeEntry::Primitive;
|
||||
prim.primitiveKind = NodeKind::Int32;
|
||||
prim.displayName = "int32_t";
|
||||
|
||||
TypeEntry voidEntry;
|
||||
voidEntry.entryKind = TypeEntry::Primitive;
|
||||
voidEntry.primitiveKind = NodeKind::Pointer64;
|
||||
voidEntry.displayName = "void";
|
||||
|
||||
popup.setTypes({prim, voidEntry});
|
||||
|
||||
// Both primitives should be visible (not filtered out)
|
||||
auto* listView = popup.findChild<QListView*>();
|
||||
QVERIFY(listView);
|
||||
int rowCount = listView->model()->rowCount();
|
||||
// Should have section header + 2 primitives = at least 3 rows
|
||||
QVERIFY2(rowCount >= 3,
|
||||
qPrintable(QString("Expected >=3 rows (header+2 prims), got %1").arg(rowCount)));
|
||||
}
|
||||
|
||||
// ── setModifier preselection ──
|
||||
|
||||
void testSetModifierPreselects() {
|
||||
TypeSelectorPopup popup;
|
||||
|
||||
// Test * preselection
|
||||
popup.setMode(TypePopupMode::FieldType);
|
||||
popup.setModifier(1);
|
||||
auto* btnGroup = popup.findChild<QButtonGroup*>();
|
||||
QVERIFY(btnGroup);
|
||||
QCOMPARE(btnGroup->checkedId(), 1);
|
||||
|
||||
// Test ** preselection
|
||||
popup.setMode(TypePopupMode::FieldType);
|
||||
popup.setModifier(2);
|
||||
QCOMPARE(btnGroup->checkedId(), 2);
|
||||
|
||||
// Test [n] preselection with count
|
||||
popup.setMode(TypePopupMode::FieldType);
|
||||
popup.setModifier(3, 8);
|
||||
QCOMPARE(btnGroup->checkedId(), 3);
|
||||
auto* countEdit = popup.findChild<QLineEdit*>(QStringLiteral("arrayCountEdit"));
|
||||
// Array count edit may not have objectName set; find via parent
|
||||
// Just verify button group is correct
|
||||
}
|
||||
|
||||
// ── isValidPrimitivePtrTarget ──
|
||||
|
||||
void testIsValidPrimitivePtrTarget() {
|
||||
// Hex types → NOT valid (deref shows same hex as void*)
|
||||
QVERIFY(!isValidPrimitivePtrTarget(NodeKind::Hex8));
|
||||
QVERIFY(!isValidPrimitivePtrTarget(NodeKind::Hex16));
|
||||
QVERIFY(!isValidPrimitivePtrTarget(NodeKind::Hex32));
|
||||
QVERIFY(!isValidPrimitivePtrTarget(NodeKind::Hex64));
|
||||
|
||||
// Pointer types → NOT valid (use composite * for chains)
|
||||
QVERIFY(!isValidPrimitivePtrTarget(NodeKind::Pointer32));
|
||||
QVERIFY(!isValidPrimitivePtrTarget(NodeKind::Pointer64));
|
||||
|
||||
// Function pointers → NOT valid
|
||||
QVERIFY(!isValidPrimitivePtrTarget(NodeKind::FuncPtr32));
|
||||
QVERIFY(!isValidPrimitivePtrTarget(NodeKind::FuncPtr64));
|
||||
|
||||
// Containers → NOT valid
|
||||
QVERIFY(!isValidPrimitivePtrTarget(NodeKind::Struct));
|
||||
QVERIFY(!isValidPrimitivePtrTarget(NodeKind::Array));
|
||||
|
||||
// Value types → valid
|
||||
QVERIFY(isValidPrimitivePtrTarget(NodeKind::Int32));
|
||||
QVERIFY(isValidPrimitivePtrTarget(NodeKind::UInt64));
|
||||
QVERIFY(isValidPrimitivePtrTarget(NodeKind::Float));
|
||||
QVERIFY(isValidPrimitivePtrTarget(NodeKind::Double));
|
||||
QVERIFY(isValidPrimitivePtrTarget(NodeKind::Bool));
|
||||
QVERIFY(isValidPrimitivePtrTarget(NodeKind::Vec3));
|
||||
QVERIFY(isValidPrimitivePtrTarget(NodeKind::UTF8));
|
||||
}
|
||||
|
||||
// ── hex64* falls back to void* ──
|
||||
|
||||
void testHex64StarFallsBackToVoidPointer() {
|
||||
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);
|
||||
uint64_t xNodeId = doc->tree.nodes[xIdx].id;
|
||||
|
||||
// Build a TypeEntry for hex64
|
||||
TypeEntry hexEntry;
|
||||
hexEntry.entryKind = TypeEntry::Primitive;
|
||||
hexEntry.primitiveKind = NodeKind::Hex64;
|
||||
hexEntry.displayName = "hex64";
|
||||
|
||||
// Apply it with pointer modifier (fullText = "hex64*")
|
||||
ctrl->applyTypePopupResult(TypePopupMode::FieldType, xIdx,
|
||||
hexEntry, QStringLiteral("hex64*"));
|
||||
QApplication::processEvents();
|
||||
|
||||
// Should be a void pointer: Pointer64, ptrDepth=0, refId=0
|
||||
int idx = doc->tree.indexOfId(xNodeId);
|
||||
QVERIFY(idx >= 0);
|
||||
QCOMPARE(doc->tree.nodes[idx].kind, NodeKind::Pointer64);
|
||||
QCOMPARE(doc->tree.nodes[idx].ptrDepth, 0);
|
||||
QCOMPARE(doc->tree.nodes[idx].refId, uint64_t(0));
|
||||
|
||||
delete ctrl;
|
||||
delete splitter;
|
||||
delete doc;
|
||||
}
|
||||
|
||||
void testHex8StarFallsBackToVoidPointer() {
|
||||
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();
|
||||
|
||||
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);
|
||||
uint64_t xNodeId = doc->tree.nodes[xIdx].id;
|
||||
|
||||
TypeEntry hexEntry;
|
||||
hexEntry.entryKind = TypeEntry::Primitive;
|
||||
hexEntry.primitiveKind = NodeKind::Hex8;
|
||||
hexEntry.displayName = "hex8";
|
||||
|
||||
ctrl->applyTypePopupResult(TypePopupMode::FieldType, xIdx,
|
||||
hexEntry, QStringLiteral("hex8*"));
|
||||
QApplication::processEvents();
|
||||
|
||||
int idx = doc->tree.indexOfId(xNodeId);
|
||||
QVERIFY(idx >= 0);
|
||||
QCOMPARE(doc->tree.nodes[idx].kind, NodeKind::Pointer64);
|
||||
QCOMPARE(doc->tree.nodes[idx].ptrDepth, 0);
|
||||
QCOMPARE(doc->tree.nodes[idx].refId, uint64_t(0));
|
||||
|
||||
delete ctrl;
|
||||
delete splitter;
|
||||
delete doc;
|
||||
}
|
||||
|
||||
void testPtr64StarFallsBackToVoidPointer() {
|
||||
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();
|
||||
|
||||
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);
|
||||
uint64_t xNodeId = doc->tree.nodes[xIdx].id;
|
||||
|
||||
TypeEntry ptrEntry;
|
||||
ptrEntry.entryKind = TypeEntry::Primitive;
|
||||
ptrEntry.primitiveKind = NodeKind::Pointer64;
|
||||
ptrEntry.displayName = "ptr64";
|
||||
|
||||
ctrl->applyTypePopupResult(TypePopupMode::FieldType, xIdx,
|
||||
ptrEntry, QStringLiteral("ptr64*"));
|
||||
QApplication::processEvents();
|
||||
|
||||
int idx = doc->tree.indexOfId(xNodeId);
|
||||
QVERIFY(idx >= 0);
|
||||
QCOMPARE(doc->tree.nodes[idx].kind, NodeKind::Pointer64);
|
||||
QCOMPARE(doc->tree.nodes[idx].ptrDepth, 0);
|
||||
QCOMPARE(doc->tree.nodes[idx].refId, uint64_t(0));
|
||||
|
||||
delete ctrl;
|
||||
delete splitter;
|
||||
delete doc;
|
||||
}
|
||||
|
||||
// ── Valid primitive pointers still work ──
|
||||
|
||||
void testInt32StarStillCreatesPrimitivePointer() {
|
||||
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();
|
||||
|
||||
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);
|
||||
uint64_t xNodeId = doc->tree.nodes[xIdx].id;
|
||||
|
||||
TypeEntry intEntry;
|
||||
intEntry.entryKind = TypeEntry::Primitive;
|
||||
intEntry.primitiveKind = NodeKind::Int32;
|
||||
intEntry.displayName = "int32_t";
|
||||
|
||||
ctrl->applyTypePopupResult(TypePopupMode::FieldType, xIdx,
|
||||
intEntry, QStringLiteral("int32_t*"));
|
||||
QApplication::processEvents();
|
||||
|
||||
int idx = doc->tree.indexOfId(xNodeId);
|
||||
QVERIFY(idx >= 0);
|
||||
QCOMPARE(doc->tree.nodes[idx].kind, NodeKind::Pointer64);
|
||||
QCOMPARE(doc->tree.nodes[idx].ptrDepth, 1);
|
||||
QCOMPARE(doc->tree.nodes[idx].elementKind, NodeKind::Int32);
|
||||
QCOMPARE(doc->tree.nodes[idx].refId, uint64_t(0));
|
||||
|
||||
delete ctrl;
|
||||
delete splitter;
|
||||
delete doc;
|
||||
}
|
||||
|
||||
void testDoubleDoubleStarStillCreatesPrimitivePointer() {
|
||||
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();
|
||||
|
||||
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);
|
||||
uint64_t xNodeId = doc->tree.nodes[xIdx].id;
|
||||
|
||||
TypeEntry dblEntry;
|
||||
dblEntry.entryKind = TypeEntry::Primitive;
|
||||
dblEntry.primitiveKind = NodeKind::Double;
|
||||
dblEntry.displayName = "double";
|
||||
|
||||
ctrl->applyTypePopupResult(TypePopupMode::FieldType, xIdx,
|
||||
dblEntry, QStringLiteral("double**"));
|
||||
QApplication::processEvents();
|
||||
|
||||
int idx = doc->tree.indexOfId(xNodeId);
|
||||
QVERIFY(idx >= 0);
|
||||
QCOMPARE(doc->tree.nodes[idx].kind, NodeKind::Pointer64);
|
||||
QCOMPARE(doc->tree.nodes[idx].ptrDepth, 2);
|
||||
QCOMPARE(doc->tree.nodes[idx].elementKind, NodeKind::Double);
|
||||
QCOMPARE(doc->tree.nodes[idx].refId, uint64_t(0));
|
||||
|
||||
delete ctrl;
|
||||
delete splitter;
|
||||
delete doc;
|
||||
}
|
||||
|
||||
// ── Defense: compose/format treat invalid ptrDepth as void* ──
|
||||
|
||||
void testComposeShowsVoidPtrForHexPtrDepth() {
|
||||
// If a node somehow has ptrDepth>0 with hex elementKind
|
||||
// (e.g. from old JSON), compose should show "void*" not "hex64*"
|
||||
NodeTree tree;
|
||||
tree.baseAddress = 0x1000;
|
||||
|
||||
Node root;
|
||||
root.kind = NodeKind::Struct;
|
||||
root.name = "Test";
|
||||
root.structTypeName = "Test";
|
||||
root.parentId = 0;
|
||||
tree.addNode(root);
|
||||
uint64_t rootId = tree.nodes[0].id;
|
||||
|
||||
Node ptr;
|
||||
ptr.kind = NodeKind::Pointer64;
|
||||
ptr.name = "badPtr";
|
||||
ptr.parentId = rootId;
|
||||
ptr.offset = 0;
|
||||
ptr.ptrDepth = 1;
|
||||
ptr.elementKind = NodeKind::Hex64; // invalid target
|
||||
tree.addNode(ptr);
|
||||
|
||||
QByteArray buf(0x100, '\0');
|
||||
BufferProvider prov(buf);
|
||||
|
||||
ComposeResult result = compose(tree, prov);
|
||||
|
||||
// The composed text should NOT contain "hex64*" — the invalid target
|
||||
// should fall through to normal void pointer display
|
||||
QVERIFY2(!result.text.contains("hex64*"),
|
||||
qPrintable("Should not show 'hex64*', got: " + result.text));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestTypeSelector)
|
||||
|
||||
Reference in New Issue
Block a user