mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Unified type popup: explicit TypeEntry model, modifier toggles, section headers
- Replace sentinel-id scheme (kPrimBase) with TypeEntry::Kind discriminant - Merge showTypeSelectorPopup + showTypePickerPopup into single showTypePopup - Merge applyTypePickerResult into applyTypePopupResult matching on entryKind - Add modifier toggle buttons (plain, *, **, [n]) with array count input - Add section headers (primitives / project types) with dim centered styling - Root mode shows project types first; non-Root sorts same-size primitives first - Remove popup outside border, flat "Create new type" button - Add parseTypeSpec parser, update tests with new TypeEntry API
This commit is contained in:
@@ -2,14 +2,19 @@
|
||||
#include <QtTest/QSignalSpy>
|
||||
#include <QApplication>
|
||||
#include <QSplitter>
|
||||
#include <QElapsedTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <QToolButton>
|
||||
#include <QLineEdit>
|
||||
#include <QListView>
|
||||
#include <QStringListModel>
|
||||
#include <Qsci/qsciscintilla.h>
|
||||
#include "controller.h"
|
||||
#include "typeselectorpopup.h"
|
||||
#include "themes/thememanager.h"
|
||||
#include "core.h"
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
Q_DECLARE_METATYPE(uint64_t)
|
||||
#endif
|
||||
Q_DECLARE_METATYPE(rcx::TypeEntry)
|
||||
|
||||
using namespace rcx;
|
||||
|
||||
@@ -49,9 +54,7 @@ class TestTypeSelector : public QObject {
|
||||
|
||||
private slots:
|
||||
void initTestCase() {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
qRegisterMetaType<uint64_t>("uint64_t");
|
||||
#endif
|
||||
qRegisterMetaType<TypeEntry>("TypeEntry");
|
||||
}
|
||||
|
||||
// ── Chevron span detection ──
|
||||
@@ -89,6 +92,112 @@ private slots:
|
||||
QCOMPARE(text.mid(rootName.start, rootName.end - rootName.start).trimmed(), QString("Alpha"));
|
||||
}
|
||||
|
||||
// ── Benchmark: warmUp() + cached reuse vs cold new/delete ──
|
||||
|
||||
void benchmarkPopupOpen() {
|
||||
auto makeComposite = [](uint64_t id, const QString& name, const QString& kw) {
|
||||
TypeEntry e;
|
||||
e.entryKind = TypeEntry::Composite;
|
||||
e.structId = id;
|
||||
e.displayName = name;
|
||||
e.classKeyword = kw;
|
||||
return e;
|
||||
};
|
||||
QVector<TypeEntry> types;
|
||||
types.append(makeComposite(1, "Alpha", "struct"));
|
||||
types.append(makeComposite(2, "Bravo", "struct"));
|
||||
types.append(makeComposite(3, "Charlie", "struct"));
|
||||
types.append(makeComposite(4, "Delta", "class"));
|
||||
|
||||
TypeEntry cur1 = makeComposite(1, "Alpha", "struct");
|
||||
TypeEntry cur2 = makeComposite(2, "Bravo", "struct");
|
||||
|
||||
QFont font("Consolas", 12);
|
||||
font.setFixedPitch(true);
|
||||
|
||||
auto ms = [](qint64 ns) { return QString::number(ns / 1000000.0, 'f', 2); };
|
||||
|
||||
// --- Measure cold path: new popup, first show ever ---
|
||||
{
|
||||
QElapsedTimer total;
|
||||
total.start();
|
||||
auto* popup = new TypeSelectorPopup();
|
||||
popup->setFont(font);
|
||||
popup->setTypes(types, &cur1);
|
||||
popup->popup(QPoint(100, 100));
|
||||
QApplication::processEvents();
|
||||
qint64 tCold = total.nsecsElapsed();
|
||||
popup->hide();
|
||||
QApplication::processEvents();
|
||||
|
||||
qDebug() << "";
|
||||
qDebug().noquote() << QString("=== COLD (new popup, no warmUp) ===");
|
||||
qDebug().noquote() << QString(" Total: %1 ms").arg(ms(tCold));
|
||||
|
||||
// --- Measure cached reuse of same instance ---
|
||||
{
|
||||
QElapsedTimer t2;
|
||||
t2.start();
|
||||
popup->setTypes(types, &cur2);
|
||||
popup->popup(QPoint(100, 100));
|
||||
QApplication::processEvents();
|
||||
qint64 tReuse = t2.nsecsElapsed();
|
||||
popup->hide();
|
||||
QApplication::processEvents();
|
||||
|
||||
qDebug() << "";
|
||||
qDebug().noquote() << QString("=== WARM (reuse same popup) ===");
|
||||
qDebug().noquote() << QString(" Total: %1 ms").arg(ms(tReuse));
|
||||
}
|
||||
|
||||
delete popup;
|
||||
}
|
||||
|
||||
// --- Measure warmUp() approach ---
|
||||
{
|
||||
QElapsedTimer tWarmup;
|
||||
tWarmup.start();
|
||||
auto* popup2 = new TypeSelectorPopup();
|
||||
popup2->warmUp();
|
||||
qint64 tWarmMs = tWarmup.nsecsElapsed();
|
||||
|
||||
qDebug() << "";
|
||||
qDebug().noquote() << QString("=== warmUp() cost (constructor + hidden show/hide) ===");
|
||||
qDebug().noquote() << QString(" Total: %1 ms").arg(ms(tWarmMs));
|
||||
|
||||
// First user-visible show after warmUp
|
||||
QElapsedTimer t3;
|
||||
t3.start();
|
||||
popup2->setFont(font);
|
||||
popup2->setTypes(types, &cur1);
|
||||
popup2->popup(QPoint(100, 100));
|
||||
QApplication::processEvents();
|
||||
qint64 tFirst = t3.nsecsElapsed();
|
||||
popup2->hide();
|
||||
QApplication::processEvents();
|
||||
|
||||
qDebug() << "";
|
||||
qDebug().noquote() << QString("=== FIRST visible show after warmUp() ===");
|
||||
qDebug().noquote() << QString(" Total: %1 ms").arg(ms(tFirst));
|
||||
|
||||
// Second show (fully warm)
|
||||
QElapsedTimer t4;
|
||||
t4.start();
|
||||
popup2->setTypes(types, &cur2);
|
||||
popup2->popup(QPoint(100, 100));
|
||||
QApplication::processEvents();
|
||||
qint64 tSecond = t4.nsecsElapsed();
|
||||
popup2->hide();
|
||||
QApplication::processEvents();
|
||||
|
||||
qDebug() << "";
|
||||
qDebug().noquote() << QString("=== SECOND visible show after warmUp() ===");
|
||||
qDebug().noquote() << QString(" Total: %1 ms").arg(ms(tSecond));
|
||||
|
||||
delete popup2;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Popup data model ──
|
||||
|
||||
void testPopupListsRootStructs() {
|
||||
@@ -98,8 +207,12 @@ private slots:
|
||||
QVector<TypeEntry> types;
|
||||
for (const auto& n : tree.nodes) {
|
||||
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
||||
types.append({n.id, n.structTypeName.isEmpty() ? n.name : n.structTypeName,
|
||||
n.resolvedClassKeyword()});
|
||||
TypeEntry e;
|
||||
e.entryKind = TypeEntry::Composite;
|
||||
e.structId = n.id;
|
||||
e.displayName = n.structTypeName.isEmpty() ? n.name : n.structTypeName;
|
||||
e.classKeyword = n.resolvedClassKeyword();
|
||||
types.append(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,14 +225,28 @@ private slots:
|
||||
|
||||
void testPopupSignals() {
|
||||
TypeSelectorPopup popup;
|
||||
popup.setTypes({{1, "A", "struct"}, {2, "B", "struct"}}, 1);
|
||||
|
||||
TypeEntry eA;
|
||||
eA.entryKind = TypeEntry::Composite;
|
||||
eA.structId = 1;
|
||||
eA.displayName = "A";
|
||||
eA.classKeyword = "struct";
|
||||
TypeEntry eB;
|
||||
eB.entryKind = TypeEntry::Composite;
|
||||
eB.structId = 2;
|
||||
eB.displayName = "B";
|
||||
eB.classKeyword = "struct";
|
||||
QVector<TypeEntry> types;
|
||||
types.append(eA);
|
||||
types.append(eB);
|
||||
popup.setTypes(types, &eA);
|
||||
|
||||
QSignalSpy typeSpy(&popup, &TypeSelectorPopup::typeSelected);
|
||||
QSignalSpy createSpy(&popup, &TypeSelectorPopup::createNewTypeRequested);
|
||||
|
||||
emit popup.typeSelected(2, QStringLiteral("B"));
|
||||
emit popup.typeSelected(eB, QStringLiteral("B"));
|
||||
QCOMPARE(typeSpy.count(), 1);
|
||||
QCOMPARE(typeSpy.at(0).at(0).toULongLong(), (uint64_t)2);
|
||||
// Verify the entry came through — check the fullText (second arg)
|
||||
QCOMPARE(typeSpy.at(0).at(1).toString(), QStringLiteral("B"));
|
||||
|
||||
emit popup.createNewTypeRequested();
|
||||
@@ -227,6 +354,85 @@ private slots:
|
||||
delete splitter;
|
||||
delete doc;
|
||||
}
|
||||
|
||||
// ── parseTypeSpec tests ──
|
||||
|
||||
void testParseTypeSpecPlain() {
|
||||
TypeSpec spec = parseTypeSpec("int32_t");
|
||||
QCOMPARE(spec.baseName, QString("int32_t"));
|
||||
QVERIFY(!spec.isPointer);
|
||||
QCOMPARE(spec.arrayCount, 0);
|
||||
}
|
||||
|
||||
void testParseTypeSpecArray() {
|
||||
TypeSpec spec = parseTypeSpec("int32_t[10]");
|
||||
QCOMPARE(spec.baseName, QString("int32_t"));
|
||||
QVERIFY(!spec.isPointer);
|
||||
QCOMPARE(spec.arrayCount, 10);
|
||||
}
|
||||
|
||||
void testParseTypeSpecPointer() {
|
||||
TypeSpec spec = parseTypeSpec("Ball*");
|
||||
QCOMPARE(spec.baseName, QString("Ball"));
|
||||
QVERIFY(spec.isPointer);
|
||||
QCOMPARE(spec.arrayCount, 0);
|
||||
}
|
||||
|
||||
void testParseTypeSpecDoublePointer() {
|
||||
TypeSpec spec = parseTypeSpec("Ball**");
|
||||
QCOMPARE(spec.baseName, QString("Ball"));
|
||||
QVERIFY(spec.isPointer);
|
||||
}
|
||||
|
||||
void testParseTypeSpecEmpty() {
|
||||
TypeSpec spec = parseTypeSpec("");
|
||||
QVERIFY(spec.baseName.isEmpty());
|
||||
QVERIFY(!spec.isPointer);
|
||||
QCOMPARE(spec.arrayCount, 0);
|
||||
}
|
||||
|
||||
void testParseTypeSpecWhitespace() {
|
||||
TypeSpec spec = parseTypeSpec(" Ball * ");
|
||||
// trimmed → "Ball *", ends with '*'
|
||||
QCOMPARE(spec.baseName, QString("Ball"));
|
||||
QVERIFY(spec.isPointer);
|
||||
}
|
||||
|
||||
void testParseTypeSpecArrayZero() {
|
||||
// [0] parses baseName but arrayCount stays 0 (invalid count)
|
||||
TypeSpec spec = parseTypeSpec("int32_t[0]");
|
||||
QCOMPARE(spec.baseName, QString("int32_t"));
|
||||
QCOMPARE(spec.arrayCount, 0);
|
||||
}
|
||||
|
||||
// ── Section headers in filtered list ──
|
||||
|
||||
void testSectionHeadersPresent() {
|
||||
TypeSelectorPopup popup;
|
||||
|
||||
// Build entries with both primitives and composites
|
||||
QVector<TypeEntry> types;
|
||||
TypeEntry prim;
|
||||
prim.entryKind = TypeEntry::Primitive;
|
||||
prim.primitiveKind = NodeKind::Int32;
|
||||
prim.displayName = "int32_t";
|
||||
types.append(prim);
|
||||
|
||||
TypeEntry comp;
|
||||
comp.entryKind = TypeEntry::Composite;
|
||||
comp.structId = 42;
|
||||
comp.displayName = "MyStruct";
|
||||
comp.classKeyword = "struct";
|
||||
types.append(comp);
|
||||
|
||||
popup.setTypes(types);
|
||||
// After setTypes, the internal filtered list should have section headers
|
||||
// We can verify this indirectly by checking the model row count
|
||||
// (should be > 2 due to section headers)
|
||||
auto* listView = popup.findChild<QListView*>();
|
||||
QVERIFY(listView);
|
||||
QVERIFY(listView->model()->rowCount() > 2);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestTypeSelector)
|
||||
|
||||
Reference in New Issue
Block a user