mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
feat: simplify cmd bar keyword, add File menu class/struct/enum, remove Align Members
- Command bar shows static keyword (struct/class/enum) without dropdown or colon - Right-click keyword in cmd bar for class↔struct conversion (enum blocked) - File menu: New Class (Ctrl+N), New Struct (Ctrl+T), New Enum (Ctrl+E) - Project explorer right-click: New Class/Struct/Enum on Project node - Explorer right-click: Convert to Class/Struct on class/struct items - Remove Align Members submenu, performRealignment, computeStructAlignment - Remove screenshot code and screenshot.png Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -116,14 +116,6 @@ endforeach()
|
|||||||
|
|
||||||
include(deploy)
|
include(deploy)
|
||||||
|
|
||||||
if(TARGET deploy)
|
|
||||||
add_custom_target(screenshot ALL
|
|
||||||
COMMAND Reclass --screenshot ${CMAKE_BINARY_DIR}/screenshot.png
|
|
||||||
DEPENDS Reclass deploy
|
|
||||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
|
||||||
COMMENT "Capturing UI screenshot with class open..."
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(_combine_script "${CMAKE_BINARY_DIR}/combine_sources.cmake")
|
set(_combine_script "${CMAKE_BINARY_DIR}/combine_sources.cmake")
|
||||||
file(WRITE ${_combine_script} "
|
file(WRITE ${_combine_script} "
|
||||||
|
|||||||
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -671,7 +671,7 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Emit CommandRow as line 0 (combined: source + address + root class type + name)
|
// Emit CommandRow as line 0 (combined: source + address + root class type + name)
|
||||||
const QString cmdRowText = QStringLiteral("[\u25B8] source\u25BE \u00B7 0x0 \u00B7 struct\u25BE NoName {");
|
const QString cmdRowText = QStringLiteral("[\u25B8] source\u25BE \u00B7 0x0 \u00B7 struct NoName {");
|
||||||
{
|
{
|
||||||
LineMeta lm;
|
LineMeta lm;
|
||||||
lm.nodeIdx = -1;
|
lm.nodeIdx = -1;
|
||||||
@@ -743,20 +743,5 @@ QSet<uint64_t> NodeTree::normalizePreferDescendants(const QSet<uint64_t>& ids) c
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int NodeTree::computeStructAlignment(uint64_t structId) const {
|
|
||||||
int idx = indexOfId(structId);
|
|
||||||
if (idx < 0) return 1;
|
|
||||||
int maxAlign = 1;
|
|
||||||
QVector<int> kids = childrenOf(structId);
|
|
||||||
for (int ci : kids) {
|
|
||||||
const Node& c = nodes[ci];
|
|
||||||
if (c.kind == NodeKind::Struct || c.kind == NodeKind::Array) {
|
|
||||||
maxAlign = qMax(maxAlign, computeStructAlignment(c.id));
|
|
||||||
} else {
|
|
||||||
maxAlign = qMax(maxAlign, alignmentFor(c.kind));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maxAlign;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace rcx
|
} // namespace rcx
|
||||||
|
|||||||
@@ -203,6 +203,8 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
this, [this, editor](int line, int nodeIdx, int subLine, QPoint globalPos) {
|
this, [this, editor](int line, int nodeIdx, int subLine, QPoint globalPos) {
|
||||||
showContextMenu(editor, line, nodeIdx, subLine, globalPos);
|
showContextMenu(editor, line, nodeIdx, subLine, globalPos);
|
||||||
});
|
});
|
||||||
|
connect(editor, &RcxEditor::keywordConvertRequested,
|
||||||
|
this, &RcxController::convertRootKeyword);
|
||||||
connect(editor, &RcxEditor::nodeClicked,
|
connect(editor, &RcxEditor::nodeClicked,
|
||||||
this, [this, editor](int line, uint64_t nodeId, Qt::KeyboardModifiers mods) {
|
this, [this, editor](int line, uint64_t nodeId, Qt::KeyboardModifiers mods) {
|
||||||
handleNodeClick(editor, line, nodeId, mods);
|
handleNodeClick(editor, line, nodeId, mods);
|
||||||
@@ -729,6 +731,27 @@ void RcxController::refresh() {
|
|||||||
applySelectionOverlays();
|
applySelectionOverlays();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RcxController::convertRootKeyword(const QString& newKeyword) {
|
||||||
|
uint64_t targetId = m_viewRootId;
|
||||||
|
if (targetId == 0) {
|
||||||
|
for (const auto& n : m_doc->tree.nodes) {
|
||||||
|
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
||||||
|
targetId = n.id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (targetId == 0) return;
|
||||||
|
int idx = m_doc->tree.indexOfId(targetId);
|
||||||
|
if (idx < 0) return;
|
||||||
|
QString oldKw = m_doc->tree.nodes[idx].resolvedClassKeyword();
|
||||||
|
if (oldKw == newKeyword) return;
|
||||||
|
// Only allow class↔struct conversion
|
||||||
|
if (oldKw == QStringLiteral("enum") || newKeyword == QStringLiteral("enum")) return;
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangeClassKeyword{targetId, oldKw, newKeyword}));
|
||||||
|
}
|
||||||
|
|
||||||
void RcxController::changeNodeKind(int nodeIdx, NodeKind newKind) {
|
void RcxController::changeNodeKind(int nodeIdx, NodeKind newKind) {
|
||||||
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return;
|
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return;
|
||||||
auto& node = m_doc->tree.nodes[nodeIdx];
|
auto& node = m_doc->tree.nodes[nodeIdx];
|
||||||
@@ -1438,22 +1461,6 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Align Members submenu
|
|
||||||
if (node.kind == NodeKind::Struct) {
|
|
||||||
int curAlign = m_doc->tree.computeStructAlignment(nodeId);
|
|
||||||
auto* alignMenu = menu.addMenu(icon("symbol-ruler.svg"), "Align &Members");
|
|
||||||
static const int alignValues[] = {1, 2, 4, 8, 16, 32, 64, 128};
|
|
||||||
for (int av : alignValues) {
|
|
||||||
QString label = (av == 1)
|
|
||||||
? QStringLiteral("1 (packed)")
|
|
||||||
: QString::number(av);
|
|
||||||
auto* act = alignMenu->addAction(label, [this, nodeId, av]() {
|
|
||||||
performRealignment(nodeId, av);
|
|
||||||
});
|
|
||||||
act->setCheckable(true);
|
|
||||||
act->setChecked(av == curAlign);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.addAction(icon("files.svg"), "D&uplicate\tCtrl+D", [this, nodeId]() {
|
menu.addAction(icon("files.svg"), "D&uplicate\tCtrl+D", [this, nodeId]() {
|
||||||
@@ -1488,33 +1495,6 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
|
|
||||||
// ── Always-available actions ──
|
// ── Always-available actions ──
|
||||||
|
|
||||||
// Root struct alignment (always available if a root struct exists)
|
|
||||||
{
|
|
||||||
uint64_t rootStructId = 0;
|
|
||||||
for (const auto& n : m_doc->tree.nodes) {
|
|
||||||
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
|
||||||
rootStructId = n.id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rootStructId != 0) {
|
|
||||||
int curAlign = m_doc->tree.computeStructAlignment(rootStructId);
|
|
||||||
auto* alignMenu = menu.addMenu(icon("symbol-ruler.svg"), "Align &Members");
|
|
||||||
static const int alignValues[] = {1, 2, 4, 8, 16, 32, 64, 128};
|
|
||||||
for (int av : alignValues) {
|
|
||||||
QString label = (av == 1)
|
|
||||||
? QStringLiteral("1 (packed)")
|
|
||||||
: QString::number(av);
|
|
||||||
auto* act = alignMenu->addAction(label, [this, rootStructId, av]() {
|
|
||||||
performRealignment(rootStructId, av);
|
|
||||||
});
|
|
||||||
act->setCheckable(true);
|
|
||||||
act->setChecked(av == curAlign);
|
|
||||||
}
|
|
||||||
menu.addSeparator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.addAction(icon("diff-added.svg"), "Append 128 bytes", [this]() {
|
menu.addAction(icon("diff-added.svg"), "Append 128 bytes", [this]() {
|
||||||
uint64_t target = m_viewRootId ? m_viewRootId : 0;
|
uint64_t target = m_viewRootId ? m_viewRootId : 0;
|
||||||
m_suppressRefresh = true;
|
m_suppressRefresh = true;
|
||||||
@@ -1670,112 +1650,6 @@ void RcxController::applySelectionOverlays() {
|
|||||||
editor->applySelectionOverlay(m_selIds);
|
editor->applySelectionOverlay(m_selIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RcxController::performRealignment(uint64_t structId, int targetAlign) {
|
|
||||||
auto& tree = m_doc->tree;
|
|
||||||
int rootIdx = tree.indexOfId(structId);
|
|
||||||
if (rootIdx < 0) return;
|
|
||||||
|
|
||||||
// Gather direct children sorted by offset
|
|
||||||
QVector<int> kids = tree.childrenOf(structId);
|
|
||||||
std::sort(kids.begin(), kids.end(), [&](int a, int b) {
|
|
||||||
return tree.nodes[a].offset < tree.nodes[b].offset;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Separate into real nodes (non-hex) and hex filler nodes
|
|
||||||
struct NodeInfo { uint64_t id; int offset; int size; };
|
|
||||||
QVector<NodeInfo> realNodes;
|
|
||||||
QVector<uint64_t> hexIds;
|
|
||||||
|
|
||||||
for (int ci : kids) {
|
|
||||||
const Node& child = tree.nodes[ci];
|
|
||||||
int sz = (child.kind == NodeKind::Struct || child.kind == NodeKind::Array)
|
|
||||||
? tree.structSpan(child.id) : child.byteSize();
|
|
||||||
if (isHexNode(child.kind))
|
|
||||||
hexIds.append(child.id);
|
|
||||||
else
|
|
||||||
realNodes.append({child.id, child.offset, sz});
|
|
||||||
}
|
|
||||||
|
|
||||||
auto roundUp = [](int x, int align) -> int {
|
|
||||||
return align <= 1 ? x : ((x + align - 1) / align) * align;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compute new offsets for real nodes
|
|
||||||
struct OffChange { uint64_t id; int oldOff; int newOff; };
|
|
||||||
QVector<OffChange> offChanges;
|
|
||||||
int cursor = 0;
|
|
||||||
for (auto& rn : realNodes) {
|
|
||||||
int newOff = roundUp(cursor, targetAlign);
|
|
||||||
if (newOff != rn.offset)
|
|
||||||
offChanges.append({rn.id, rn.offset, newOff});
|
|
||||||
rn.offset = newOff; // update local copy for gap computation
|
|
||||||
cursor = newOff + rn.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute where padding is needed (gaps between consecutive nodes)
|
|
||||||
struct PadInsert { int offset; int size; };
|
|
||||||
QVector<PadInsert> padsNeeded;
|
|
||||||
|
|
||||||
for (int i = 0; i < realNodes.size(); i++) {
|
|
||||||
int gapStart = (i == 0) ? 0 : realNodes[i - 1].offset + realNodes[i - 1].size;
|
|
||||||
int gapEnd = realNodes[i].offset;
|
|
||||||
if (gapEnd > gapStart)
|
|
||||||
padsNeeded.append({gapStart, gapEnd - gapStart});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if anything actually changes
|
|
||||||
if (offChanges.isEmpty() && hexIds.isEmpty() && padsNeeded.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Apply as undoable macro
|
|
||||||
bool wasSuppressed = m_suppressRefresh;
|
|
||||||
m_suppressRefresh = true;
|
|
||||||
m_doc->undoStack.beginMacro(QStringLiteral("Realign to %1").arg(targetAlign));
|
|
||||||
|
|
||||||
// 1. Remove all existing hex filler nodes (no offset adjustments — we recompute)
|
|
||||||
for (uint64_t hid : hexIds) {
|
|
||||||
int idx = tree.indexOfId(hid);
|
|
||||||
if (idx < 0) continue;
|
|
||||||
QVector<Node> subtree;
|
|
||||||
subtree.append(tree.nodes[idx]);
|
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
|
||||||
cmd::Remove{hid, subtree, {}}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Reposition real nodes
|
|
||||||
for (const auto& oc : offChanges) {
|
|
||||||
m_doc->undoStack.push(new RcxCommand(this,
|
|
||||||
cmd::ChangeOffset{oc.id, oc.oldOff, oc.newOff}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Insert hex nodes to fill gaps (largest first for alignment)
|
|
||||||
for (const auto& pi : padsNeeded) {
|
|
||||||
int padOffset = pi.offset;
|
|
||||||
int gap = pi.size;
|
|
||||||
while (gap > 0) {
|
|
||||||
NodeKind padKind;
|
|
||||||
int padSize;
|
|
||||||
if (gap >= 8) { padKind = NodeKind::Hex64; padSize = 8; }
|
|
||||||
else if (gap >= 4) { padKind = NodeKind::Hex32; padSize = 4; }
|
|
||||||
else if (gap >= 2) { padKind = NodeKind::Hex16; padSize = 2; }
|
|
||||||
else { padKind = NodeKind::Hex8; padSize = 1; }
|
|
||||||
|
|
||||||
Node pad;
|
|
||||||
pad.kind = padKind;
|
|
||||||
pad.parentId = structId;
|
|
||||||
pad.offset = padOffset;
|
|
||||||
pad.name = QString("pad_%1").arg(padOffset, 2, 16, QChar('0'));
|
|
||||||
pad.id = tree.reserveId();
|
|
||||||
m_doc->undoStack.push(new RcxCommand(this, cmd::Insert{pad}));
|
|
||||||
padOffset += padSize;
|
|
||||||
gap -= padSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_doc->undoStack.endMacro();
|
|
||||||
m_suppressRefresh = wasSuppressed;
|
|
||||||
if (!m_suppressRefresh) refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RcxController::updateCommandRow() {
|
void RcxController::updateCommandRow() {
|
||||||
// -- Source label: driven by provider metadata --
|
// -- Source label: driven by provider metadata --
|
||||||
@@ -1821,7 +1695,7 @@ void RcxController::updateCommandRow() {
|
|||||||
const auto& n = m_doc->tree.nodes[vi];
|
const auto& n = m_doc->tree.nodes[vi];
|
||||||
QString keyword = n.resolvedClassKeyword();
|
QString keyword = n.resolvedClassKeyword();
|
||||||
QString className = n.structTypeName.isEmpty() ? n.name : n.structTypeName;
|
QString className = n.structTypeName.isEmpty() ? n.name : n.structTypeName;
|
||||||
row2 = QStringLiteral("%1\u25BE %2 {")
|
row2 = QStringLiteral("%1 %2 {")
|
||||||
.arg(keyword, className.isEmpty() ? QStringLiteral("NoName") : className);
|
.arg(keyword, className.isEmpty() ? QStringLiteral("NoName") : className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1832,14 +1706,14 @@ void RcxController::updateCommandRow() {
|
|||||||
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
||||||
QString keyword = n.resolvedClassKeyword();
|
QString keyword = n.resolvedClassKeyword();
|
||||||
QString className = n.structTypeName.isEmpty() ? n.name : n.structTypeName;
|
QString className = n.structTypeName.isEmpty() ? n.name : n.structTypeName;
|
||||||
row2 = QStringLiteral("%1\u25BE %2 {")
|
row2 = QStringLiteral("%1 %2 {")
|
||||||
.arg(keyword, className.isEmpty() ? QStringLiteral("NoName") : className);
|
.arg(keyword, className.isEmpty() ? QStringLiteral("NoName") : className);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (row2.isEmpty())
|
if (row2.isEmpty())
|
||||||
row2 = QStringLiteral("struct\u25BE NoName {");
|
row2 = QStringLiteral("struct NoName {");
|
||||||
|
|
||||||
QString combined = QStringLiteral("[\u25B8] ") + row + QStringLiteral(" \u00B7 ") + row2;
|
QString combined = QStringLiteral("[\u25B8] ") + row + QStringLiteral(" \u00B7 ") + row2;
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ public:
|
|||||||
void removeSplitEditor(RcxEditor* editor);
|
void removeSplitEditor(RcxEditor* editor);
|
||||||
QList<RcxEditor*> editors() const { return m_editors; }
|
QList<RcxEditor*> editors() const { return m_editors; }
|
||||||
|
|
||||||
|
void convertRootKeyword(const QString& newKeyword);
|
||||||
void changeNodeKind(int nodeIdx, NodeKind newKind);
|
void changeNodeKind(int nodeIdx, NodeKind newKind);
|
||||||
void renameNode(int nodeIdx, const QString& newName);
|
void renameNode(int nodeIdx, const QString& newName);
|
||||||
void insertNode(uint64_t parentId, int offset, NodeKind kind, const QString& name);
|
void insertNode(uint64_t parentId, int offset, NodeKind kind, const QString& name);
|
||||||
@@ -160,7 +161,6 @@ private:
|
|||||||
void connectEditor(RcxEditor* editor);
|
void connectEditor(RcxEditor* editor);
|
||||||
void handleMarginClick(RcxEditor* editor, int margin, int line, Qt::KeyboardModifiers mods);
|
void handleMarginClick(RcxEditor* editor, int margin, int line, Qt::KeyboardModifiers mods);
|
||||||
void updateCommandRow();
|
void updateCommandRow();
|
||||||
void performRealignment(uint64_t structId, int targetAlign);
|
|
||||||
void switchToSavedSource(int idx);
|
void switchToSavedSource(int idx);
|
||||||
void pushSavedSourcesToEditors();
|
void pushSavedSourcesToEditors();
|
||||||
void showTypePopup(RcxEditor* editor, TypePopupMode mode, int nodeIdx, QPoint globalPos);
|
void showTypePopup(RcxEditor* editor, TypePopupMode mode, int nodeIdx, QPoint globalPos);
|
||||||
|
|||||||
15
src/core.h
15
src/core.h
@@ -380,9 +380,6 @@ struct NodeTree {
|
|||||||
return qMax(declaredSize, maxEnd);
|
return qMax(declaredSize, maxEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute natural alignment of a struct (max alignment of direct children)
|
|
||||||
int computeStructAlignment(uint64_t structId) const;
|
|
||||||
|
|
||||||
// Batch selection normalizers
|
// Batch selection normalizers
|
||||||
QSet<uint64_t> normalizePreferAncestors(const QSet<uint64_t>& ids) const;
|
QSet<uint64_t> normalizePreferAncestors(const QSet<uint64_t>& ids) const;
|
||||||
QSet<uint64_t> normalizePreferDescendants(const QSet<uint64_t>& ids) const;
|
QSet<uint64_t> normalizePreferDescendants(const QSet<uint64_t>& ids) const;
|
||||||
@@ -660,16 +657,17 @@ inline ColumnSpan commandRowAddrSpan(const QString& lineText) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── CommandRow root-class spans ──
|
// ── CommandRow root-class spans ──
|
||||||
// Combined CommandRow format ends with: " struct▾ ClassName {"
|
// Combined CommandRow format ends with: " struct ClassName {"
|
||||||
|
|
||||||
inline int commandRowRootStart(const QString& lineText) {
|
inline int commandRowRootStart(const QString& lineText) {
|
||||||
int best = -1;
|
int best = -1;
|
||||||
int i;
|
int i;
|
||||||
i = lineText.lastIndexOf(QStringLiteral("struct\u25BE"));
|
// Match "struct " / "class " / "enum " as whole words before the class name
|
||||||
|
i = lineText.lastIndexOf(QStringLiteral("struct "));
|
||||||
if (i > best) best = i;
|
if (i > best) best = i;
|
||||||
i = lineText.lastIndexOf(QStringLiteral("class\u25BE"));
|
i = lineText.lastIndexOf(QStringLiteral("class "));
|
||||||
if (i > best) best = i;
|
if (i > best) best = i;
|
||||||
i = lineText.lastIndexOf(QStringLiteral("enum\u25BE"));
|
i = lineText.lastIndexOf(QStringLiteral("enum "));
|
||||||
if (i > best) best = i;
|
if (i > best) best = i;
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
@@ -678,8 +676,7 @@ inline ColumnSpan commandRowRootTypeSpan(const QString& lineText) {
|
|||||||
int start = commandRowRootStart(lineText);
|
int start = commandRowRootStart(lineText);
|
||||||
if (start < 0) return {};
|
if (start < 0) return {};
|
||||||
int end = start;
|
int end = start;
|
||||||
while (end < lineText.size() && lineText[end] != QChar(' ')
|
while (end < lineText.size() && lineText[end] != QChar(' ')) end++;
|
||||||
&& lineText[end] != QChar(0x25BE)) end++;
|
|
||||||
if (end <= start) return {};
|
if (end <= start) return {};
|
||||||
return {start, end, true};
|
return {start, end, true};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@
|
|||||||
|
|
||||||
namespace rcx {
|
namespace rcx {
|
||||||
|
|
||||||
|
// Forward declaration (defined below, after RcxEditor constructor)
|
||||||
|
static QString getLineText(QsciScintilla* sci, int line);
|
||||||
|
|
||||||
// ── Value history popup (styled like TypeSelectorPopup) ──
|
// ── Value history popup (styled like TypeSelectorPopup) ──
|
||||||
|
|
||||||
class ValueHistoryPopup : public QFrame {
|
class ValueHistoryPopup : public QFrame {
|
||||||
@@ -327,6 +330,33 @@ RcxEditor::RcxEditor(QWidget* parent) : QWidget(parent) {
|
|||||||
}
|
}
|
||||||
HitInfo hi = hitTest(pos);
|
HitInfo hi = hitTest(pos);
|
||||||
int line = hi.line;
|
int line = hi.line;
|
||||||
|
|
||||||
|
// Right-click on command row keyword → show conversion menu
|
||||||
|
if (line == 0 && hi.col >= 0 && !m_meta.isEmpty()
|
||||||
|
&& m_meta[0].lineKind == LineKind::CommandRow) {
|
||||||
|
QString lineText = getLineText(m_sci, 0);
|
||||||
|
ColumnSpan rts = commandRowRootTypeSpan(lineText);
|
||||||
|
if (rts.valid && hi.col >= rts.start && hi.col < rts.end) {
|
||||||
|
// Extract current keyword from span text
|
||||||
|
QString kw = lineText.mid(rts.start, rts.end - rts.start).trimmed();
|
||||||
|
QMenu menu;
|
||||||
|
if (kw == QStringLiteral("class"))
|
||||||
|
menu.addAction("Convert to Struct");
|
||||||
|
else if (kw == QStringLiteral("struct"))
|
||||||
|
menu.addAction("Convert to Class");
|
||||||
|
// enum: no conversion options
|
||||||
|
if (!menu.isEmpty()) {
|
||||||
|
QAction* chosen = menu.exec(m_sci->mapToGlobal(pos));
|
||||||
|
if (chosen) {
|
||||||
|
QString newKw = chosen->text().contains("Class")
|
||||||
|
? QStringLiteral("class") : QStringLiteral("struct");
|
||||||
|
emit keywordConvertRequested(newKw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int nodeIdx = -1;
|
int nodeIdx = -1;
|
||||||
int subLine = 0;
|
int subLine = 0;
|
||||||
if (line >= 0 && line < m_meta.size()) {
|
if (line >= 0 && line < m_meta.size()) {
|
||||||
@@ -341,8 +371,7 @@ RcxEditor::RcxEditor(QWidget* parent) : QWidget(parent) {
|
|||||||
if (!m_editState.active) return;
|
if (!m_editState.active) return;
|
||||||
if (id == 1 && (m_editState.target == EditTarget::Type
|
if (id == 1 && (m_editState.target == EditTarget::Type
|
||||||
|| m_editState.target == EditTarget::ArrayElementType
|
|| m_editState.target == EditTarget::ArrayElementType
|
||||||
|| m_editState.target == EditTarget::PointerTarget
|
|| m_editState.target == EditTarget::PointerTarget)) {
|
||||||
|| m_editState.target == EditTarget::RootClassType)) {
|
|
||||||
auto info = endInlineEdit();
|
auto info = endInlineEdit();
|
||||||
emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, text);
|
emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, text);
|
||||||
}
|
}
|
||||||
@@ -1469,8 +1498,7 @@ static bool hitTestTarget(QsciScintilla* sci,
|
|||||||
ColumnSpan as = commandRowAddrSpan(lineText);
|
ColumnSpan as = commandRowAddrSpan(lineText);
|
||||||
if (inSpan(as)) { outTarget = EditTarget::BaseAddress; outLine = line; return true; }
|
if (inSpan(as)) { outTarget = EditTarget::BaseAddress; outLine = line; return true; }
|
||||||
|
|
||||||
ColumnSpan rts = commandRowRootTypeSpan(lineText);
|
// RootClassType is no longer clickable — use right-click to convert
|
||||||
if (inSpan(rts)) { outTarget = EditTarget::RootClassType; outLine = line; return true; }
|
|
||||||
ColumnSpan rns = commandRowRootNameSpan(lineText);
|
ColumnSpan rns = commandRowRootNameSpan(lineText);
|
||||||
if (inSpan(rns)) { outTarget = EditTarget::RootClassName; outLine = line; return true; }
|
if (inSpan(rns)) { outTarget = EditTarget::RootClassName; outLine = line; return true; }
|
||||||
return false;
|
return false;
|
||||||
@@ -2151,23 +2179,7 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line, int col) {
|
|||||||
// and exit early above (never reach here).
|
// and exit early above (never reach here).
|
||||||
if (target == EditTarget::Source)
|
if (target == EditTarget::Source)
|
||||||
QTimer::singleShot(0, this, &RcxEditor::showSourcePicker);
|
QTimer::singleShot(0, this, &RcxEditor::showSourcePicker);
|
||||||
if (target == EditTarget::RootClassType) {
|
// RootClassType is no longer editable via click — use right-click conversion instead
|
||||||
QTimer::singleShot(0, this, [this]() {
|
|
||||||
if (!m_editState.active || m_editState.target != EditTarget::RootClassType) return;
|
|
||||||
// Replace text with spaces and show picker
|
|
||||||
int len = m_editState.original.size();
|
|
||||||
QString spaces(len, ' ');
|
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETSEL,
|
|
||||||
m_editState.posStart, m_editState.posEnd);
|
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_REPLACESEL,
|
|
||||||
(uintptr_t)0, spaces.toUtf8().constData());
|
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_GOTOPOS, m_editState.posStart);
|
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_AUTOCSETSEPARATOR, (long)'\n');
|
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_USERLISTSHOW,
|
|
||||||
(uintptr_t)1, "struct\nclass\nenum");
|
|
||||||
m_sci->viewport()->setCursor(Qt::ArrowCursor);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Refresh hover cursor so value history popup appears with Set buttons immediately
|
// Refresh hover cursor so value history popup appears with Set buttons immediately
|
||||||
if (target == EditTarget::Value)
|
if (target == EditTarget::Value)
|
||||||
QTimer::singleShot(0, this, &RcxEditor::applyHoverCursor);
|
QTimer::singleShot(0, this, &RcxEditor::applyHoverCursor);
|
||||||
@@ -2444,8 +2456,7 @@ void RcxEditor::paintEditableSpans(int line) {
|
|||||||
fillIndicatorCols(IND_EDITABLE, line, norm.start, norm.end);
|
fillIndicatorCols(IND_EDITABLE, line, norm.start, norm.end);
|
||||||
if (resolvedSpanFor(line, EditTarget::BaseAddress, norm))
|
if (resolvedSpanFor(line, EditTarget::BaseAddress, norm))
|
||||||
fillIndicatorCols(IND_EDITABLE, line, norm.start, norm.end);
|
fillIndicatorCols(IND_EDITABLE, line, norm.start, norm.end);
|
||||||
if (resolvedSpanFor(line, EditTarget::RootClassType, norm))
|
// RootClassType no longer shown as editable — right-click conversion instead
|
||||||
fillIndicatorCols(IND_EDITABLE, line, norm.start, norm.end);
|
|
||||||
if (resolvedSpanFor(line, EditTarget::RootClassName, norm))
|
if (resolvedSpanFor(line, EditTarget::RootClassName, norm))
|
||||||
fillIndicatorCols(IND_EDITABLE, line, norm.start, norm.end);
|
fillIndicatorCols(IND_EDITABLE, line, norm.start, norm.end);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ public:
|
|||||||
signals:
|
signals:
|
||||||
void marginClicked(int margin, int line, Qt::KeyboardModifiers mods);
|
void marginClicked(int margin, int line, Qt::KeyboardModifiers mods);
|
||||||
void contextMenuRequested(int line, int nodeIdx, int subLine, QPoint globalPos);
|
void contextMenuRequested(int line, int nodeIdx, int subLine, QPoint globalPos);
|
||||||
|
void keywordConvertRequested(const QString& newKeyword);
|
||||||
void nodeClicked(int line, uint64_t nodeId, Qt::KeyboardModifiers mods);
|
void nodeClicked(int line, uint64_t nodeId, Qt::KeyboardModifiers mods);
|
||||||
void inlineEditCommitted(int nodeIdx, int subLine,
|
void inlineEditCommitted(int nodeIdx, int subLine,
|
||||||
EditTarget target, const QString& text);
|
EditTarget target, const QString& text);
|
||||||
|
|||||||
115
src/main.cpp
115
src/main.cpp
@@ -399,8 +399,9 @@ inline QAction* Qt5Qt6AddAction(QMenu* menu, const QString &text, const QKeySequ
|
|||||||
void MainWindow::createMenus() {
|
void MainWindow::createMenus() {
|
||||||
// File
|
// File
|
||||||
auto* file = m_titleBar->menuBar()->addMenu("&File");
|
auto* file = m_titleBar->menuBar()->addMenu("&File");
|
||||||
Qt5Qt6AddAction(file, "&New", QKeySequence::New, QIcon(), this, &MainWindow::newDocument);
|
Qt5Qt6AddAction(file, "New &Class", QKeySequence::New, QIcon(), this, &MainWindow::newClass);
|
||||||
Qt5Qt6AddAction(file, "New &Tab", QKeySequence(Qt::CTRL | Qt::Key_T), QIcon(), this, &MainWindow::newFile);
|
Qt5Qt6AddAction(file, "New &Struct", QKeySequence(Qt::CTRL | Qt::Key_T), QIcon(), this, &MainWindow::newStruct);
|
||||||
|
Qt5Qt6AddAction(file, "New &Enum", QKeySequence(Qt::CTRL | Qt::Key_E), QIcon(), this, &MainWindow::newEnum);
|
||||||
Qt5Qt6AddAction(file, "&Open...", QKeySequence::Open, makeIcon(":/vsicons/folder-opened.svg"), this, &MainWindow::openFile);
|
Qt5Qt6AddAction(file, "&Open...", QKeySequence::Open, makeIcon(":/vsicons/folder-opened.svg"), this, &MainWindow::openFile);
|
||||||
file->addSeparator();
|
file->addSeparator();
|
||||||
Qt5Qt6AddAction(file, "&Save", QKeySequence::Save, makeIcon(":/vsicons/save.svg"), this, &MainWindow::saveFile);
|
Qt5Qt6AddAction(file, "&Save", QKeySequence::Save, makeIcon(":/vsicons/save.svg"), this, &MainWindow::saveFile);
|
||||||
@@ -745,11 +746,12 @@ QMdiSubWindow* MainWindow::createTab(RcxDocument* doc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build a minimal empty struct for new documents
|
// Build a minimal empty struct for new documents
|
||||||
static void buildEmptyStruct(NodeTree& tree) {
|
static void buildEmptyStruct(NodeTree& tree, const QString& classKeyword = QString()) {
|
||||||
Node root;
|
Node root;
|
||||||
root.kind = NodeKind::Struct;
|
root.kind = NodeKind::Struct;
|
||||||
root.name = "instance";
|
root.name = "instance";
|
||||||
root.structTypeName = "Unnamed";
|
root.structTypeName = "Unnamed";
|
||||||
|
root.classKeyword = classKeyword;
|
||||||
root.parentId = 0;
|
root.parentId = 0;
|
||||||
root.offset = 0;
|
root.offset = 0;
|
||||||
int ri = tree.addNode(root);
|
int ri = tree.addNode(root);
|
||||||
@@ -765,47 +767,16 @@ static void buildEmptyStruct(NodeTree& tree) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::newFile() {
|
void MainWindow::newClass() {
|
||||||
|
project_new(QStringLiteral("class"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::newStruct() {
|
||||||
project_new();
|
project_new();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::newDocument() {
|
void MainWindow::newEnum() {
|
||||||
auto* tab = activeTab();
|
project_new(QStringLiteral("enum"));
|
||||||
if (!tab) {
|
|
||||||
project_new();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto* doc = tab->doc;
|
|
||||||
auto* ctrl = tab->ctrl;
|
|
||||||
|
|
||||||
// Clear everything
|
|
||||||
doc->undoStack.clear();
|
|
||||||
doc->tree = NodeTree();
|
|
||||||
doc->tree.baseAddress = 0x00400000;
|
|
||||||
doc->filePath.clear();
|
|
||||||
doc->typeAliases.clear();
|
|
||||||
doc->modified = false;
|
|
||||||
|
|
||||||
buildEmptyStruct(doc->tree);
|
|
||||||
|
|
||||||
QByteArray data(256, '\0');
|
|
||||||
doc->provider = std::make_shared<BufferProvider>(data);
|
|
||||||
|
|
||||||
// Focus on first struct
|
|
||||||
ctrl->setViewRootId(0);
|
|
||||||
for (const auto& n : doc->tree.nodes) {
|
|
||||||
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
|
||||||
ctrl->setViewRootId(n.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctrl->clearSelection();
|
|
||||||
emit doc->documentChanged();
|
|
||||||
|
|
||||||
auto* sub = m_mdiArea->activeSubWindow();
|
|
||||||
if (sub) sub->setWindowTitle(rootName(doc->tree, ctrl->viewRootId()));
|
|
||||||
updateWindowTitle();
|
|
||||||
rebuildWorkspaceModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void buildEditorDemo(NodeTree& tree, uintptr_t editorAddr) {
|
static void buildEditorDemo(NodeTree& tree, uintptr_t editorAddr) {
|
||||||
@@ -1560,14 +1531,14 @@ void MainWindow::showTypeAliasesDialog() {
|
|||||||
|
|
||||||
// ── Project Lifecycle API ──
|
// ── Project Lifecycle API ──
|
||||||
|
|
||||||
QMdiSubWindow* MainWindow::project_new() {
|
QMdiSubWindow* MainWindow::project_new(const QString& classKeyword) {
|
||||||
auto* doc = new RcxDocument(this);
|
auto* doc = new RcxDocument(this);
|
||||||
|
|
||||||
QByteArray data(256, '\0');
|
QByteArray data(256, '\0');
|
||||||
doc->loadData(data);
|
doc->loadData(data);
|
||||||
doc->tree.baseAddress = 0x00400000;
|
doc->tree.baseAddress = 0x00400000;
|
||||||
|
|
||||||
buildEmptyStruct(doc->tree);
|
buildEmptyStruct(doc->tree, classKeyword);
|
||||||
|
|
||||||
auto* sub = createTab(doc);
|
auto* sub = createTab(doc);
|
||||||
rebuildWorkspaceModel();
|
rebuildWorkspaceModel();
|
||||||
@@ -1681,22 +1652,52 @@ void MainWindow::createWorkspaceDock() {
|
|||||||
|
|
||||||
auto structIdVar = index.data(Qt::UserRole + 1);
|
auto structIdVar = index.data(Qt::UserRole + 1);
|
||||||
uint64_t structId = structIdVar.isValid() ? structIdVar.toULongLong() : 0;
|
uint64_t structId = structIdVar.isValid() ? structIdVar.toULongLong() : 0;
|
||||||
if (structId == 0 || structId == rcx::kGroupSentinel) return;
|
|
||||||
|
// Right-click on "Project" group → New Class / New Struct / New Enum
|
||||||
|
if (structId == rcx::kGroupSentinel) {
|
||||||
|
QMenu menu;
|
||||||
|
auto* actClass = menu.addAction("New Class");
|
||||||
|
auto* actStruct = menu.addAction("New Struct");
|
||||||
|
auto* actEnum = menu.addAction("New Enum");
|
||||||
|
QAction* chosen = menu.exec(m_workspaceTree->viewport()->mapToGlobal(pos));
|
||||||
|
if (chosen == actClass) newClass();
|
||||||
|
else if (chosen == actStruct) newStruct();
|
||||||
|
else if (chosen == actEnum) newEnum();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (structId == 0) return;
|
||||||
|
|
||||||
auto subVar = index.data(Qt::UserRole);
|
auto subVar = index.data(Qt::UserRole);
|
||||||
if (!subVar.isValid()) return;
|
if (!subVar.isValid()) return;
|
||||||
auto* sub = static_cast<QMdiSubWindow*>(subVar.value<void*>());
|
auto* sub = static_cast<QMdiSubWindow*>(subVar.value<void*>());
|
||||||
if (!sub || !m_tabs.contains(sub)) return;
|
if (!sub || !m_tabs.contains(sub)) return;
|
||||||
|
|
||||||
QMenu menu;
|
|
||||||
auto* deleteAction = menu.addAction(QIcon(":/vsicons/remove.svg"), "Delete");
|
|
||||||
if (menu.exec(m_workspaceTree->viewport()->mapToGlobal(pos)) == deleteAction) {
|
|
||||||
auto& tab = m_tabs[sub];
|
auto& tab = m_tabs[sub];
|
||||||
int ni = tab.doc->tree.indexOfId(structId);
|
int ni = tab.doc->tree.indexOfId(structId);
|
||||||
if (ni >= 0) {
|
if (ni < 0) return;
|
||||||
|
QString kw = tab.doc->tree.nodes[ni].resolvedClassKeyword();
|
||||||
|
|
||||||
|
QMenu menu;
|
||||||
|
QAction* actConvert = nullptr;
|
||||||
|
// class↔struct conversion only (no enum conversion)
|
||||||
|
if (kw == QStringLiteral("class"))
|
||||||
|
actConvert = menu.addAction("Convert to Struct");
|
||||||
|
else if (kw == QStringLiteral("struct"))
|
||||||
|
actConvert = menu.addAction("Convert to Class");
|
||||||
|
auto* actDelete = menu.addAction(QIcon(":/vsicons/remove.svg"), "Delete");
|
||||||
|
|
||||||
|
QAction* chosen = menu.exec(m_workspaceTree->viewport()->mapToGlobal(pos));
|
||||||
|
if (chosen == actDelete) {
|
||||||
tab.ctrl->removeNode(ni);
|
tab.ctrl->removeNode(ni);
|
||||||
rebuildWorkspaceModel();
|
rebuildWorkspaceModel();
|
||||||
}
|
} else if (chosen && chosen == actConvert) {
|
||||||
|
QString newKw = kw == QStringLiteral("class")
|
||||||
|
? QStringLiteral("struct") : QStringLiteral("class");
|
||||||
|
QString oldKw = tab.doc->tree.nodes[ni].resolvedClassKeyword();
|
||||||
|
tab.doc->undoStack.push(new rcx::RcxCommand(tab.ctrl,
|
||||||
|
rcx::cmd::ChangeClassKeyword{structId, oldKw, newKw}));
|
||||||
|
rebuildWorkspaceModel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1897,27 +1898,11 @@ int main(int argc, char* argv[]) {
|
|||||||
rcx::MainWindow window;
|
rcx::MainWindow window;
|
||||||
window.setWindowIcon(QIcon(":/icons/class.png"));
|
window.setWindowIcon(QIcon(":/icons/class.png"));
|
||||||
|
|
||||||
bool screenshotMode = app.arguments().contains("--screenshot");
|
|
||||||
if (screenshotMode)
|
|
||||||
window.setWindowOpacity(0.0);
|
|
||||||
window.show();
|
window.show();
|
||||||
|
|
||||||
// Auto-open demo project from saved .rcx file
|
// Auto-open demo project from saved .rcx file
|
||||||
QMetaObject::invokeMethod(&window, "selfTest");
|
QMetaObject::invokeMethod(&window, "selfTest");
|
||||||
|
|
||||||
if (screenshotMode) {
|
|
||||||
QString out = "screenshot.png";
|
|
||||||
int idx = app.arguments().indexOf("--screenshot");
|
|
||||||
if (idx + 1 < app.arguments().size())
|
|
||||||
out = app.arguments().at(idx + 1);
|
|
||||||
|
|
||||||
QTimer::singleShot(1000, [&window, out]() {
|
|
||||||
QDir().mkpath(QFileInfo(out).absolutePath());
|
|
||||||
window.grab().save(out);
|
|
||||||
::_Exit(0); // immediate exit — no need for clean shutdown in screenshot mode
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ public:
|
|||||||
explicit MainWindow(QWidget* parent = nullptr);
|
explicit MainWindow(QWidget* parent = nullptr);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void newFile();
|
void newClass();
|
||||||
void newDocument();
|
void newStruct();
|
||||||
|
void newEnum();
|
||||||
void selfTest();
|
void selfTest();
|
||||||
void openFile();
|
void openFile();
|
||||||
void saveFile();
|
void saveFile();
|
||||||
@@ -56,7 +57,7 @@ private slots:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// Project Lifecycle API
|
// Project Lifecycle API
|
||||||
QMdiSubWindow* project_new();
|
QMdiSubWindow* project_new(const QString& classKeyword = QString());
|
||||||
QMdiSubWindow* project_open(const QString& path = {});
|
QMdiSubWindow* project_open(const QString& path = {});
|
||||||
bool project_save(QMdiSubWindow* sub = nullptr, bool saveAs = false);
|
bool project_save(QMdiSubWindow* sub = nullptr, bool saveAs = false);
|
||||||
void project_close(QMdiSubWindow* sub = nullptr);
|
void project_close(QMdiSubWindow* sub = nullptr);
|
||||||
|
|||||||
@@ -1920,54 +1920,9 @@ private slots:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void testComputeStructAlignment() {
|
|
||||||
NodeTree tree;
|
|
||||||
tree.baseAddress = 0;
|
|
||||||
|
|
||||||
Node root;
|
|
||||||
root.kind = NodeKind::Struct;
|
|
||||||
root.name = "Root";
|
|
||||||
root.parentId = 0;
|
|
||||||
int ri = tree.addNode(root);
|
|
||||||
uint64_t rootId = tree.nodes[ri].id;
|
|
||||||
|
|
||||||
// Int32 has alignment 4
|
|
||||||
Node f1;
|
|
||||||
f1.kind = NodeKind::Int32;
|
|
||||||
f1.name = "a";
|
|
||||||
f1.parentId = rootId;
|
|
||||||
f1.offset = 0;
|
|
||||||
tree.addNode(f1);
|
|
||||||
|
|
||||||
QCOMPARE(tree.computeStructAlignment(rootId), 4);
|
|
||||||
|
|
||||||
// Add Hex64 (alignment 8) — max should become 8
|
|
||||||
Node f2;
|
|
||||||
f2.kind = NodeKind::Hex64;
|
|
||||||
f2.name = "b";
|
|
||||||
f2.parentId = rootId;
|
|
||||||
f2.offset = 8;
|
|
||||||
tree.addNode(f2);
|
|
||||||
|
|
||||||
QCOMPARE(tree.computeStructAlignment(rootId), 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testComputeStructAlignmentEmpty() {
|
|
||||||
NodeTree tree;
|
|
||||||
Node root;
|
|
||||||
root.kind = NodeKind::Struct;
|
|
||||||
root.name = "Empty";
|
|
||||||
root.parentId = 0;
|
|
||||||
int ri = tree.addNode(root);
|
|
||||||
uint64_t rootId = tree.nodes[ri].id;
|
|
||||||
|
|
||||||
// Empty struct → alignment 1
|
|
||||||
QCOMPARE(tree.computeStructAlignment(rootId), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testCommandRowRootNameSpan() {
|
void testCommandRowRootNameSpan() {
|
||||||
// Name span should cover the class name in the merged command row
|
// Name span should cover the class name in the merged command row
|
||||||
QString text = "source\u25BE \u00B7 0x0 \u00B7 struct\u25BE MyClass {";
|
QString text = "source\u25BE \u00B7 0x0 \u00B7 struct MyClass {";
|
||||||
ColumnSpan nameSpan = commandRowRootNameSpan(text);
|
ColumnSpan nameSpan = commandRowRootNameSpan(text);
|
||||||
QVERIFY(nameSpan.valid);
|
QVERIFY(nameSpan.valid);
|
||||||
|
|
||||||
|
|||||||
@@ -941,19 +941,13 @@ private slots:
|
|||||||
|
|
||||||
// Set CommandRow text with root class (simulates controller.updateCommandRow)
|
// Set CommandRow text with root class (simulates controller.updateCommandRow)
|
||||||
m_editor->setCommandRowText(
|
m_editor->setCommandRowText(
|
||||||
QStringLiteral("source\u25BE \u00B7 0xD87B5E5000 \u00B7 struct\u25BE _PEB64 {"));
|
QStringLiteral("source\u25BE \u00B7 0xD87B5E5000 \u00B7 struct _PEB64 {"));
|
||||||
|
|
||||||
// RootClassName should be allowed on CommandRow (line 0)
|
// RootClassName should be allowed on CommandRow (line 0)
|
||||||
bool ok = m_editor->beginInlineEdit(EditTarget::RootClassName, 0);
|
bool ok = m_editor->beginInlineEdit(EditTarget::RootClassName, 0);
|
||||||
QVERIFY2(ok, "RootClassName edit should be allowed on CommandRow");
|
QVERIFY2(ok, "RootClassName edit should be allowed on CommandRow");
|
||||||
QVERIFY(m_editor->isEditing());
|
QVERIFY(m_editor->isEditing());
|
||||||
m_editor->cancelInlineEdit();
|
m_editor->cancelInlineEdit();
|
||||||
|
|
||||||
// RootClassType should be allowed on CommandRow (line 0)
|
|
||||||
ok = m_editor->beginInlineEdit(EditTarget::RootClassType, 0);
|
|
||||||
QVERIFY2(ok, "RootClassType edit should be allowed on CommandRow");
|
|
||||||
QVERIFY(m_editor->isEditing());
|
|
||||||
m_editor->cancelInlineEdit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Test: CommandRow root class name editable ──
|
// ── Test: CommandRow root class name editable ──
|
||||||
@@ -962,7 +956,7 @@ private slots:
|
|||||||
|
|
||||||
// Set CommandRow with root class
|
// Set CommandRow with root class
|
||||||
m_editor->setCommandRowText(
|
m_editor->setCommandRowText(
|
||||||
QStringLiteral("source\u25BE \u00B7 0xD87B5E5000 \u00B7 struct\u25BE _PEB64 {"));
|
QStringLiteral("source\u25BE \u00B7 0xD87B5E5000 \u00B7 struct _PEB64 {"));
|
||||||
|
|
||||||
// Line 0 is CommandRow
|
// Line 0 is CommandRow
|
||||||
const LineMeta* lm = m_editor->metaForLine(0);
|
const LineMeta* lm = m_editor->metaForLine(0);
|
||||||
@@ -1008,7 +1002,7 @@ private slots:
|
|||||||
|
|
||||||
// Set command row text (simulates controller.updateCommandRow)
|
// Set command row text (simulates controller.updateCommandRow)
|
||||||
QString cmdText = QStringLiteral(
|
QString cmdText = QStringLiteral(
|
||||||
"source\u25BE \u00B7 0xD87B5E5000 \u00B7 struct\u25BE _PEB64 {");
|
"source\u25BE \u00B7 0xD87B5E5000 \u00B7 struct _PEB64 {");
|
||||||
m_editor->setCommandRowText(cmdText);
|
m_editor->setCommandRowText(cmdText);
|
||||||
QApplication::processEvents();
|
QApplication::processEvents();
|
||||||
|
|
||||||
@@ -1086,7 +1080,7 @@ private slots:
|
|||||||
m_editor->applyDocument(m_result);
|
m_editor->applyDocument(m_result);
|
||||||
|
|
||||||
QString cmdText = QStringLiteral(
|
QString cmdText = QStringLiteral(
|
||||||
"source\u25BE \u00B7 0xD87B5E5000 \u00B7 struct\u25BE _PEB64 {");
|
"source\u25BE \u00B7 0xD87B5E5000 \u00B7 struct _PEB64 {");
|
||||||
m_editor->setCommandRowText(cmdText);
|
m_editor->setCommandRowText(cmdText);
|
||||||
QApplication::processEvents();
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ private slots:
|
|||||||
// ── Chevron span detection ──
|
// ── Chevron span detection ──
|
||||||
|
|
||||||
void testChevronSpanDetected() {
|
void testChevronSpanDetected() {
|
||||||
QString text = QStringLiteral("[\u25B8] source\u25BE \u00B7 0x1000 \u00B7 struct\u25BE Alpha {");
|
QString text = QStringLiteral("[\u25B8] source\u25BE \u00B7 0x1000 \u00B7 struct Alpha {");
|
||||||
ColumnSpan span = commandRowChevronSpan(text);
|
ColumnSpan span = commandRowChevronSpan(text);
|
||||||
QVERIFY(span.valid);
|
QVERIFY(span.valid);
|
||||||
QCOMPARE(span.start, 0);
|
QCOMPARE(span.start, 0);
|
||||||
@@ -79,7 +79,7 @@ private slots:
|
|||||||
// ── Existing spans unbroken by chevron prefix ──
|
// ── Existing spans unbroken by chevron prefix ──
|
||||||
|
|
||||||
void testSpansWithPrefix() {
|
void testSpansWithPrefix() {
|
||||||
QString text = QStringLiteral("[\u25B8] source\u25BE \u00B7 0x1000 \u00B7 struct\u25BE Alpha {");
|
QString text = QStringLiteral("[\u25B8] source\u25BE \u00B7 0x1000 \u00B7 struct Alpha {");
|
||||||
|
|
||||||
ColumnSpan src = commandRowSrcSpan(text);
|
ColumnSpan src = commandRowSrcSpan(text);
|
||||||
QVERIFY(src.valid);
|
QVERIFY(src.valid);
|
||||||
|
|||||||
Reference in New Issue
Block a user