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 <noreply@anthropic.com>
This commit is contained in:
sysadmin
2026-02-06 13:16:26 -07:00
parent 13d403a568
commit 238d895e83
4 changed files with 145 additions and 5 deletions

View File

@@ -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<QWidget*>(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<QWidget*>(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<SavedSourceDisplay> 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);

View File

@@ -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<SavedSourceEntry> 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

View File

@@ -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();
}

View File

@@ -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<SavedSourceDisplay>& 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<SavedSourceDisplay> m_savedSourceDisplay;
// ── Reentrancy guards ──
bool m_clampingSelection = false;
bool m_updatingComment = false;