mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Unify right-click menu: all items available everywhere
Right-click menu is now one unified menu. Node-specific actions (edit value, rename, change type, add field below, duplicate, delete, copy address/offset) appear when clicking on a node. Creation actions (Add Hex64, Add Struct, Append 128 bytes), Undo/Redo, and Copy All as Text are always present regardless of where you click. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -742,52 +742,21 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
int subLine, const QPoint& globalPos) {
|
int subLine, const QPoint& globalPos) {
|
||||||
auto icon = [](const char* name) { return QIcon(QStringLiteral(":/vsicons/%1").arg(name)); };
|
auto icon = [](const char* name) { return QIcon(QStringLiteral(":/vsicons/%1").arg(name)); };
|
||||||
|
|
||||||
// Empty area or CommandRow: show full creation menu
|
const bool hasNode = nodeIdx >= 0 && nodeIdx < m_doc->tree.nodes.size();
|
||||||
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) {
|
|
||||||
QMenu menu;
|
|
||||||
menu.addAction(icon("add.svg"), "&Add Hex64 Field", [this]() {
|
|
||||||
insertNode(0, -1, NodeKind::Hex64, "newField");
|
|
||||||
});
|
|
||||||
menu.addAction(icon("symbol-structure.svg"), "Add &Struct", [this]() {
|
|
||||||
insertNode(0, -1, NodeKind::Struct, "NewClass");
|
|
||||||
});
|
|
||||||
menu.addSeparator();
|
|
||||||
menu.addAction(icon("diff-added.svg"), "Append &128 bytes", [this]() {
|
|
||||||
m_suppressRefresh = true;
|
|
||||||
m_doc->undoStack.beginMacro(QStringLiteral("Append 128 bytes"));
|
|
||||||
for (int i = 0; i < 16; i++)
|
|
||||||
insertNode(0, -1, NodeKind::Hex64,
|
|
||||||
QStringLiteral("field_%1").arg(i));
|
|
||||||
m_doc->undoStack.endMacro();
|
|
||||||
m_suppressRefresh = false;
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
menu.addSeparator();
|
|
||||||
menu.addAction(icon("arrow-left.svg"), "&Undo", [this]() {
|
|
||||||
m_doc->undoStack.undo();
|
|
||||||
})->setEnabled(m_doc->undoStack.canUndo());
|
|
||||||
menu.addAction(icon("arrow-right.svg"), "&Redo", [this]() {
|
|
||||||
m_doc->undoStack.redo();
|
|
||||||
})->setEnabled(m_doc->undoStack.canRedo());
|
|
||||||
menu.addSeparator();
|
|
||||||
menu.addAction(icon("clippy.svg"), "Copy All as &Text", [editor]() {
|
|
||||||
QApplication::clipboard()->setText(editor->scintilla()->text());
|
|
||||||
});
|
|
||||||
menu.exec(globalPos);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint64_t clickedId = m_doc->tree.nodes[nodeIdx].id;
|
|
||||||
|
|
||||||
// Right-click selection policy: if not in selection, select only this node
|
// Selection policy
|
||||||
if (!m_selIds.contains(clickedId)) {
|
if (hasNode) {
|
||||||
m_selIds.clear();
|
uint64_t clickedId = m_doc->tree.nodes[nodeIdx].id;
|
||||||
m_selIds.insert(clickedId);
|
if (!m_selIds.contains(clickedId)) {
|
||||||
m_anchorLine = line;
|
m_selIds.clear();
|
||||||
applySelectionOverlays();
|
m_selIds.insert(clickedId);
|
||||||
|
m_anchorLine = line;
|
||||||
|
applySelectionOverlays();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-select batch menu
|
// Multi-select batch actions at top
|
||||||
if (m_selIds.size() > 1) {
|
if (hasNode && m_selIds.size() > 1) {
|
||||||
QMenu menu;
|
QMenu menu;
|
||||||
int count = m_selIds.size();
|
int count = m_selIds.size();
|
||||||
QSet<uint64_t> ids = m_selIds;
|
QSet<uint64_t> ids = m_selIds;
|
||||||
@@ -819,81 +788,115 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Node& node = m_doc->tree.nodes[nodeIdx];
|
|
||||||
uint64_t nodeId = node.id;
|
|
||||||
uint64_t parentId = node.parentId;
|
|
||||||
|
|
||||||
QMenu menu;
|
QMenu menu;
|
||||||
|
|
||||||
// Inline edit actions — position cursor on the right-clicked line
|
// ── Node-specific actions (only when clicking on a node) ──
|
||||||
bool isEditable = node.kind != NodeKind::Struct && node.kind != NodeKind::Array
|
if (hasNode) {
|
||||||
&& node.kind != NodeKind::Padding && node.kind != NodeKind::Mat4x4
|
const Node& node = m_doc->tree.nodes[nodeIdx];
|
||||||
&& m_doc->provider->isWritable();
|
uint64_t nodeId = node.id;
|
||||||
if (isEditable) {
|
uint64_t parentId = node.parentId;
|
||||||
menu.addAction(icon("edit.svg"), "Edit &Value\tEnter", [editor, line]() {
|
|
||||||
editor->beginInlineEdit(EditTarget::Value, line);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.addAction(icon("rename.svg"), "Re&name\tF2", [editor, line]() {
|
bool isEditable = node.kind != NodeKind::Struct && node.kind != NodeKind::Array
|
||||||
editor->beginInlineEdit(EditTarget::Name, line);
|
&& node.kind != NodeKind::Padding && node.kind != NodeKind::Mat4x4
|
||||||
});
|
&& m_doc->provider->isWritable();
|
||||||
|
if (isEditable) {
|
||||||
menu.addAction(icon("symbol-structure.svg"), "Change &Type\tT", [editor, line]() {
|
menu.addAction(icon("edit.svg"), "Edit &Value\tEnter", [editor, line]() {
|
||||||
editor->beginInlineEdit(EditTarget::Type, line);
|
editor->beginInlineEdit(EditTarget::Value, line);
|
||||||
});
|
|
||||||
|
|
||||||
menu.addSeparator();
|
|
||||||
|
|
||||||
menu.addAction(icon("add.svg"), "&Add Field Below\tInsert", [this, parentId]() {
|
|
||||||
insertNode(parentId, -1, NodeKind::Hex64, "newField");
|
|
||||||
});
|
|
||||||
|
|
||||||
if (node.kind == NodeKind::Struct || node.kind == NodeKind::Array) {
|
|
||||||
menu.addAction(icon("diff-added.svg"), "Add &Child", [this, nodeId]() {
|
|
||||||
insertNode(nodeId, 0, NodeKind::Hex64, "newField");
|
|
||||||
});
|
|
||||||
if (node.collapsed) {
|
|
||||||
menu.addAction(icon("expand-all.svg"), "&Expand", [this, nodeId]() {
|
|
||||||
int ni = m_doc->tree.indexOfId(nodeId);
|
|
||||||
if (ni >= 0) toggleCollapse(ni);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
menu.addAction(icon("collapse-all.svg"), "&Collapse", [this, nodeId]() {
|
|
||||||
int ni = m_doc->tree.indexOfId(nodeId);
|
|
||||||
if (ni >= 0) toggleCollapse(ni);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menu.addAction(icon("rename.svg"), "Re&name\tF2", [editor, line]() {
|
||||||
|
editor->beginInlineEdit(EditTarget::Name, line);
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.addAction(icon("symbol-structure.svg"), "Change &Type\tT", [editor, line]() {
|
||||||
|
editor->beginInlineEdit(EditTarget::Type, line);
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
menu.addAction(icon("add.svg"), "&Add Field Below\tInsert", [this, parentId]() {
|
||||||
|
insertNode(parentId, -1, NodeKind::Hex64, "newField");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (node.kind == NodeKind::Struct || node.kind == NodeKind::Array) {
|
||||||
|
menu.addAction(icon("diff-added.svg"), "Add &Child", [this, nodeId]() {
|
||||||
|
insertNode(nodeId, 0, NodeKind::Hex64, "newField");
|
||||||
|
});
|
||||||
|
if (node.collapsed) {
|
||||||
|
menu.addAction(icon("expand-all.svg"), "&Expand", [this, nodeId]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni >= 0) toggleCollapse(ni);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
menu.addAction(icon("collapse-all.svg"), "&Collapse", [this, nodeId]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni >= 0) toggleCollapse(ni);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.addAction(icon("files.svg"), "D&uplicate\tCtrl+D", [this, nodeId]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni >= 0) duplicateNode(ni);
|
||||||
|
});
|
||||||
|
menu.addAction(icon("trash.svg"), "&Delete\tDelete", [this, nodeId]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni >= 0) removeNode(ni);
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
menu.addAction(icon("link.svg"), "Copy &Address", [this, nodeId]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni < 0) return;
|
||||||
|
uint64_t addr = m_doc->tree.baseAddress + m_doc->tree.computeOffset(ni);
|
||||||
|
QApplication::clipboard()->setText(
|
||||||
|
QStringLiteral("0x") + QString::number(addr, 16).toUpper());
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.addAction(icon("whole-word.svg"), "Copy &Offset", [this, nodeId]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni < 0) return;
|
||||||
|
int off = m_doc->tree.nodes[ni].offset;
|
||||||
|
QApplication::clipboard()->setText(
|
||||||
|
QStringLiteral("+0x") + QString::number(off, 16).toUpper().rightJustified(4, '0'));
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.addSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.addAction(icon("files.svg"), "D&uplicate\tCtrl+D", [this, nodeId]() {
|
// ── Always-available actions ──
|
||||||
int ni = m_doc->tree.indexOfId(nodeId);
|
|
||||||
if (ni >= 0) duplicateNode(ni);
|
menu.addAction(icon("add.svg"), "Add Hex64 at Root", [this]() {
|
||||||
|
insertNode(0, -1, NodeKind::Hex64, "newField");
|
||||||
});
|
});
|
||||||
menu.addAction(icon("trash.svg"), "&Delete\tDelete", [this, nodeId]() {
|
menu.addAction(icon("symbol-structure.svg"), "Add Struct at Root", [this]() {
|
||||||
int ni = m_doc->tree.indexOfId(nodeId);
|
insertNode(0, -1, NodeKind::Struct, "NewClass");
|
||||||
if (ni >= 0) removeNode(ni);
|
});
|
||||||
|
menu.addAction(icon("diff-added.svg"), "Append 128 bytes", [this]() {
|
||||||
|
m_suppressRefresh = true;
|
||||||
|
m_doc->undoStack.beginMacro(QStringLiteral("Append 128 bytes"));
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
insertNode(0, -1, NodeKind::Hex64,
|
||||||
|
QStringLiteral("field_%1").arg(i));
|
||||||
|
m_doc->undoStack.endMacro();
|
||||||
|
m_suppressRefresh = false;
|
||||||
|
refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|
||||||
menu.addAction(icon("link.svg"), "Copy &Address", [this, nodeId]() {
|
menu.addAction(icon("arrow-left.svg"), "Undo", [this]() {
|
||||||
int ni = m_doc->tree.indexOfId(nodeId);
|
m_doc->undoStack.undo();
|
||||||
if (ni < 0) return;
|
})->setEnabled(m_doc->undoStack.canUndo());
|
||||||
uint64_t addr = m_doc->tree.baseAddress + m_doc->tree.computeOffset(ni);
|
menu.addAction(icon("arrow-right.svg"), "Redo", [this]() {
|
||||||
QApplication::clipboard()->setText(
|
m_doc->undoStack.redo();
|
||||||
QStringLiteral("0x") + QString::number(addr, 16).toUpper());
|
})->setEnabled(m_doc->undoStack.canRedo());
|
||||||
});
|
|
||||||
|
|
||||||
menu.addAction(icon("whole-word.svg"), "Copy &Offset", [this, nodeId]() {
|
menu.addSeparator();
|
||||||
int ni = m_doc->tree.indexOfId(nodeId);
|
|
||||||
if (ni < 0) return;
|
|
||||||
int off = m_doc->tree.nodes[ni].offset;
|
|
||||||
QApplication::clipboard()->setText(
|
|
||||||
QStringLiteral("+0x") + QString::number(off, 16).toUpper().rightJustified(4, '0'));
|
|
||||||
});
|
|
||||||
|
|
||||||
menu.addAction(icon("clippy.svg"), "Copy All as &Text", [editor]() {
|
menu.addAction(icon("clippy.svg"), "Copy All as Text", [editor]() {
|
||||||
QApplication::clipboard()->setText(editor->scintilla()->text());
|
QApplication::clipboard()->setText(editor->scintilla()->text());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user