feat: Vergilius-style C++ generator, struct type click fix, item view highlight fix

Rewrite C++ generator for Vergilius-style output: inline anonymous
structs/unions, reference opaque types by name with struct keyword
prefix, size comments, aligned offset comments, no anon_ stubs.

Fix struct type name not clickable in editor headers (headerTypeNameSpan
assumed "struct TYPENAME" format but named structs use bare name).

Add static_assert toggle in Options > Generator, default off.

Fix item view highlight bleed: patch PE_PanelItemViewRow to use
theme.hover so row background matches CE_ItemViewItem.
This commit is contained in:
IChooseYou
2026-02-26 08:21:15 -07:00
committed by IChooseYou
parent 52f751e751
commit 1465e7fbed
9 changed files with 362 additions and 171 deletions

View File

@@ -1468,39 +1468,35 @@ static ColumnSpan headerNameSpan(const LineMeta& lm, const QString& lineText) {
}
// Type name span for struct headers (not arrays)
// Format: "struct TYPENAME NAME {" or collapsed variants
// For "struct NAME {" (no typename), returns invalid span
// Named structs format as: "_MMPTE OriginalPte {" (type column = just the name)
// Anonymous structs format as: "union {" or "struct {" (no clickable type)
static ColumnSpan headerTypeNameSpan(const LineMeta& lm, const QString& lineText) {
if (lm.lineKind != LineKind::Header) return {};
if (lm.isArrayHeader) return {}; // Arrays use arrayHeaderTypeSpan instead
if (lm.isArrayHeader) return {};
int ind = kFoldCol + lm.depth * 3;
int typeW = lm.effectiveTypeW;
int typeEnd = ind + typeW;
// Clamp to actual line content
if (typeEnd > lineText.size()) typeEnd = lineText.size();
// Extract the type column text and check if it has a typename
// Format: "struct" or "struct TYPENAME"
QString typeCol = lineText.mid(ind, typeEnd - ind).trimmed();
if (typeCol.isEmpty()) return {};
// Find first space (after "struct")
int firstSpace = typeCol.indexOf(' ');
if (firstSpace < 0) return {}; // Just "struct", no typename
// Anonymous structs use bare keywords — not clickable
static const QStringList kKeywords = {
QStringLiteral("struct"), QStringLiteral("union"), QStringLiteral("class")
};
if (kKeywords.contains(typeCol)) return {};
// If there's content after "struct ", that's the typename
QString typename_ = typeCol.mid(firstSpace + 1).trimmed();
if (typename_.isEmpty()) return {};
// Named struct: entire type column is the type name (e.g. "_MMPTE")
// Find the actual text bounds within the padded column
int start = ind;
while (start < typeEnd && lineText[start] == ' ') start++;
int end = start;
while (end < typeEnd && lineText[end] != ' ') end++;
if (end <= start) return {};
// Return span of the typename within the type column
int typenameStart = ind + firstSpace + 1;
// Find where the typename actually ends (skip padding)
int typenameEnd = typenameStart;
while (typenameEnd < typeEnd && lineText[typenameEnd] != ' ')
typenameEnd++;
return {typenameStart, typenameEnd, true};
return {start, end, true};
}
// Type span for array headers: "int32_t[10]" in "int32_t[10] positions {"

View File

@@ -68,6 +68,7 @@ struct GenContext {
QString output;
int padCounter = 0;
const QHash<NodeKind, QString>* typeAliases = nullptr;
bool emitAsserts = false;
QString uniquePadName() {
return QStringLiteral("_pad%1").arg(padCounter++, 4, 16, QChar('0'));
@@ -104,64 +105,70 @@ static QString offsetComment(int offset) {
return QString(kCommentMarker) + QStringLiteral("// 0x%1").arg(QString::number(offset, 16).toUpper());
}
static QString emitField(GenContext& ctx, const Node& node) {
static QString indent(int depth) {
return QString(depth * 4, ' ');
}
static QString emitField(GenContext& ctx, const Node& node, int depth, int baseOffset) {
const NodeTree& tree = ctx.tree;
QString ind = indent(depth);
QString name = sanitizeIdent(node.name.isEmpty()
? QStringLiteral("field_%1").arg(node.offset, 2, 16, QChar('0'))
: node.name);
QString oc = offsetComment(node.offset);
QString oc = offsetComment(baseOffset + node.offset);
switch (node.kind) {
case NodeKind::Vec2:
return QStringLiteral(" %1 %2[2];").arg(ctx.cType(NodeKind::Float), name) + oc;
return ind + QStringLiteral("%1 %2[2];").arg(ctx.cType(NodeKind::Float), name) + oc;
case NodeKind::Vec3:
return QStringLiteral(" %1 %2[3];").arg(ctx.cType(NodeKind::Float), name) + oc;
return ind + QStringLiteral("%1 %2[3];").arg(ctx.cType(NodeKind::Float), name) + oc;
case NodeKind::Vec4:
return QStringLiteral(" %1 %2[4];").arg(ctx.cType(NodeKind::Float), name) + oc;
return ind + QStringLiteral("%1 %2[4];").arg(ctx.cType(NodeKind::Float), name) + oc;
case NodeKind::Mat4x4:
return QStringLiteral(" %1 %2[4][4];").arg(ctx.cType(NodeKind::Float), name) + oc;
return ind + QStringLiteral("%1 %2[4][4];").arg(ctx.cType(NodeKind::Float), name) + oc;
case NodeKind::UTF8:
return QStringLiteral(" %1 %2[%3];").arg(ctx.cType(NodeKind::UTF8), name).arg(node.strLen) + oc;
return ind + QStringLiteral("%1 %2[%3];").arg(ctx.cType(NodeKind::UTF8), name).arg(node.strLen) + oc;
case NodeKind::UTF16:
return QStringLiteral(" %1 %2[%3];").arg(ctx.cType(NodeKind::UTF16), name).arg(node.strLen) + oc;
return ind + QStringLiteral("%1 %2[%3];").arg(ctx.cType(NodeKind::UTF16), name).arg(node.strLen) + oc;
case NodeKind::Pointer32: {
if (node.refId != 0) {
int refIdx = tree.indexOfId(node.refId);
if (refIdx >= 0) {
QString target = ctx.structName(tree.nodes[refIdx]);
return QStringLiteral(" %1 %2;").arg(ctx.cType(NodeKind::Pointer32), name) +
offsetComment(node.offset).replace(QStringLiteral("//"), QStringLiteral("// -> %1*").arg(target));
return ind + QStringLiteral("struct %1* %2;").arg(target, name) + oc;
}
}
return QStringLiteral(" %1 %2;").arg(ctx.cType(NodeKind::Pointer32), name) + oc;
return ind + QStringLiteral("%1 %2;").arg(ctx.cType(NodeKind::Pointer32), name) + oc;
}
case NodeKind::Pointer64: {
if (node.refId != 0) {
int refIdx = tree.indexOfId(node.refId);
if (refIdx >= 0) {
QString target = ctx.structName(tree.nodes[refIdx]);
return QStringLiteral(" %1* %2;").arg(target, name) + oc;
return ind + QStringLiteral("struct %1* %2;").arg(target, name) + oc;
}
}
return QStringLiteral(" void* %1;").arg(name) + oc;
return ind + QStringLiteral("void* %1;").arg(name) + oc;
}
case NodeKind::FuncPtr32:
return QStringLiteral(" void (*%1)();").arg(name) + oc;
return ind + QStringLiteral("void (*%1)();").arg(name) + oc;
case NodeKind::FuncPtr64:
return QStringLiteral(" void (*%1)();").arg(name) + oc;
return ind + QStringLiteral("void (*%1)();").arg(name) + oc;
default:
return QStringLiteral(" %1 %2;").arg(ctx.cType(node.kind), name) + oc;
return ind + QStringLiteral("%1 %2;").arg(ctx.cType(node.kind), name) + oc;
}
}
// ── Emit struct body (fields + padding) ──
// ── Emit struct body (fields + padding) — Vergilius-style ──
static void emitStructBody(GenContext& ctx, uint64_t structId) {
static void emitStructBody(GenContext& ctx, uint64_t structId,
bool isUnion, int depth, int baseOffset) {
const NodeTree& tree = ctx.tree;
int idx = tree.indexOfId(structId);
if (idx < 0) return;
int structSize = tree.structSpan(structId, &ctx.childMap);
QString ind = indent(depth);
QVector<int> children = ctx.childMap.value(structId);
std::sort(children.begin(), children.end(), [&](int a, int b) {
@@ -169,13 +176,12 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
});
// Helper: emit a padding/hex run as a single collapsed byte array
auto emitPadRun = [&](int offset, int size) {
auto emitPadRun = [&](int relOffset, int size) {
if (size <= 0) return;
ctx.output += QStringLiteral(" %1 %2[0x%3];%4\n")
.arg(QStringLiteral("uint8_t"))
ctx.output += ind + QStringLiteral("uint8_t %1[0x%2];%3\n")
.arg(ctx.uniquePadName())
.arg(QString::number(size, 16).toUpper())
.arg(offsetComment(offset));
.arg(offsetComment(baseOffset + relOffset));
};
int cursor = 0;
@@ -189,13 +195,15 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
else
childSize = child.byteSize();
// Gap before this field
if (child.offset > cursor)
emitPadRun(cursor, child.offset - cursor);
else if (child.offset < cursor)
ctx.output += QStringLiteral(" // WARNING: overlap at offset 0x%1 (previous field ends at 0x%2)\n")
.arg(QString::number(child.offset, 16).toUpper())
.arg(QString::number(cursor, 16).toUpper());
// Gap/overlap handling (skip for unions)
if (!isUnion) {
if (child.offset > cursor)
emitPadRun(cursor, child.offset - cursor);
else if (child.offset < cursor)
ctx.output += ind + QStringLiteral("// WARNING: overlap at offset 0x%1 (previous field ends at 0x%2)\n")
.arg(QString::number(baseOffset + child.offset, 16).toUpper())
.arg(QString::number(baseOffset + cursor, 16).toUpper());
}
// Collapse consecutive hex nodes into a single padding array
if (isHexNode(child.kind)) {
@@ -206,8 +214,7 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
const Node& next = tree.nodes[children[j]];
if (!isHexNode(next.kind)) break;
int nextSize = next.byteSize();
// Allow gaps within the run (they become part of the pad)
if (next.offset < runEnd) break; // overlap — stop merging
if (next.offset < runEnd) break;
runEnd = next.offset + nextSize;
j++;
}
@@ -219,10 +226,31 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
// Emit the field
if (child.kind == NodeKind::Struct) {
emitStruct(ctx, child.id);
QString typeName = ctx.structName(child);
QString fieldName = sanitizeIdent(child.name);
ctx.output += QStringLiteral(" %1 %2;%3\n").arg(typeName, fieldName, offsetComment(child.offset));
bool isAnonymous = child.structTypeName.isEmpty();
if (isAnonymous) {
// Inline anonymous struct/union
QString kw = child.resolvedClassKeyword();
ctx.output += ind + kw + QStringLiteral("\n");
ctx.output += ind + QStringLiteral("{\n");
bool childIsUnion = (kw == QStringLiteral("union"));
emitStructBody(ctx, child.id, childIsUnion, depth + 1,
baseOffset + child.offset);
QString fieldName = child.name.isEmpty()
? QString() : QStringLiteral(" ") + sanitizeIdent(child.name);
ctx.output += ind + QStringLiteral("}") + fieldName + QStringLiteral(";")
+ offsetComment(baseOffset + child.offset) + QStringLiteral("\n");
} else {
// Named struct — reference by name with struct keyword prefix
QString kw = child.resolvedClassKeyword();
if (kw == QStringLiteral("enum") && child.enumMembers.isEmpty())
kw = QStringLiteral("struct");
QString typeName = sanitizeIdent(child.structTypeName);
QString fieldName = sanitizeIdent(child.name);
ctx.output += ind + kw + QStringLiteral(" ") + typeName
+ QStringLiteral(" ") + fieldName + QStringLiteral(";")
+ offsetComment(baseOffset + child.offset) + QStringLiteral("\n");
}
} else if (child.kind == NodeKind::Array) {
QVector<int> arrayKids = ctx.childMap.value(child.id);
bool hasStructChild = false;
@@ -231,7 +259,6 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
for (int ak : arrayKids) {
if (tree.nodes[ak].kind == NodeKind::Struct) {
hasStructChild = true;
emitStruct(ctx, tree.nodes[ak].id);
elemTypeName = ctx.structName(tree.nodes[ak]);
break;
}
@@ -239,14 +266,16 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
QString fieldName = sanitizeIdent(child.name);
if (hasStructChild && !elemTypeName.isEmpty()) {
ctx.output += QStringLiteral(" %1 %2[%3];%4\n")
.arg(elemTypeName, fieldName).arg(child.arrayLen).arg(offsetComment(child.offset));
ctx.output += ind + QStringLiteral("struct %1 %2[%3];%4\n")
.arg(elemTypeName, fieldName).arg(child.arrayLen)
.arg(offsetComment(baseOffset + child.offset));
} else {
ctx.output += QStringLiteral(" %1 %2[%3];%4\n")
.arg(ctx.cType(child.elementKind), fieldName).arg(child.arrayLen).arg(offsetComment(child.offset));
ctx.output += ind + QStringLiteral("%1 %2[%3];%4\n")
.arg(ctx.cType(child.elementKind), fieldName).arg(child.arrayLen)
.arg(offsetComment(baseOffset + child.offset));
}
} else {
ctx.output += emitField(ctx, child) + QStringLiteral("\n");
ctx.output += emitField(ctx, child, depth, baseOffset) + QStringLiteral("\n");
}
int childEnd = child.offset + childSize;
@@ -254,12 +283,12 @@ static void emitStructBody(GenContext& ctx, uint64_t structId) {
i++;
}
// Tail padding
if (cursor < structSize)
// Tail padding (skip for unions)
if (!isUnion && cursor < structSize)
emitPadRun(cursor, structSize - cursor);
}
// ── Emit a complete struct definition ──
// ── Emit a complete top-level struct definition (Vergilius-style) ──
static void emitStruct(GenContext& ctx, uint64_t structId) {
if (ctx.emittedIds.contains(structId)) return;
@@ -275,19 +304,12 @@ static void emitStruct(GenContext& ctx, uint64_t structId) {
return;
}
// For arrays, we don't emit a top-level struct — the array itself
// is a field inside its parent. But we do emit struct element types.
if (node.kind == NodeKind::Array) {
QVector<int> kids = ctx.childMap.value(structId);
for (int ki : kids) {
if (ctx.tree.nodes[ki].kind == NodeKind::Struct)
emitStruct(ctx, ctx.tree.nodes[ki].id);
}
ctx.visiting.remove(structId);
return;
}
// Deduplicate by struct type name (different nodes may share the same type)
// Deduplicate by struct type name
QString typeName = ctx.structName(node);
if (ctx.emittedTypeNames.contains(typeName)) {
ctx.emittedIds.insert(structId);
@@ -295,34 +317,6 @@ static void emitStruct(GenContext& ctx, uint64_t structId) {
return;
}
// Emit nested struct types first (dependency order)
QVector<int> children = ctx.childMap.value(structId);
for (int ci : children) {
const Node& child = ctx.tree.nodes[ci];
if (child.kind == NodeKind::Struct)
emitStruct(ctx, child.id);
else if (child.kind == NodeKind::Array) {
QVector<int> arrayKids = ctx.childMap.value(child.id);
for (int ak : arrayKids) {
if (ctx.tree.nodes[ak].kind == NodeKind::Struct)
emitStruct(ctx, ctx.tree.nodes[ak].id);
}
}
// Forward-declare pointer target types if they're outside this subtree
if (child.kind == NodeKind::Pointer64 && child.refId != 0) {
int refIdx = ctx.tree.indexOfId(child.refId);
if (refIdx >= 0 && !ctx.emittedIds.contains(child.refId)
&& !ctx.forwardDeclared.contains(child.refId)) {
QString fwdName = ctx.structName(ctx.tree.nodes[refIdx]);
QString fwdKw = ctx.tree.nodes[refIdx].resolvedClassKeyword();
if (fwdKw == QStringLiteral("enum") && ctx.tree.nodes[refIdx].enumMembers.isEmpty())
fwdKw = QStringLiteral("struct");
ctx.output += QStringLiteral("%1 %2;\n").arg(fwdKw, fwdName);
ctx.forwardDeclared.insert(child.refId);
}
}
}
ctx.emittedIds.insert(structId);
ctx.emittedTypeNames.insert(typeName);
int structSize = ctx.tree.structSpan(structId, &ctx.childMap);
@@ -342,15 +336,21 @@ static void emitStruct(GenContext& ctx, uint64_t structId) {
return;
}
if (kw == QStringLiteral("enum")) kw = QStringLiteral("struct"); // enum without members: fallback
ctx.output += QStringLiteral("%1 %2 {\n").arg(kw, typeName);
if (kw == QStringLiteral("enum")) kw = QStringLiteral("struct");
emitStructBody(ctx, structId);
// Size comment (Vergilius-style)
ctx.output += QStringLiteral("//0x%1 bytes (sizeof)\n")
.arg(QString::number(structSize, 16).toUpper());
ctx.output += kw + QStringLiteral(" ") + typeName + QStringLiteral("\n{\n");
emitStructBody(ctx, structId, kw == QStringLiteral("union"), 1, 0);
ctx.output += QStringLiteral("};\n");
ctx.output += QStringLiteral("static_assert(sizeof(%1) == 0x%2, \"Size mismatch for %1\");\n\n")
.arg(typeName)
.arg(QString::number(structSize, 16).toUpper());
if (ctx.emitAsserts)
ctx.output += QStringLiteral("static_assert(sizeof(%1) == 0x%2, \"Size mismatch for %1\");\n")
.arg(typeName)
.arg(QString::number(structSize, 16).toUpper());
ctx.output += QStringLiteral("\n");
ctx.visiting.remove(structId);
}
@@ -404,14 +404,15 @@ static QString alignComments(const QString& raw) {
// ── Public API ──
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
const QHash<NodeKind, QString>* typeAliases) {
const QHash<NodeKind, QString>* typeAliases,
bool emitAsserts) {
int idx = tree.indexOfId(rootStructId);
if (idx < 0) return {};
const Node& root = tree.nodes[idx];
if (root.kind != NodeKind::Struct) return {};
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases};
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts};
ctx.output += QStringLiteral("#pragma once\n\n");
@@ -421,8 +422,9 @@ QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
}
QString renderCppAll(const NodeTree& tree,
const QHash<NodeKind, QString>* typeAliases) {
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases};
const QHash<NodeKind, QString>* typeAliases,
bool emitAsserts) {
GenContext ctx{tree, buildChildMap(tree), {}, {}, {}, {}, {}, 0, typeAliases, emitAsserts};
ctx.output += QStringLiteral("#pragma once\n\n");

View File

@@ -9,11 +9,13 @@ namespace rcx {
// Generate C++ struct definitions for a single root struct and all
// nested/referenced types reachable from it.
QString renderCpp(const NodeTree& tree, uint64_t rootStructId,
const QHash<NodeKind, QString>* typeAliases = nullptr);
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
// Generate C++ struct definitions for every root-level struct (full SDK).
QString renderCppAll(const NodeTree& tree,
const QHash<NodeKind, QString>* typeAliases = nullptr);
const QHash<NodeKind, QString>* typeAliases = nullptr,
bool emitAsserts = false);
// Null generator placeholder (returns empty string).
QString renderNull(const NodeTree& tree, uint64_t rootStructId);

View File

@@ -267,6 +267,16 @@ public:
// Transparent menu bar background (no CSS needed)
if (elem == PE_PanelMenuBar)
return;
// Item-view row background — patch Highlight so the row bg matches CE_ItemViewItem
if (elem == PE_PanelItemViewRow) {
if (auto* vi = qstyleoption_cast<const QStyleOptionViewItem*>(opt)) {
QStyleOptionViewItem patched = *vi;
patched.palette.setColor(QPalette::Highlight,
vi->palette.color(QPalette::Mid));
QProxyStyle::drawPrimitive(elem, &patched, p, w);
return;
}
}
QProxyStyle::drawPrimitive(elem, opt, p, w);
}
void drawControl(ControlElement element, const QStyleOption* opt,
@@ -1804,6 +1814,7 @@ void MainWindow::showOptionsDialog() {
current.safeMode = QSettings("Reclass", "Reclass").value("safeMode", false).toBool();
current.autoStartMcp = QSettings("Reclass", "Reclass").value("autoStartMcp", true).toBool();
current.refreshMs = QSettings("Reclass", "Reclass").value("refreshMs", 660).toInt();
current.generatorAsserts = QSettings("Reclass", "Reclass").value("generatorAsserts", false).toBool();
OptionsDialog dlg(current, this);
if (dlg.exec() != QDialog::Accepted) return; // OptionsDialog doesn't apply anything. Only apply on OK
@@ -1837,6 +1848,9 @@ void MainWindow::showOptionsDialog() {
for (auto& tab : m_tabs)
tab.ctrl->setRefreshInterval(r.refreshMs);
}
if (r.generatorAsserts != current.generatorAsserts)
QSettings("Reclass", "Reclass").setValue("generatorAsserts", r.generatorAsserts);
}
void MainWindow::setEditorFont(const QString& fontName) {
@@ -2023,11 +2037,12 @@ void MainWindow::updateRenderedView(TabState& tab, SplitPane& pane) {
// Generate text
const QHash<NodeKind, QString>* aliases =
tab.doc->typeAliases.isEmpty() ? nullptr : &tab.doc->typeAliases;
bool asserts = QSettings("Reclass", "Reclass").value("generatorAsserts", false).toBool();
QString text;
if (rootId != 0)
text = renderCpp(tab.doc->tree, rootId, aliases);
text = renderCpp(tab.doc->tree, rootId, aliases, asserts);
else
text = renderCppAll(tab.doc->tree, aliases);
text = renderCppAll(tab.doc->tree, aliases, asserts);
// Scroll restoration: save if same root, reset if different
int restoreLine = 0;
@@ -2071,7 +2086,8 @@ void MainWindow::exportCpp() {
const QHash<NodeKind, QString>* aliases =
tab->doc->typeAliases.isEmpty() ? nullptr : &tab->doc->typeAliases;
QString text = renderCppAll(tab->doc->tree, aliases);
bool asserts = QSettings("Reclass", "Reclass").value("generatorAsserts", false).toBool();
QString text = renderCppAll(tab->doc->tree, aliases, asserts);
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(this, "Export Failed",

View File

@@ -4,6 +4,7 @@
#include "generator.h"
#include "mainwindow.h"
#include <QCoreApplication>
#include <QSettings>
#include <QDebug>
#include <cstring>
@@ -1094,15 +1095,16 @@ QJsonObject McpBridge::toolUiAction(const QJsonObject& args) {
if (action == "export_cpp") {
if (!doc) return makeTextResult("No active tab", true);
const QHash<NodeKind, QString>* aliases = doc->typeAliases.isEmpty() ? nullptr : &doc->typeAliases;
bool asserts = QSettings("Reclass", "Reclass").value("generatorAsserts", false).toBool();
QString code;
if (!nodeIdStr.isEmpty()) {
// Per-struct export
uint64_t nid = nodeIdStr.toULongLong();
code = renderCpp(doc->tree, nid, aliases);
code = renderCpp(doc->tree, nid, aliases, asserts);
if (code.isEmpty())
return makeTextResult("Node not found or not a struct: " + nodeIdStr, true);
} else {
code = renderCppAll(doc->tree, aliases);
code = renderCppAll(doc->tree, aliases, asserts);
}
// Truncate if too large (64 KB limit)
if (code.size() > 65536) {

View File

@@ -170,6 +170,14 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent)
auto* generatorLayout = new QVBoxLayout(generatorPage);
generatorLayout->setContentsMargins(0, 0, 0, 0);
generatorLayout->setSpacing(8);
auto* cppGroup = new QGroupBox("C++ Header");
auto* cppLayout = new QVBoxLayout(cppGroup);
m_assertCheck = new QCheckBox("Emit static_assert size checks");
m_assertCheck->setChecked(current.generatorAsserts);
cppLayout->addWidget(m_assertCheck);
generatorLayout->addWidget(cppGroup);
generatorLayout->addStretch();
m_pages->addWidget(generatorPage); // index 2
@@ -208,6 +216,7 @@ OptionsResult OptionsDialog::result() const {
r.safeMode = m_safeModeCheck->isChecked();
r.autoStartMcp = m_autoMcpCheck->isChecked();
r.refreshMs = m_refreshSpin->value();
r.generatorAsserts = m_assertCheck->isChecked();
return r;
}

View File

@@ -18,6 +18,7 @@ struct OptionsResult {
bool safeMode = false;
bool autoStartMcp = true;
int refreshMs = 660;
bool generatorAsserts = false;
};
class OptionsDialog : public QDialog {
@@ -41,6 +42,7 @@ private:
QCheckBox* m_safeModeCheck = nullptr;
QCheckBox* m_autoMcpCheck = nullptr;
QSpinBox* m_refreshSpin = nullptr;
QCheckBox* m_assertCheck = nullptr;
// searchable keywords per leaf tree item
QHash<QTreeWidgetItem*, QStringList> m_pageKeywords;