mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Compare commits
17 Commits
snapshot-0
...
snapshot-0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f981fe456d | ||
|
|
877ceea4c1 | ||
|
|
4160a229c6 | ||
|
|
1e1afc1640 | ||
|
|
f0cf6c549a | ||
|
|
683eab16ee | ||
|
|
b53dea8f9f | ||
|
|
f06abbab79 | ||
|
|
2477591ed2 | ||
|
|
6c13356d6d | ||
|
|
3b273a7ab2 | ||
|
|
3509a0d9dd | ||
|
|
43c3f5a842 | ||
|
|
0697ce4853 | ||
|
|
ed1bfd04cd | ||
|
|
c275eb33c9 | ||
|
|
636176ee8c |
@@ -83,7 +83,7 @@ Full command stack with 15 undoable operations: ChangeKind, Rename, Collapse, In
|
|||||||
|
|
||||||
## Plugin System
|
## Plugin System
|
||||||
|
|
||||||
Extensible provider architecture via DLL plugins with `IPlugin` interface, factory function discovery, and auto/manual loading from a Plugins folder.
|
DLL plugins loaded from a `Plugins` folder, auto or manual.
|
||||||
|
|
||||||
**Bundled plugins:**
|
**Bundled plugins:**
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ struct ComposeState {
|
|||||||
bool baseEmitted = false; // only first root struct shows base address
|
bool baseEmitted = false; // only first root struct shows base address
|
||||||
bool compactColumns = false; // compact column mode: cap type width, overflow long types
|
bool compactColumns = false; // compact column mode: cap type width, overflow long types
|
||||||
bool treeLines = false; // draw Unicode tree connectors in indentation
|
bool treeLines = false; // draw Unicode tree connectors in indentation
|
||||||
|
bool braceWrap = false; // opening brace on its own line
|
||||||
QVector<bool> siblingStack; // per-depth: true = more siblings follow at this level
|
QVector<bool> siblingStack; // per-depth: true = more siblings follow at this level
|
||||||
uint64_t currentPtrBase = 0; // absolute addr of current pointer expansion target
|
uint64_t currentPtrBase = 0; // absolute addr of current pointer expansion target
|
||||||
|
|
||||||
@@ -319,7 +320,24 @@ void composeParent(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.effectiveNameW = nameW;
|
lm.effectiveNameW = nameW;
|
||||||
headerText = fmt::fmtStructHeader(node, depth, node.collapsed, typeW, nameW, state.compactColumns);
|
headerText = fmt::fmtStructHeader(node, depth, node.collapsed, typeW, nameW, state.compactColumns);
|
||||||
}
|
}
|
||||||
state.emitLine(headerText, lm);
|
// Brace wrapping: move trailing '{' to its own line
|
||||||
|
if (state.braceWrap && !node.collapsed && headerText.endsWith(QChar('{'))) {
|
||||||
|
headerText.chop(1);
|
||||||
|
// Remove trailing separator spaces
|
||||||
|
while (headerText.endsWith(' ')) headerText.chop(1);
|
||||||
|
state.emitLine(headerText, lm);
|
||||||
|
// Emit standalone brace line
|
||||||
|
LineMeta braceLm;
|
||||||
|
braceLm.nodeIdx = nodeIdx;
|
||||||
|
braceLm.nodeId = node.id;
|
||||||
|
braceLm.depth = depth;
|
||||||
|
braceLm.lineKind = LineKind::Header;
|
||||||
|
braceLm.foldLevel = computeFoldLevel(depth, true);
|
||||||
|
braceLm.markerMask = (1u << M_STRUCT_BG);
|
||||||
|
state.emitLine(fmt::indent(depth) + QStringLiteral("{"), braceLm);
|
||||||
|
} else {
|
||||||
|
state.emitLine(headerText, lm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!node.collapsed || isArrayChild || isRootHeader) {
|
if (!node.collapsed || isArrayChild || isRootHeader) {
|
||||||
@@ -840,9 +858,26 @@ void composeNode(ComposeState& state, const NodeTree& tree,
|
|||||||
lm.effectiveTypeW = ptrOverflow ? ptrTypeOverride.size() : typeW;
|
lm.effectiveTypeW = ptrOverflow ? ptrTypeOverride.size() : typeW;
|
||||||
lm.effectiveNameW = nameW;
|
lm.effectiveNameW = nameW;
|
||||||
lm.pointerTargetName = ptrTargetName;
|
lm.pointerTargetName = ptrTargetName;
|
||||||
state.emitLine(fmt::fmtPointerHeader(node, depth, effectiveCollapsed,
|
{
|
||||||
prov, absAddr, ptrTypeOverride,
|
QString ptrText = fmt::fmtPointerHeader(node, depth, effectiveCollapsed,
|
||||||
typeW, nameW, state.compactColumns), lm);
|
prov, absAddr, ptrTypeOverride,
|
||||||
|
typeW, nameW, state.compactColumns);
|
||||||
|
if (state.braceWrap && !effectiveCollapsed && ptrText.endsWith(QChar('{'))) {
|
||||||
|
ptrText.chop(1);
|
||||||
|
while (ptrText.endsWith(' ')) ptrText.chop(1);
|
||||||
|
state.emitLine(ptrText, lm);
|
||||||
|
LineMeta braceLm;
|
||||||
|
braceLm.nodeIdx = nodeIdx;
|
||||||
|
braceLm.nodeId = node.id;
|
||||||
|
braceLm.depth = depth;
|
||||||
|
braceLm.lineKind = LineKind::Header;
|
||||||
|
braceLm.foldLevel = computeFoldLevel(depth, true);
|
||||||
|
braceLm.markerMask = lm.markerMask;
|
||||||
|
state.emitLine(fmt::indent(depth) + QStringLiteral("{"), braceLm);
|
||||||
|
} else {
|
||||||
|
state.emitLine(ptrText, lm);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!effectiveCollapsed) {
|
if (!effectiveCollapsed) {
|
||||||
@@ -936,10 +971,11 @@ void composeNode(ComposeState& state, const NodeTree& tree,
|
|||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId,
|
ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId,
|
||||||
bool compactColumns, bool treeLines) {
|
bool compactColumns, bool treeLines, bool braceWrap) {
|
||||||
ComposeState state;
|
ComposeState state;
|
||||||
state.compactColumns = compactColumns;
|
state.compactColumns = compactColumns;
|
||||||
state.treeLines = treeLines;
|
state.treeLines = treeLines;
|
||||||
|
state.braceWrap = braceWrap;
|
||||||
|
|
||||||
// Precompute parent→children map
|
// Precompute parent→children map
|
||||||
for (int i = 0; i < tree.nodes.size(); i++)
|
for (int i = 0; i < tree.nodes.size(); i++)
|
||||||
@@ -1014,6 +1050,9 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
|||||||
|
|
||||||
for (int childIdx : state.childMap.value(container.id)) {
|
for (int childIdx : state.childMap.value(container.id)) {
|
||||||
const Node& child = tree.nodes[childIdx];
|
const Node& child = tree.nodes[childIdx];
|
||||||
|
// Skip struct children — pointer headers shouldn't inflate sibling widths
|
||||||
|
if (child.kind == NodeKind::Struct)
|
||||||
|
continue;
|
||||||
scopeMaxType = qMax(scopeMaxType, (int)nodeTypeName(child).size());
|
scopeMaxType = qMax(scopeMaxType, (int)nodeTypeName(child).size());
|
||||||
|
|
||||||
// Name width (skip hex, but include containers)
|
// Name width (skip hex, but include containers)
|
||||||
@@ -1046,6 +1085,9 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
|||||||
int rootMaxName = kMinNameW;
|
int rootMaxName = kMinNameW;
|
||||||
for (int childIdx : state.childMap.value(0)) {
|
for (int childIdx : state.childMap.value(0)) {
|
||||||
const Node& child = tree.nodes[childIdx];
|
const Node& child = tree.nodes[childIdx];
|
||||||
|
// Skip struct children — pointer headers shouldn't inflate sibling widths
|
||||||
|
if (child.kind == NodeKind::Struct)
|
||||||
|
continue;
|
||||||
rootMaxType = qMax(rootMaxType, (int)nodeTypeName(child).size());
|
rootMaxType = qMax(rootMaxType, (int)nodeTypeName(child).size());
|
||||||
|
|
||||||
// Name width (skip hex, include containers)
|
// Name width (skip hex, include containers)
|
||||||
@@ -1076,6 +1118,18 @@ ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewR
|
|||||||
state.emitLine(cmdRowText, lm);
|
state.emitLine(cmdRowText, lm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Brace wrapping: emit standalone "{" after CommandRow
|
||||||
|
if (state.braceWrap) {
|
||||||
|
LineMeta braceLm;
|
||||||
|
braceLm.nodeIdx = -1;
|
||||||
|
braceLm.nodeId = 0; // not associated with any node (no hover)
|
||||||
|
braceLm.depth = 0;
|
||||||
|
braceLm.lineKind = LineKind::Header;
|
||||||
|
braceLm.foldLevel = SC_FOLDLEVELBASE;
|
||||||
|
braceLm.markerMask = 0;
|
||||||
|
state.emitLine(QStringLiteral("{"), braceLm);
|
||||||
|
}
|
||||||
|
|
||||||
const QVector<int>& roots = childIndices(state, 0);
|
const QVector<int>& roots = childIndices(state, 0);
|
||||||
|
|
||||||
for (int idx : roots) {
|
for (int idx : roots) {
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ RcxDocument::RcxDocument(QObject* parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ComposeResult RcxDocument::compose(uint64_t viewRootId, bool compactColumns,
|
ComposeResult RcxDocument::compose(uint64_t viewRootId, bool compactColumns,
|
||||||
bool treeLines) const {
|
bool treeLines, bool braceWrap) const {
|
||||||
return rcx::compose(tree, *provider, viewRootId, compactColumns, treeLines);
|
return rcx::compose(tree, *provider, viewRootId, compactColumns, treeLines, braceWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RcxDocument::save(const QString& path) {
|
bool RcxDocument::save(const QString& path) {
|
||||||
@@ -558,9 +558,9 @@ void RcxController::refresh() {
|
|||||||
|
|
||||||
// Compose against snapshot provider if active, otherwise real provider
|
// Compose against snapshot provider if active, otherwise real provider
|
||||||
if (m_snapshotProv)
|
if (m_snapshotProv)
|
||||||
m_lastResult = rcx::compose(m_doc->tree, *m_snapshotProv, m_viewRootId, m_compactColumns, m_treeLines);
|
m_lastResult = rcx::compose(m_doc->tree, *m_snapshotProv, m_viewRootId, m_compactColumns, m_treeLines, m_braceWrap);
|
||||||
else
|
else
|
||||||
m_lastResult = m_doc->compose(m_viewRootId, m_compactColumns, m_treeLines);
|
m_lastResult = m_doc->compose(m_viewRootId, m_compactColumns, m_treeLines, m_braceWrap);
|
||||||
|
|
||||||
s_composeDoc = nullptr;
|
s_composeDoc = nullptr;
|
||||||
|
|
||||||
@@ -1766,6 +1766,7 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
QApplication::clipboard()->setText(addrs.join('\n'));
|
QApplication::clipboard()->setText(addrs.join('\n'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
emit contextMenuAboutToShow(&menu, line);
|
||||||
menu.exec(globalPos);
|
menu.exec(globalPos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2282,6 +2283,7 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
QTimer::singleShot(0, editor, &RcxEditor::showFindBar);
|
QTimer::singleShot(0, editor, &RcxEditor::showFindBar);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
emit contextMenuAboutToShow(&menu, line);
|
||||||
menu.exec(globalPos);
|
menu.exec(globalPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2442,6 +2444,7 @@ void RcxController::updateCommandRow() {
|
|||||||
.arg(elide(src, 40), elide(addr, 24));
|
.arg(elide(src, 40), elide(addr, 24));
|
||||||
|
|
||||||
// Build row 2: root class type + name (uses current view root)
|
// Build row 2: root class type + name (uses current view root)
|
||||||
|
QString brace = m_braceWrap ? QString() : QStringLiteral(" {");
|
||||||
QString row2;
|
QString row2;
|
||||||
if (m_viewRootId != 0) {
|
if (m_viewRootId != 0) {
|
||||||
int vi = m_doc->tree.indexOfId(m_viewRootId);
|
int vi = m_doc->tree.indexOfId(m_viewRootId);
|
||||||
@@ -2449,8 +2452,8 @@ 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 %2 {")
|
row2 = QStringLiteral("%1 %2%3")
|
||||||
.arg(keyword, className.isEmpty() ? QStringLiteral("NoName") : className);
|
.arg(keyword, className.isEmpty() ? QStringLiteral("NoName") : className, brace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (row2.isEmpty()) {
|
if (row2.isEmpty()) {
|
||||||
@@ -2460,14 +2463,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 %2 {")
|
row2 = QStringLiteral("%1 %2%3")
|
||||||
.arg(keyword, className.isEmpty() ? QStringLiteral("NoName") : className);
|
.arg(keyword, className.isEmpty() ? QStringLiteral("NoName") : className, brace);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (row2.isEmpty())
|
if (row2.isEmpty())
|
||||||
row2 = QStringLiteral("struct NoName {");
|
row2 = QStringLiteral("struct NoName") + brace;
|
||||||
|
|
||||||
QString combined = QStringLiteral("[\u25B8] ") + row + QStringLiteral(" ") + row2;
|
QString combined = QStringLiteral("[\u25B8] ") + row + QStringLiteral(" ") + row2;
|
||||||
|
|
||||||
@@ -3259,6 +3262,11 @@ void RcxController::setTreeLines(bool v) {
|
|||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RcxController::setBraceWrap(bool v) {
|
||||||
|
m_braceWrap = v;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
void RcxController::setupAutoRefresh() {
|
void RcxController::setupAutoRefresh() {
|
||||||
int ms = QSettings("Reclass", "Reclass").value("refreshMs", 660).toInt();
|
int ms = QSettings("Reclass", "Reclass").value("refreshMs", 660).toInt();
|
||||||
m_refreshTimer = new QTimer(this);
|
m_refreshTimer = new QTimer(this);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
ComposeResult compose(uint64_t viewRootId = 0, bool compactColumns = false,
|
ComposeResult compose(uint64_t viewRootId = 0, bool compactColumns = false,
|
||||||
bool treeLines = false) const;
|
bool treeLines = false, bool braceWrap = false) const;
|
||||||
bool save(const QString& path);
|
bool save(const QString& path);
|
||||||
bool load(const QString& path);
|
bool load(const QString& path);
|
||||||
void loadData(const QString& binaryPath);
|
void loadData(const QString& binaryPath);
|
||||||
@@ -130,6 +130,7 @@ public:
|
|||||||
void setRefreshInterval(int ms);
|
void setRefreshInterval(int ms);
|
||||||
void setCompactColumns(bool v);
|
void setCompactColumns(bool v);
|
||||||
void setTreeLines(bool v);
|
void setTreeLines(bool v);
|
||||||
|
void setBraceWrap(bool v);
|
||||||
void resetProvider();
|
void resetProvider();
|
||||||
|
|
||||||
// MCP bridge accessors
|
// MCP bridge accessors
|
||||||
@@ -158,6 +159,7 @@ public:
|
|||||||
signals:
|
signals:
|
||||||
void nodeSelected(int nodeIdx);
|
void nodeSelected(int nodeIdx);
|
||||||
void selectionChanged(int count);
|
void selectionChanged(int count);
|
||||||
|
void contextMenuAboutToShow(QMenu* menu, int line);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RcxDocument* m_doc;
|
RcxDocument* m_doc;
|
||||||
@@ -168,6 +170,7 @@ private:
|
|||||||
bool m_suppressRefresh = false;
|
bool m_suppressRefresh = false;
|
||||||
bool m_compactColumns = false;
|
bool m_compactColumns = false;
|
||||||
bool m_treeLines = false;
|
bool m_treeLines = false;
|
||||||
|
bool m_braceWrap = false;
|
||||||
uint64_t m_viewRootId = 0;
|
uint64_t m_viewRootId = 0;
|
||||||
|
|
||||||
// ── Saved sources for quick-switch ──
|
// ── Saved sources for quick-switch ──
|
||||||
|
|||||||
@@ -699,7 +699,7 @@ inline constexpr int kColValue = 96;
|
|||||||
inline constexpr int kColComment = 28; // "// Enter=Save Esc=Cancel" fits
|
inline constexpr int kColComment = 28; // "// Enter=Save Esc=Cancel" fits
|
||||||
inline constexpr int kColBaseAddr = 12; // "0x" + up to 10 hex digits (40-bit address)
|
inline constexpr int kColBaseAddr = 12; // "0x" + up to 10 hex digits (40-bit address)
|
||||||
inline constexpr int kSepWidth = 1;
|
inline constexpr int kSepWidth = 1;
|
||||||
inline constexpr int kMinTypeW = 8; // Minimum type column width (fits "uint64_t")
|
inline constexpr int kMinTypeW = 7; // Minimum type column width (fits "uint8_t")
|
||||||
inline constexpr int kMaxTypeW = 128; // Maximum type column width
|
inline constexpr int kMaxTypeW = 128; // Maximum type column width
|
||||||
inline constexpr int kMinNameW = 8; // Minimum name column width (matches ASCII preview)
|
inline constexpr int kMinNameW = 8; // Minimum name column width (matches ASCII preview)
|
||||||
inline constexpr int kMaxNameW = 128; // Maximum name column width
|
inline constexpr int kMaxNameW = 128; // Maximum name column width
|
||||||
@@ -1031,6 +1031,7 @@ namespace fmt {
|
|||||||
// ── Compose function forward declaration ──
|
// ── Compose function forward declaration ──
|
||||||
|
|
||||||
ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId = 0,
|
ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId = 0,
|
||||||
bool compactColumns = false, bool treeLines = false);
|
bool compactColumns = false, bool treeLines = false,
|
||||||
|
bool braceWrap = false);
|
||||||
|
|
||||||
} // namespace rcx
|
} // namespace rcx
|
||||||
|
|||||||
132
src/editor.cpp
132
src/editor.cpp
@@ -23,6 +23,7 @@
|
|||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include "themes/thememanager.h"
|
#include "themes/thememanager.h"
|
||||||
|
|
||||||
@@ -39,18 +40,30 @@ class ValueHistoryPopup : public QFrame {
|
|||||||
QStringList m_values;
|
QStringList m_values;
|
||||||
QVector<QLabel*> m_labels;
|
QVector<QLabel*> m_labels;
|
||||||
std::function<void(const QString&)> m_onSet;
|
std::function<void(const QString&)> m_onSet;
|
||||||
|
std::function<void(QMouseEvent*)> m_onMouseMove;
|
||||||
public:
|
public:
|
||||||
explicit ValueHistoryPopup(QWidget* parent)
|
explicit ValueHistoryPopup(QWidget* parent)
|
||||||
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||||
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
||||||
|
setMouseTracking(true);
|
||||||
setFrameShape(QFrame::NoFrame);
|
setFrameShape(QFrame::NoFrame);
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t nodeId() const { return m_nodeId; }
|
uint64_t nodeId() const { return m_nodeId; }
|
||||||
|
bool hasButtons() const { return m_hasButtons; }
|
||||||
void setOnSet(std::function<void(const QString&)> fn) { m_onSet = std::move(fn); }
|
void setOnSet(std::function<void(const QString&)> fn) { m_onSet = std::move(fn); }
|
||||||
|
void setOnMouseMove(std::function<void(QMouseEvent*)> fn) { m_onMouseMove = std::move(fn); }
|
||||||
|
protected:
|
||||||
|
void mouseMoveEvent(QMouseEvent* e) override {
|
||||||
|
if (!m_hasButtons && m_onMouseMove)
|
||||||
|
m_onMouseMove(e);
|
||||||
|
else
|
||||||
|
QFrame::mouseMoveEvent(e);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
|
||||||
void populate(uint64_t nodeId, const ValueHistory& hist, const QFont& font,
|
void populate(uint64_t nodeId, const ValueHistory& hist, const QFont& font,
|
||||||
bool showButtons = false) {
|
bool showButtons = false) {
|
||||||
@@ -184,12 +197,14 @@ class DisasmPopup : public QFrame {
|
|||||||
QString m_body;
|
QString m_body;
|
||||||
QLabel* m_titleLabel = nullptr;
|
QLabel* m_titleLabel = nullptr;
|
||||||
QLabel* m_bodyLabel = nullptr;
|
QLabel* m_bodyLabel = nullptr;
|
||||||
|
std::function<void(QMouseEvent*)> m_onMouseMove;
|
||||||
public:
|
public:
|
||||||
explicit DisasmPopup(QWidget* parent)
|
explicit DisasmPopup(QWidget* parent)
|
||||||
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||||
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
||||||
|
setMouseTracking(true);
|
||||||
setFrameShape(QFrame::NoFrame);
|
setFrameShape(QFrame::NoFrame);
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
|
|
||||||
@@ -215,8 +230,14 @@ public:
|
|||||||
vbox->addWidget(m_bodyLabel);
|
vbox->addWidget(m_bodyLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setOnMouseMove(std::function<void(QMouseEvent*)> fn) { m_onMouseMove = std::move(fn); }
|
||||||
uint64_t nodeId() const { return m_nodeId; }
|
uint64_t nodeId() const { return m_nodeId; }
|
||||||
|
protected:
|
||||||
|
void mouseMoveEvent(QMouseEvent* e) override {
|
||||||
|
if (m_onMouseMove) m_onMouseMove(e);
|
||||||
|
else QFrame::mouseMoveEvent(e);
|
||||||
|
}
|
||||||
|
public:
|
||||||
void populate(uint64_t nodeId, const QString& title, const QString& body,
|
void populate(uint64_t nodeId, const QString& title, const QString& body,
|
||||||
const QFont& font) {
|
const QFont& font) {
|
||||||
if (nodeId == m_nodeId && body == m_body && isVisible())
|
if (nodeId == m_nodeId && body == m_body && isVisible())
|
||||||
@@ -282,12 +303,14 @@ class StructPreviewPopup : public QFrame {
|
|||||||
QString m_body;
|
QString m_body;
|
||||||
QLabel* m_titleLabel = nullptr;
|
QLabel* m_titleLabel = nullptr;
|
||||||
QLabel* m_bodyLabel = nullptr;
|
QLabel* m_bodyLabel = nullptr;
|
||||||
|
std::function<void(QMouseEvent*)> m_onMouseMove;
|
||||||
public:
|
public:
|
||||||
explicit StructPreviewPopup(QWidget* parent)
|
explicit StructPreviewPopup(QWidget* parent)
|
||||||
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
: QFrame(parent, Qt::ToolTip | Qt::FramelessWindowHint)
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||||
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
||||||
|
setMouseTracking(true);
|
||||||
setFrameShape(QFrame::NoFrame);
|
setFrameShape(QFrame::NoFrame);
|
||||||
setAutoFillBackground(true);
|
setAutoFillBackground(true);
|
||||||
|
|
||||||
@@ -314,7 +337,13 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t nodeId() const { return m_nodeId; }
|
uint64_t nodeId() const { return m_nodeId; }
|
||||||
|
void setOnMouseMove(std::function<void(QMouseEvent*)> fn) { m_onMouseMove = std::move(fn); }
|
||||||
|
protected:
|
||||||
|
void mouseMoveEvent(QMouseEvent* e) override {
|
||||||
|
if (m_onMouseMove) m_onMouseMove(e);
|
||||||
|
else QFrame::mouseMoveEvent(e);
|
||||||
|
}
|
||||||
|
public:
|
||||||
void populate(uint64_t nodeId, const QString& title, const QString& body,
|
void populate(uint64_t nodeId, const QString& title, const QString& body,
|
||||||
const QFont& font) {
|
const QFont& font) {
|
||||||
if (nodeId == m_nodeId && body == m_body && isVisible())
|
if (nodeId == m_nodeId && body == m_body && isVisible())
|
||||||
@@ -938,9 +967,9 @@ void RcxEditor::applyDocument(const ComposeResult& result) {
|
|||||||
int maxLen = 0;
|
int maxLen = 0;
|
||||||
const QStringList lines = result.text.split(QChar('\n'));
|
const QStringList lines = result.text.split(QChar('\n'));
|
||||||
for (const auto& line : lines) {
|
for (const auto& line : lines) {
|
||||||
int len = line.size();
|
int len = (int)line.size();
|
||||||
while (len > 0 && line[len - 1] == QChar(' ')) --len;
|
while (len > 0 && line[len - 1] == QChar(' ')) --len;
|
||||||
if (len > maxLen) maxLen = len;
|
maxLen = std::max(len, maxLen);
|
||||||
}
|
}
|
||||||
QFontMetrics fm(editorFont());
|
QFontMetrics fm(editorFont());
|
||||||
int pixelWidth = fm.horizontalAdvance(QString(maxLen, QChar('0')));
|
int pixelWidth = fm.horizontalAdvance(QString(maxLen, QChar('0')));
|
||||||
@@ -2547,8 +2576,10 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line, int col) {
|
|||||||
m_editState.commentCol = -1;
|
m_editState.commentCol = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable Scintilla undo during inline edit
|
// Keep undo collection enabled during inline edit so CellBuffer::DeleteChars
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETUNDOCOLLECTION, (long)0);
|
// returns valid text pointers (collectingUndo=false returns nullptr, which
|
||||||
|
// crashes QsciAccessibleBase::textDeleted). We clear the buffer on edit end.
|
||||||
|
m_sci->SendScintilla(QsciScintillaBase::SCI_SETUNDOCOLLECTION, (long)1);
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETCARETWIDTH, 1);
|
m_sci->SendScintilla(QsciScintillaBase::SCI_SETCARETWIDTH, 1);
|
||||||
m_sci->setReadOnly(false);
|
m_sci->setReadOnly(false);
|
||||||
|
|
||||||
@@ -2999,8 +3030,26 @@ void RcxEditor::applyHoverCursor() {
|
|||||||
if (lm.heatLevel > 0 && lm.nodeId != 0) {
|
if (lm.heatLevel > 0 && lm.nodeId != 0) {
|
||||||
auto it = m_valueHistory->find(lm.nodeId);
|
auto it = m_valueHistory->find(lm.nodeId);
|
||||||
if (it != m_valueHistory->end() && it->uniqueCount() > 1) {
|
if (it != m_valueHistory->end() && it->uniqueCount() > 1) {
|
||||||
if (!m_historyPopup)
|
if (!m_historyPopup) {
|
||||||
m_historyPopup = new ValueHistoryPopup(this);
|
m_historyPopup = new ValueHistoryPopup(this);
|
||||||
|
static_cast<ValueHistoryPopup*>(m_historyPopup)->setOnMouseMove([this](QMouseEvent* e) {
|
||||||
|
QPoint gp = e->globalPosition().toPoint();
|
||||||
|
QPoint vp = m_sci->viewport()->mapFromGlobal(gp);
|
||||||
|
m_lastHoverPos = vp;
|
||||||
|
m_hoverInside = m_sci->viewport()->rect().contains(vp);
|
||||||
|
if (!m_editState.active) {
|
||||||
|
auto h2 = hitTest(m_lastHoverPos);
|
||||||
|
uint64_t nid = (m_hoverInside && h2.line >= 0) ? h2.nodeId : 0;
|
||||||
|
int nln = (m_hoverInside && h2.line >= 0) ? h2.line : -1;
|
||||||
|
if (nid != m_hoveredNodeId || nln != m_hoveredLine) {
|
||||||
|
m_hoveredNodeId = nid;
|
||||||
|
m_hoveredLine = nln;
|
||||||
|
applyHoverHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyHoverCursor();
|
||||||
|
});
|
||||||
|
}
|
||||||
auto* popup = static_cast<ValueHistoryPopup*>(m_historyPopup);
|
auto* popup = static_cast<ValueHistoryPopup*>(m_historyPopup);
|
||||||
popup->setOnSet([this](const QString& val) {
|
popup->setOnSet([this](const QString& val) {
|
||||||
if (!m_editState.active) return;
|
if (!m_editState.active) return;
|
||||||
@@ -3160,8 +3209,26 @@ void RcxEditor::applyHoverCursor() {
|
|||||||
QString lineText = getLineText(m_sci, h.line);
|
QString lineText = getLineText(m_sci, h.line);
|
||||||
ColumnSpan vs = valueSpan(lm, lineText.size(), lm.effectiveTypeW, lm.effectiveNameW);
|
ColumnSpan vs = valueSpan(lm, lineText.size(), lm.effectiveTypeW, lm.effectiveNameW);
|
||||||
if (vs.valid && h.col >= vs.start && h.col < vs.end) {
|
if (vs.valid && h.col >= vs.start && h.col < vs.end) {
|
||||||
if (!m_historyPopup)
|
if (!m_historyPopup) {
|
||||||
m_historyPopup = new ValueHistoryPopup(this);
|
m_historyPopup = new ValueHistoryPopup(this);
|
||||||
|
static_cast<ValueHistoryPopup*>(m_historyPopup)->setOnMouseMove([this](QMouseEvent* e) {
|
||||||
|
QPoint gp = e->globalPosition().toPoint();
|
||||||
|
QPoint vp = m_sci->viewport()->mapFromGlobal(gp);
|
||||||
|
m_lastHoverPos = vp;
|
||||||
|
m_hoverInside = m_sci->viewport()->rect().contains(vp);
|
||||||
|
if (!m_editState.active) {
|
||||||
|
auto h2 = hitTest(m_lastHoverPos);
|
||||||
|
uint64_t nid = (m_hoverInside && h2.line >= 0) ? h2.nodeId : 0;
|
||||||
|
int nln = (m_hoverInside && h2.line >= 0) ? h2.line : -1;
|
||||||
|
if (nid != m_hoveredNodeId || nln != m_hoveredLine) {
|
||||||
|
m_hoveredNodeId = nid;
|
||||||
|
m_hoveredLine = nln;
|
||||||
|
applyHoverHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyHoverCursor();
|
||||||
|
});
|
||||||
|
}
|
||||||
auto* popup = static_cast<ValueHistoryPopup*>(m_historyPopup);
|
auto* popup = static_cast<ValueHistoryPopup*>(m_historyPopup);
|
||||||
popup->populate(lm.nodeId, *it, editorFont(), false);
|
popup->populate(lm.nodeId, *it, editorFont(), false);
|
||||||
long linePos = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE,
|
long linePos = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE,
|
||||||
@@ -3245,8 +3312,26 @@ void RcxEditor::applyHoverCursor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!body.isEmpty()) {
|
if (!body.isEmpty()) {
|
||||||
if (!m_disasmPopup)
|
if (!m_disasmPopup) {
|
||||||
m_disasmPopup = new DisasmPopup(this);
|
m_disasmPopup = new DisasmPopup(this);
|
||||||
|
static_cast<DisasmPopup*>(m_disasmPopup)->setOnMouseMove([this](QMouseEvent* e) {
|
||||||
|
QPoint gp = e->globalPosition().toPoint();
|
||||||
|
QPoint vp = m_sci->viewport()->mapFromGlobal(gp);
|
||||||
|
m_lastHoverPos = vp;
|
||||||
|
m_hoverInside = m_sci->viewport()->rect().contains(vp);
|
||||||
|
if (!m_editState.active) {
|
||||||
|
auto h2 = hitTest(m_lastHoverPos);
|
||||||
|
uint64_t nid = (m_hoverInside && h2.line >= 0) ? h2.nodeId : 0;
|
||||||
|
int nln = (m_hoverInside && h2.line >= 0) ? h2.line : -1;
|
||||||
|
if (nid != m_hoveredNodeId || nln != m_hoveredLine) {
|
||||||
|
m_hoveredNodeId = nid;
|
||||||
|
m_hoveredLine = nln;
|
||||||
|
applyHoverHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyHoverCursor();
|
||||||
|
});
|
||||||
|
}
|
||||||
auto* popup = static_cast<DisasmPopup*>(
|
auto* popup = static_cast<DisasmPopup*>(
|
||||||
m_disasmPopup);
|
m_disasmPopup);
|
||||||
popup->populate(lm.nodeId, title, body,
|
popup->populate(lm.nodeId, title, body,
|
||||||
@@ -3314,8 +3399,26 @@ void RcxEditor::applyHoverCursor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!body.isEmpty()) {
|
if (!body.isEmpty()) {
|
||||||
if (!m_structPreviewPopup)
|
if (!m_structPreviewPopup) {
|
||||||
m_structPreviewPopup = new StructPreviewPopup(this);
|
m_structPreviewPopup = new StructPreviewPopup(this);
|
||||||
|
static_cast<StructPreviewPopup*>(m_structPreviewPopup)->setOnMouseMove([this](QMouseEvent* e) {
|
||||||
|
QPoint gp = e->globalPosition().toPoint();
|
||||||
|
QPoint vp = m_sci->viewport()->mapFromGlobal(gp);
|
||||||
|
m_lastHoverPos = vp;
|
||||||
|
m_hoverInside = m_sci->viewport()->rect().contains(vp);
|
||||||
|
if (!m_editState.active) {
|
||||||
|
auto h2 = hitTest(m_lastHoverPos);
|
||||||
|
uint64_t nid = (m_hoverInside && h2.line >= 0) ? h2.nodeId : 0;
|
||||||
|
int nln = (m_hoverInside && h2.line >= 0) ? h2.line : -1;
|
||||||
|
if (nid != m_hoveredNodeId || nln != m_hoveredLine) {
|
||||||
|
m_hoveredNodeId = nid;
|
||||||
|
m_hoveredLine = nln;
|
||||||
|
applyHoverHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyHoverCursor();
|
||||||
|
});
|
||||||
|
}
|
||||||
auto* popup = static_cast<StructPreviewPopup*>(m_structPreviewPopup);
|
auto* popup = static_cast<StructPreviewPopup*>(m_structPreviewPopup);
|
||||||
popup->populate(lm.nodeId,
|
popup->populate(lm.nodeId,
|
||||||
lm.pointerTargetName, body, editorFont());
|
lm.pointerTargetName, body, editorFont());
|
||||||
@@ -3460,14 +3563,8 @@ void RcxEditor::setCommandRowText(const QString& line) {
|
|||||||
long savedPos = m_sci->SendScintilla(QsciScintillaBase::SCI_GETCURRENTPOS);
|
long savedPos = m_sci->SendScintilla(QsciScintillaBase::SCI_GETCURRENTPOS);
|
||||||
long savedAnchor = m_sci->SendScintilla(QsciScintillaBase::SCI_GETANCHOR);
|
long savedAnchor = m_sci->SendScintilla(QsciScintillaBase::SCI_GETANCHOR);
|
||||||
|
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETUNDOCOLLECTION, 0);
|
|
||||||
m_sci->setReadOnly(false);
|
m_sci->setReadOnly(false);
|
||||||
|
|
||||||
// Suppress modification notifications during replace to avoid
|
|
||||||
// QScintilla accessibility crash (textDeleted called with null text).
|
|
||||||
long savedMask = m_sci->SendScintilla(QsciScintillaBase::SCI_GETMODEVENTMASK);
|
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETMODEVENTMASK, 0);
|
|
||||||
|
|
||||||
long start = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, 0);
|
long start = m_sci->SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, 0);
|
||||||
long end = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLINEENDPOSITION, 0);
|
long end = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLINEENDPOSITION, 0);
|
||||||
QByteArray utf8 = s.toUtf8();
|
QByteArray utf8 = s.toUtf8();
|
||||||
@@ -3476,15 +3573,12 @@ void RcxEditor::setCommandRowText(const QString& line) {
|
|||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETEND, end);
|
m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETEND, end);
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_REPLACETARGET, (uintptr_t)utf8.size(), utf8.constData());
|
m_sci->SendScintilla(QsciScintillaBase::SCI_REPLACETARGET, (uintptr_t)utf8.size(), utf8.constData());
|
||||||
|
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETMODEVENTMASK, savedMask);
|
|
||||||
|
|
||||||
// Adjust saved cursor/anchor for length change in line 0
|
// Adjust saved cursor/anchor for length change in line 0
|
||||||
long delta = (long)utf8.size() - oldLen;
|
long delta = (long)utf8.size() - oldLen;
|
||||||
if (savedPos > end) savedPos += delta;
|
if (savedPos > end) savedPos += delta;
|
||||||
if (savedAnchor > end) savedAnchor += delta;
|
if (savedAnchor > end) savedAnchor += delta;
|
||||||
|
|
||||||
if (wasReadOnly) m_sci->setReadOnly(true);
|
if (wasReadOnly) m_sci->setReadOnly(true);
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETUNDOCOLLECTION, 1);
|
|
||||||
if (!wasModified) m_sci->SendScintilla(QsciScintillaBase::SCI_SETSAVEPOINT);
|
if (!wasModified) m_sci->SendScintilla(QsciScintillaBase::SCI_SETSAVEPOINT);
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETCURRENTPOS, savedPos);
|
m_sci->SendScintilla(QsciScintillaBase::SCI_SETCURRENTPOS, savedPos);
|
||||||
m_sci->SendScintilla(QsciScintillaBase::SCI_SETANCHOR, savedAnchor);
|
m_sci->SendScintilla(QsciScintillaBase::SCI_SETANCHOR, savedAnchor);
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ public:
|
|||||||
void restoreViewState(const ViewState& vs);
|
void restoreViewState(const ViewState& vs);
|
||||||
|
|
||||||
QsciScintilla* scintilla() const { return m_sci; }
|
QsciScintilla* scintilla() const { return m_sci; }
|
||||||
|
QWidget* historyPopup() const { return m_historyPopup; }
|
||||||
|
QWidget* disasmPopup() const { return m_disasmPopup; }
|
||||||
QWidget* structPreviewPopup() const { return m_structPreviewPopup; }
|
QWidget* structPreviewPopup() const { return m_structPreviewPopup; }
|
||||||
const LineMeta* metaForLine(int line) const;
|
const LineMeta* metaForLine(int line) const;
|
||||||
int currentNodeIndex() const;
|
int currentNodeIndex() const;
|
||||||
|
|||||||
1172
src/main.cpp
1172
src/main.cpp
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,6 @@
|
|||||||
#include "pluginmanager.h"
|
#include "pluginmanager.h"
|
||||||
#include "scannerpanel.h"
|
#include "scannerpanel.h"
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QMdiArea>
|
|
||||||
#include <QMdiSubWindow>
|
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
@@ -72,22 +70,19 @@ public:
|
|||||||
void clearMcpStatus();
|
void clearMcpStatus();
|
||||||
|
|
||||||
// Project Lifecycle API
|
// Project Lifecycle API
|
||||||
QMdiSubWindow* project_new(const QString& classKeyword = QString());
|
QDockWidget* project_new(const QString& classKeyword = QString());
|
||||||
QMdiSubWindow* project_open(const QString& path = {});
|
QDockWidget* project_open(const QString& path = {});
|
||||||
bool project_save(QMdiSubWindow* sub = nullptr, bool saveAs = false);
|
bool project_save(QDockWidget* dock = nullptr, bool saveAs = false);
|
||||||
void project_close(QMdiSubWindow* sub = nullptr);
|
void project_close(QDockWidget* dock = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum ViewMode { VM_Reclass, VM_Rendered };
|
enum ViewMode { VM_Reclass, VM_Rendered };
|
||||||
|
|
||||||
QMdiArea* m_mdiArea;
|
QWidget* m_centralPlaceholder;
|
||||||
ShimmerLabel* m_statusLabel;
|
ShimmerLabel* m_statusLabel;
|
||||||
QString m_appStatus;
|
QString m_appStatus;
|
||||||
bool m_mcpBusy = false;
|
bool m_mcpBusy = false;
|
||||||
QTimer* m_mcpClearTimer = nullptr;
|
QTimer* m_mcpClearTimer = nullptr;
|
||||||
QButtonGroup* m_viewBtnGroup = nullptr;
|
|
||||||
QPushButton* m_btnReclass = nullptr;
|
|
||||||
QPushButton* m_btnRendered = nullptr;
|
|
||||||
TitleBarWidget* m_titleBar = nullptr;
|
TitleBarWidget* m_titleBar = nullptr;
|
||||||
QMenuBar* m_menuBar = nullptr;
|
QMenuBar* m_menuBar = nullptr;
|
||||||
bool m_menuBarTitleCase = false;
|
bool m_menuBarTitleCase = false;
|
||||||
@@ -95,7 +90,6 @@ private:
|
|||||||
PluginManager m_pluginManager;
|
PluginManager m_pluginManager;
|
||||||
McpBridge* m_mcp = nullptr;
|
McpBridge* m_mcp = nullptr;
|
||||||
QAction* m_mcpAction = nullptr;
|
QAction* m_mcpAction = nullptr;
|
||||||
QAction* m_removeSplitAction = nullptr;
|
|
||||||
QMenu* m_sourceMenu = nullptr;
|
QMenu* m_sourceMenu = nullptr;
|
||||||
QMenu* m_recentFilesMenu = nullptr;
|
QMenu* m_recentFilesMenu = nullptr;
|
||||||
|
|
||||||
@@ -117,7 +111,9 @@ private:
|
|||||||
QVector<SplitPane> panes;
|
QVector<SplitPane> panes;
|
||||||
int activePaneIdx = 0;
|
int activePaneIdx = 0;
|
||||||
};
|
};
|
||||||
QMap<QMdiSubWindow*, TabState> m_tabs;
|
QMap<QDockWidget*, TabState> m_tabs;
|
||||||
|
QVector<QDockWidget*> m_docDocks; // ordered list for tabByIndex
|
||||||
|
QDockWidget* m_activeDocDock = nullptr; // tracks active document dock
|
||||||
QVector<RcxDocument*> m_allDocs; // all open docs, shared with controllers
|
QVector<RcxDocument*> m_allDocs; // all open docs, shared with controllers
|
||||||
void rebuildAllDocs();
|
void rebuildAllDocs();
|
||||||
|
|
||||||
@@ -134,8 +130,10 @@ private:
|
|||||||
TabState* activeTab();
|
TabState* activeTab();
|
||||||
TabState* tabByIndex(int index);
|
TabState* tabByIndex(int index);
|
||||||
int tabCount() const { return m_tabs.size(); }
|
int tabCount() const { return m_tabs.size(); }
|
||||||
QMdiSubWindow* createTab(RcxDocument* doc);
|
QDockWidget* createTab(RcxDocument* doc);
|
||||||
|
void setupDockTabBars();
|
||||||
void updateWindowTitle();
|
void updateWindowTitle();
|
||||||
|
void closeAllDocDocks();
|
||||||
|
|
||||||
void setViewMode(ViewMode mode);
|
void setViewMode(ViewMode mode);
|
||||||
void updateRenderedView(TabState& tab, SplitPane& pane);
|
void updateRenderedView(TabState& tab, SplitPane& pane);
|
||||||
@@ -145,7 +143,6 @@ private:
|
|||||||
|
|
||||||
SplitPane createSplitPane(TabState& tab);
|
SplitPane createSplitPane(TabState& tab);
|
||||||
void applyTheme(const Theme& theme);
|
void applyTheme(const Theme& theme);
|
||||||
void styleTabCloseButtons();
|
|
||||||
void syncViewButtons(ViewMode mode);
|
void syncViewButtons(ViewMode mode);
|
||||||
SplitPane* findPaneByTabWidget(QTabWidget* tw);
|
SplitPane* findPaneByTabWidget(QTabWidget* tw);
|
||||||
SplitPane* findActiveSplitPane();
|
SplitPane* findActiveSplitPane();
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ OptionsDialog::OptionsDialog(const OptionsResult& current, QWidget* parent)
|
|||||||
m_showIconCheck->setChecked(current.showIcon);
|
m_showIconCheck->setChecked(current.showIcon);
|
||||||
visualLayout->addRow(m_showIconCheck);
|
visualLayout->addRow(m_showIconCheck);
|
||||||
|
|
||||||
|
m_braceWrapCheck = new QCheckBox("Opening brace on new line");
|
||||||
|
m_braceWrapCheck->setChecked(current.braceWrap);
|
||||||
|
visualLayout->addRow(m_braceWrapCheck);
|
||||||
|
|
||||||
generalLayout->addWidget(visualGroup);
|
generalLayout->addWidget(visualGroup);
|
||||||
generalLayout->addStretch();
|
generalLayout->addStretch();
|
||||||
|
|
||||||
@@ -212,6 +216,7 @@ OptionsResult OptionsDialog::result() const {
|
|||||||
r.autoStartMcp = m_autoMcpCheck->isChecked();
|
r.autoStartMcp = m_autoMcpCheck->isChecked();
|
||||||
r.refreshMs = m_refreshSpin->value();
|
r.refreshMs = m_refreshSpin->value();
|
||||||
r.generatorAsserts = m_assertCheck->isChecked();
|
r.generatorAsserts = m_assertCheck->isChecked();
|
||||||
|
r.braceWrap = m_braceWrapCheck->isChecked();
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ struct OptionsResult {
|
|||||||
bool autoStartMcp = true;
|
bool autoStartMcp = true;
|
||||||
int refreshMs = 660;
|
int refreshMs = 660;
|
||||||
bool generatorAsserts = false;
|
bool generatorAsserts = false;
|
||||||
|
bool braceWrap = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class OptionsDialog : public QDialog {
|
class OptionsDialog : public QDialog {
|
||||||
@@ -41,6 +42,7 @@ private:
|
|||||||
QCheckBox* m_autoMcpCheck = nullptr;
|
QCheckBox* m_autoMcpCheck = nullptr;
|
||||||
QSpinBox* m_refreshSpin = nullptr;
|
QSpinBox* m_refreshSpin = nullptr;
|
||||||
QCheckBox* m_assertCheck = nullptr;
|
QCheckBox* m_assertCheck = nullptr;
|
||||||
|
QCheckBox* m_braceWrapCheck = nullptr;
|
||||||
|
|
||||||
// searchable keywords per leaf tree item
|
// searchable keywords per leaf tree item
|
||||||
QHash<QTreeWidgetItem*, QStringList> m_pageKeywords;
|
QHash<QTreeWidgetItem*, QStringList> m_pageKeywords;
|
||||||
|
|||||||
@@ -60,5 +60,9 @@
|
|||||||
<file alias="search.svg">vsicons/search.svg</file>
|
<file alias="search.svg">vsicons/search.svg</file>
|
||||||
<file alias="regex.svg">vsicons/regex.svg</file>
|
<file alias="regex.svg">vsicons/regex.svg</file>
|
||||||
<file alias="refresh.svg">vsicons/refresh.svg</file>
|
<file alias="refresh.svg">vsicons/refresh.svg</file>
|
||||||
|
<file alias="pin.svg">vsicons/pin.svg</file>
|
||||||
|
<file alias="pinned.svg">vsicons/pinned.svg</file>
|
||||||
|
<file alias="close-all.svg">vsicons/close-all.svg</file>
|
||||||
|
<file alias="split-vertical.svg">vsicons/split-vertical.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ ScannerPanel::ScannerPanel(QWidget* parent)
|
|||||||
QStringLiteral("Copy Address"), this);
|
QStringLiteral("Copy Address"), this);
|
||||||
m_copyBtn->setEnabled(false);
|
m_copyBtn->setEnabled(false);
|
||||||
actionRow->addWidget(m_copyBtn);
|
actionRow->addWidget(m_copyBtn);
|
||||||
|
actionRow->addSpacing(20); // room for resize grip when floating
|
||||||
|
|
||||||
mainLayout->addLayout(actionRow);
|
mainLayout->addLayout(actionRow);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace rcx {
|
|||||||
struct TabInfo {
|
struct TabInfo {
|
||||||
const NodeTree* tree;
|
const NodeTree* tree;
|
||||||
QString name;
|
QString name;
|
||||||
void* subPtr; // QMdiSubWindow* as void*
|
void* subPtr; // QDockWidget* as void*
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sentinel value stored in UserRole+1 to mark the Project group node.
|
// Sentinel value stored in UserRole+1 to mark the Project group node.
|
||||||
|
|||||||
@@ -2768,6 +2768,125 @@ private slots:
|
|||||||
"Static fields should not have a separator line");
|
"Static fields should not have a separator line");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Test: disasm popup dismisses when mouse moves onto it ("see-through") ──
|
||||||
|
//
|
||||||
|
// Scenario: hover a FuncPtr row → disasm popup appears below the row.
|
||||||
|
// User moves mouse down onto the popup. The popup covers rows behind it
|
||||||
|
// but the mouse position maps to a different node's row in the viewport
|
||||||
|
// underneath, so the popup must dismiss.
|
||||||
|
void testDisasmPopupDismissesOnMouseMoveThrough() {
|
||||||
|
NodeTree tree;
|
||||||
|
tree.baseAddress = 0;
|
||||||
|
|
||||||
|
Node root;
|
||||||
|
root.kind = NodeKind::Struct;
|
||||||
|
root.structTypeName = "TestClass";
|
||||||
|
root.name = "TestClass";
|
||||||
|
root.parentId = 0;
|
||||||
|
root.offset = 0;
|
||||||
|
int ri = tree.addNode(root);
|
||||||
|
uint64_t rootId = tree.nodes[ri].id;
|
||||||
|
|
||||||
|
// FuncPtr64 at offset 0 — its value points to "code" at byte 256
|
||||||
|
Node fp;
|
||||||
|
fp.kind = NodeKind::FuncPtr64;
|
||||||
|
fp.name = "VFunc1";
|
||||||
|
fp.parentId = rootId;
|
||||||
|
fp.offset = 0;
|
||||||
|
tree.addNode(fp);
|
||||||
|
|
||||||
|
// A plain UInt64 after it so there's a non-FuncPtr row below
|
||||||
|
Node pad;
|
||||||
|
pad.kind = NodeKind::UInt64;
|
||||||
|
pad.name = "padding";
|
||||||
|
pad.parentId = rootId;
|
||||||
|
pad.offset = 8;
|
||||||
|
tree.addNode(pad);
|
||||||
|
|
||||||
|
// Buffer layout:
|
||||||
|
// [0..7] FuncPtr value = 256 (points to code bytes)
|
||||||
|
// [8..15] padding field value
|
||||||
|
// [256..383] x86 code bytes (push rbp; mov rbp,rsp; nop...; ret)
|
||||||
|
QByteArray data(512, '\0');
|
||||||
|
uint64_t codeAddr = 256;
|
||||||
|
memcpy(data.data(), &codeAddr, 8);
|
||||||
|
const uint8_t code[] = {
|
||||||
|
0x55, // push rbp
|
||||||
|
0x48, 0x89, 0xE5, // mov rbp, rsp
|
||||||
|
0x90, // nop
|
||||||
|
0x90, // nop
|
||||||
|
0x5D, // pop rbp
|
||||||
|
0xC3 // ret
|
||||||
|
};
|
||||||
|
memcpy(data.data() + 256, code, sizeof(code));
|
||||||
|
BufferProvider prov(data, "test_disasm_dismiss");
|
||||||
|
|
||||||
|
ComposeResult cr = compose(tree, prov);
|
||||||
|
m_editor->applyDocument(cr);
|
||||||
|
m_editor->setProviderRef(&prov, nullptr, &tree);
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
// Find the FuncPtr line
|
||||||
|
int fpLine = -1;
|
||||||
|
for (int i = 0; i < cr.meta.size(); ++i) {
|
||||||
|
if (isFuncPtr(cr.meta[i].nodeKind)) {
|
||||||
|
fpLine = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QVERIFY2(fpLine >= 0, "Could not find FuncPtr64 line in compose output");
|
||||||
|
|
||||||
|
// Hover over the FuncPtr value column to trigger the disasm popup
|
||||||
|
const LineMeta& lm = cr.meta[fpLine];
|
||||||
|
QString lineText;
|
||||||
|
{
|
||||||
|
long len = m_editor->scintilla()->SendScintilla(
|
||||||
|
QsciScintillaBase::SCI_LINELENGTH, (unsigned long)fpLine);
|
||||||
|
QByteArray buf(len + 1, '\0');
|
||||||
|
m_editor->scintilla()->SendScintilla(
|
||||||
|
QsciScintillaBase::SCI_GETLINE, (uintptr_t)fpLine,
|
||||||
|
static_cast<const char*>(buf.data()));
|
||||||
|
lineText = QString::fromUtf8(buf.left(len));
|
||||||
|
}
|
||||||
|
ColumnSpan vs = m_editor->valueSpan(lm, lineText.size(),
|
||||||
|
lm.effectiveTypeW, lm.effectiveNameW);
|
||||||
|
QVERIFY2(vs.valid, "Value span for FuncPtr line is not valid");
|
||||||
|
|
||||||
|
int hoverCol = (vs.start + vs.end) / 2;
|
||||||
|
QPoint vpFP = colToViewport(m_editor->scintilla(), fpLine, hoverCol);
|
||||||
|
sendMouseMove(m_editor->scintilla()->viewport(), vpFP);
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
QWidget* popup = m_editor->disasmPopup();
|
||||||
|
QVERIFY2(popup && popup->isVisible(),
|
||||||
|
"Disasm popup should be visible after hovering the FuncPtr value");
|
||||||
|
|
||||||
|
// See-through behavior: when the user moves the mouse down from the
|
||||||
|
// viewport onto the popup, the popup's mouseMoveEvent override forwards
|
||||||
|
// the global position back to the viewport hover logic. If the row
|
||||||
|
// underneath the popup represents a different node, the popup dismisses.
|
||||||
|
//
|
||||||
|
// Simulate by sending a MouseMove event to the popup at a global
|
||||||
|
// position that maps to the CommandRow (line 0) — a non-FuncPtr row.
|
||||||
|
// sendEvent triggers the virtual mouseMoveEvent directly.
|
||||||
|
QPoint vpCmdRow = colToViewport(m_editor->scintilla(), 0, hoverCol);
|
||||||
|
QPoint globalCmdRow = m_editor->scintilla()->viewport()->mapToGlobal(vpCmdRow);
|
||||||
|
QPoint localOnPopup = popup->mapFromGlobal(globalCmdRow);
|
||||||
|
QMouseEvent moveOnPopup(QEvent::MouseMove,
|
||||||
|
QPointF(localOnPopup), QPointF(globalCmdRow),
|
||||||
|
Qt::NoButton, Qt::NoButton, Qt::NoModifier);
|
||||||
|
QApplication::sendEvent(popup, &moveOnPopup);
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
QVERIFY2(!popup->isVisible(),
|
||||||
|
"Disasm popup must dismiss when mouseMoveEvent forwards "
|
||||||
|
"to a non-FuncPtr row underneath (see-through behavior)");
|
||||||
|
|
||||||
|
// Restore
|
||||||
|
m_editor->setProviderRef(nullptr, nullptr, nullptr);
|
||||||
|
m_editor->applyDocument(m_result);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
QTEST_MAIN(TestEditor)
|
QTEST_MAIN(TestEditor)
|
||||||
|
|||||||
Reference in New Issue
Block a user