From 238d895e83c1341e833ba8a9f4fa26b142652031 Mon Sep 17 00:00:00 2001 From: sysadmin Date: Fri, 6 Feb 2026 13:16:26 -0700 Subject: [PATCH] Add saved sources with quick-switch in source picker Source picker now remembers loaded files and attached processes below a separator with checkmarks. Clicking a saved source instantly switches back to it, preserving base addresses per-source. Co-Authored-By: Claude Opus 4.6 --- src/controller.cpp | 106 +++++++++++++++++++++++++++++++++++++++++++-- src/controller.h | 17 ++++++++ src/editor.cpp | 16 ++++++- src/editor.h | 11 +++++ 4 files changed, 145 insertions(+), 5 deletions(-) diff --git a/src/controller.cpp b/src/controller.cpp index 068c1de..3bf0d09 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -289,18 +289,79 @@ void RcxController::connectEditor(RcxEditor* editor) { break; } case EditTarget::Source: { - if (text == QStringLiteral("File")) { + if (text.startsWith(QStringLiteral("#saved:"))) { + int idx = text.mid(7).toInt(); + switchToSavedSource(idx); + } else if (text == QStringLiteral("File")) { auto* w = qobject_cast(parent()); QString path = QFileDialog::getOpenFileName(w, "Load Binary Data", {}, "All Files (*)"); - if (!path.isEmpty()) m_doc->loadData(path); + if (!path.isEmpty()) { + // Save current source's base address before switching + if (m_activeSourceIdx >= 0 && m_activeSourceIdx < m_savedSources.size()) + m_savedSources[m_activeSourceIdx].baseAddress = m_doc->tree.baseAddress; + + m_doc->loadData(path); + + // Check if this file is already saved + int existingIdx = -1; + for (int i = 0; i < m_savedSources.size(); i++) { + if (m_savedSources[i].kind == QStringLiteral("File") + && m_savedSources[i].filePath == path) { + existingIdx = i; + break; + } + } + if (existingIdx >= 0) { + m_activeSourceIdx = existingIdx; + m_doc->tree.baseAddress = m_savedSources[existingIdx].baseAddress; + } else { + SavedSourceEntry entry; + entry.kind = QStringLiteral("File"); + entry.displayName = QFileInfo(path).fileName(); + entry.filePath = path; + entry.baseAddress = m_doc->tree.baseAddress; + m_savedSources.append(entry); + m_activeSourceIdx = m_savedSources.size() - 1; + } + refresh(); + } } else if (text == QStringLiteral("Process")) { #ifdef _WIN32 auto* w = qobject_cast(parent()); ProcessPicker picker(w); if (picker.exec() == QDialog::Accepted) { - attachToProcess(picker.selectedProcessId(), - picker.selectedProcessName()); + // Save current source's base address before switching + if (m_activeSourceIdx >= 0 && m_activeSourceIdx < m_savedSources.size()) + m_savedSources[m_activeSourceIdx].baseAddress = m_doc->tree.baseAddress; + + uint32_t pid = picker.selectedProcessId(); + QString procName = picker.selectedProcessName(); + attachToProcess(pid, procName); + + // Check if this process is already saved + int existingIdx = -1; + for (int i = 0; i < m_savedSources.size(); i++) { + if (m_savedSources[i].kind == QStringLiteral("Process") + && m_savedSources[i].pid == pid) { + existingIdx = i; + break; + } + } + if (existingIdx >= 0) { + m_activeSourceIdx = existingIdx; + m_savedSources[existingIdx].baseAddress = m_doc->tree.baseAddress; + } else { + SavedSourceEntry entry; + entry.kind = QStringLiteral("Process"); + entry.displayName = procName; + entry.pid = pid; + entry.processName = procName; + entry.baseAddress = m_doc->tree.baseAddress; + m_savedSources.append(entry); + m_activeSourceIdx = m_savedSources.size() - 1; + } + refresh(); } #endif } @@ -396,6 +457,7 @@ void RcxController::refresh() { editor->restoreViewState(vs); } applySelectionOverlays(); + pushSavedSourcesToEditors(); updateCommandRow(); } @@ -1006,6 +1068,42 @@ void RcxController::attachToProcess(uint32_t pid, const QString& processName) { #endif } +void RcxController::switchToSavedSource(int idx) { + if (idx < 0 || idx >= m_savedSources.size()) return; + if (idx == m_activeSourceIdx) return; + + // Save current source's base address before switching + if (m_activeSourceIdx >= 0 && m_activeSourceIdx < m_savedSources.size()) + m_savedSources[m_activeSourceIdx].baseAddress = m_doc->tree.baseAddress; + + m_activeSourceIdx = idx; + const auto& entry = m_savedSources[idx]; + + if (entry.kind == QStringLiteral("File")) { + m_doc->loadData(entry.filePath); + m_doc->tree.baseAddress = entry.baseAddress; + refresh(); + } else if (entry.kind == QStringLiteral("Process")) { +#ifdef _WIN32 + attachToProcess(entry.pid, entry.processName); +#endif + } +} + +void RcxController::pushSavedSourcesToEditors() { + QVector display; + display.reserve(m_savedSources.size()); + for (int i = 0; i < m_savedSources.size(); i++) { + SavedSourceDisplay d; + d.text = QStringLiteral("%1 '%2'") + .arg(m_savedSources[i].kind, m_savedSources[i].displayName); + d.active = (i == m_activeSourceIdx); + display.append(d); + } + for (auto* editor : m_editors) + editor->setSavedSources(display); +} + void RcxController::handleMarginClick(RcxEditor* editor, int margin, int line, Qt::KeyboardModifiers) { const LineMeta* lm = editor->metaForLine(line); diff --git a/src/controller.h b/src/controller.h index 6a024e8..6c2324b 100644 --- a/src/controller.h +++ b/src/controller.h @@ -48,6 +48,17 @@ private: Command m_cmd; }; +// ── Saved source entry ── + +struct SavedSourceEntry { + QString kind; // "File" or "Process" + QString displayName; // filename or process name + QString filePath; // for File sources + uint32_t pid = 0; // for Process sources + QString processName; // for Process sources + uint64_t baseAddress = 0; +}; + // ── Controller ── class RcxController : public QObject { @@ -96,10 +107,16 @@ private: int m_anchorLine = -1; bool m_suppressRefresh = false; + // ── Saved sources for quick-switch ── + QVector m_savedSources; + int m_activeSourceIdx = -1; + void connectEditor(RcxEditor* editor); void handleMarginClick(RcxEditor* editor, int margin, int line, Qt::KeyboardModifiers mods); void updateCommandRow(); void attachToProcess(uint32_t pid, const QString& processName); + void switchToSavedSource(int idx); + void pushSavedSourcesToEditors(); }; } // namespace rcx diff --git a/src/editor.cpp b/src/editor.cpp index 57b60c3..a4ae9ee 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1453,6 +1453,17 @@ void RcxEditor::showSourcePicker() { menu.addAction("File"); menu.addAction("Process"); + // Saved sources below separator (with checkmarks) + if (!m_savedSourceDisplay.isEmpty()) { + menu.addSeparator(); + for (int i = 0; i < m_savedSourceDisplay.size(); i++) { + auto* act = menu.addAction(m_savedSourceDisplay[i].text); + act->setCheckable(true); + act->setChecked(m_savedSourceDisplay[i].active); + act->setData(i); + } + } + int lineH = (int)m_sci->SendScintilla(QsciScintillaBase::SCI_TEXTHEIGHT, 0); int x = (int)m_sci->SendScintilla(QsciScintillaBase::SCI_POINTXFROMPOSITION, 0, m_editState.posStart); @@ -1463,7 +1474,10 @@ void RcxEditor::showSourcePicker() { QAction* sel = menu.exec(pos); if (sel) { auto info = endInlineEdit(); - emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, sel->text()); + QString text = sel->text(); + if (sel->data().isValid()) + text = QStringLiteral("#saved:") + QString::number(sel->data().toInt()); + emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, text); } else { cancelInlineEdit(); } diff --git a/src/editor.h b/src/editor.h index beada08..2ae339d 100644 --- a/src/editor.h +++ b/src/editor.h @@ -9,6 +9,11 @@ class QsciLexerCPP; namespace rcx { +struct SavedSourceDisplay { + QString text; + bool active = false; +}; + class RcxEditor : public QWidget { Q_OBJECT public: @@ -45,6 +50,9 @@ public: // Custom type names (struct types from the tree) shown in type picker void setCustomTypeNames(const QStringList& names) { m_customTypeNames = names; } + // Saved sources for quick-switch in source picker + void setSavedSources(const QVector& sources) { m_savedSourceDisplay = sources; } + signals: void marginClicked(int margin, int line, Qt::KeyboardModifiers mods); void contextMenuRequested(int line, int nodeIdx, int subLine, QPoint globalPos); @@ -109,6 +117,9 @@ private: // ── Custom type names for type picker ── QStringList m_customTypeNames; + // ── Saved sources for quick-switch ── + QVector m_savedSourceDisplay; + // ── Reentrancy guards ── bool m_clampingSelection = false; bool m_updatingComment = false;