From 879e9f404780080dc5b97e4d09596088dcbc6dc6 Mon Sep 17 00:00:00 2001 From: IChooseYou Date: Mon, 2 Mar 2026 14:53:14 -0700 Subject: [PATCH] fix: global blue highlight, Ctrl+F find bar with prev/next/close buttons - Change QPalette::Highlight from theme.selection to theme.hover globally - RcxEditor find: use SCI_SEARCHINTARGET + INDIC_COMPOSITIONTHICK indicator (selection rendering is disabled, so findFirst was invisible) - Re-apply find indicators after applyDocument() refresh cycle - Add prev/next/close buttons to find bars in both Reclass and C/C++ modes - Buttons styled with hover/pressed states matching tab styling --- src/controller.cpp | 4 +- src/core.h | 5 +- src/editor.cpp | 118 +++++++++++++++++++++++++++++++++++++++------ src/editor.h | 2 + src/main.cpp | 64 +++++++++++++++++++----- src/mainwindow.h | 1 + 6 files changed, 161 insertions(+), 33 deletions(-) diff --git a/src/controller.cpp b/src/controller.cpp index 72c3a2c..087f82a 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -1636,7 +1636,7 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx, act->setToolTip(QStringLiteral("Reset change tracking for selected nodes")); connect(act, &QAction::triggered, this, [this, ids]() { for (uint64_t id : ids) { - m_valueHistory[id].clear(); + m_valueHistory.remove(id); for (auto& lm : m_lastResult.meta) if (lm.nodeId == id) lm.heatLevel = 0; } @@ -1835,7 +1835,7 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx, act->setToolTip(QStringLiteral("Reset change tracking for this node")); act->setEnabled(m_valueHistory.contains(nodeId) && m_valueHistory[nodeId].uniqueCount() > 0); connect(act, &QAction::triggered, this, [this, nodeId]() { - m_valueHistory[nodeId].clear(); + m_valueHistory.remove(nodeId); for (auto& lm : m_lastResult.meta) if (lm.nodeId == nodeId) lm.heatLevel = 0; refresh(); diff --git a/src/core.h b/src/core.h index f0fb7e2..fc3c3c8 100644 --- a/src/core.h +++ b/src/core.h @@ -545,13 +545,12 @@ struct ValueHistory { fn(values[(start + i) % kCapacity]); } - // Iterate with timestamps from oldest to newest + // Iterate with timestamps from newest to oldest template void forEachWithTime(Fn&& fn) const { int n = uniqueCount(); - int start = (head + kCapacity - n) % kCapacity; for (int i = 0; i < n; i++) { - int idx = (start + i) % kCapacity; + int idx = (head + kCapacity - 1 - i) % kCapacity; fn(values[idx], timestamps[idx]); } } diff --git a/src/editor.cpp b/src/editor.cpp index 5edb7fe..1f87499 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -385,6 +385,7 @@ static constexpr int IND_HINT_GREEN = 15; // Green text for hint/comment text static constexpr int IND_LOCAL_OFF = 16; // Dim text for inline local offset in relative mode static constexpr int IND_HEAT_WARM = 17; // Heatmap level 2 (moderate changes) static constexpr int IND_HEAT_HOT = 18; // Heatmap level 3 (frequent changes) +static constexpr int IND_FIND = 19; // Search match highlight static QString g_fontName = "JetBrains Mono"; @@ -402,10 +403,27 @@ RcxEditor::RcxEditor(QWidget* parent) : QWidget(parent) { layout->addWidget(m_sci); // Find bar (hidden by default, shown with Ctrl+F) - m_findBar = new QLineEdit(this); + m_findBarContainer = new QWidget(this); + auto* fbLayout = new QHBoxLayout(m_findBarContainer); + fbLayout->setContentsMargins(4, 0, 0, 0); + fbLayout->setSpacing(2); + auto* findPrevBtn = new QToolButton(m_findBarContainer); + findPrevBtn->setText(QStringLiteral("\u25C0")); + findPrevBtn->setFixedSize(24, 24); + auto* findNextBtn = new QToolButton(m_findBarContainer); + findNextBtn->setText(QStringLiteral("\u25B6")); + findNextBtn->setFixedSize(24, 24); + auto* findCloseBtn = new QToolButton(m_findBarContainer); + findCloseBtn->setText(QStringLiteral("\u2715")); + findCloseBtn->setFixedSize(24, 24); + m_findBar = new QLineEdit(m_findBarContainer); m_findBar->setPlaceholderText(QStringLiteral("Find...")); - m_findBar->setVisible(false); - layout->addWidget(m_findBar); + fbLayout->addWidget(findPrevBtn); + fbLayout->addWidget(findNextBtn); + fbLayout->addWidget(findCloseBtn); + fbLayout->addWidget(m_findBar); + m_findBarContainer->setVisible(false); + layout->addWidget(m_findBarContainer); setupScintilla(); setupLexer(); @@ -422,18 +440,46 @@ RcxEditor::RcxEditor(QWidget* parent) : QWidget(parent) { m_sci->viewport()->installEventFilter(this); m_sci->viewport()->setMouseTracking(true); - // Find bar: live search on text change - connect(m_findBar, &QLineEdit::textChanged, this, [this](const QString& text) { - if (text.isEmpty()) return; - m_sci->findFirst(text, false, false, false, true, true, 0, 0); - }); - // Find bar: Enter jumps to next match (wraps at end) - connect(m_findBar, &QLineEdit::returnPressed, this, [this]() { + // Find bar: indicator-based search (selection is disabled in our Scintilla) + auto doFind = [this](bool forward) { + long docLen = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLENGTH); + m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, (long)IND_FIND); + m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORCLEARRANGE, (long)0, docLen); + QString text = m_findBar->text(); if (text.isEmpty()) return; - if (!m_sci->findNext()) - m_sci->findFirst(text, false, false, false, true, true, 0, 0); - }); + QByteArray needle = text.toUtf8(); + + long startPos = forward ? m_findPos : (m_findPos > 0 ? m_findPos - 1 : docLen); + m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETSTART, startPos); + m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETEND, + forward ? docLen : (long)0); + m_sci->SendScintilla(QsciScintillaBase::SCI_SETSEARCHFLAGS, (long)0); + + long pos = m_sci->SendScintilla(QsciScintillaBase::SCI_SEARCHINTARGET, + (uintptr_t)needle.size(), needle.constData()); + if (pos == -1) { // wrap + m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETSTART, + forward ? (long)0 : docLen); + m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETEND, + forward ? startPos : (long)0); + pos = m_sci->SendScintilla(QsciScintillaBase::SCI_SEARCHINTARGET, + (uintptr_t)needle.size(), needle.constData()); + } + if (pos >= 0) { + m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, (long)IND_FIND); + m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORFILLRANGE, pos, (long)needle.size()); + int line = (int)m_sci->SendScintilla(QsciScintillaBase::SCI_LINEFROMPOSITION, pos); + m_sci->ensureLineVisible(line); + m_sci->SendScintilla(QsciScintillaBase::SCI_GOTOPOS, pos); + m_findPos = pos + (forward ? needle.size() : 0); + } + }; + connect(m_findBar, &QLineEdit::textChanged, this, [doFind]() { doFind(true); }); + connect(m_findBar, &QLineEdit::returnPressed, this, [doFind]() { doFind(true); }); + connect(findNextBtn, &QToolButton::clicked, this, [doFind]() { doFind(true); }); + connect(findPrevBtn, &QToolButton::clicked, this, [doFind]() { doFind(false); }); + connect(findCloseBtn, &QToolButton::clicked, this, [this]() { hideFindBar(); }); // Escape hides find bar { auto* escAction = new QAction(m_findBar); @@ -646,6 +692,12 @@ void RcxEditor::setupScintilla() { m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE, IND_LOCAL_OFF, 17 /*INDIC_TEXTFORE*/); + // Find match highlight — thick underline (avoids box rendering artifacts) + m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETSTYLE, + IND_FIND, 14 /*INDIC_COMPOSITIONTHICK*/); + m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETUNDER, + IND_FIND, (long)1); + } void RcxEditor::setupLexer() { @@ -782,6 +834,8 @@ void RcxEditor::applyTheme(const Theme& theme) { IND_HINT_GREEN, theme.indHintGreen); m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE, IND_LOCAL_OFF, theme.textFaint); + m_sci->SendScintilla(QsciScintillaBase::SCI_INDICSETFORE, + IND_FIND, theme.borderFocused); // Lexer colors m_lexer->setColor(theme.syntaxKeyword, QsciLexerCPP::Keyword); @@ -832,11 +886,17 @@ void RcxEditor::applyTheme(const Theme& theme) { } // Find bar - if (m_findBar) { + if (m_findBarContainer) { m_findBar->setStyleSheet( QStringLiteral("QLineEdit { background: %1; color: %2; border: 1px solid %3;" " padding: 4px 8px; font-size: 13px; }") .arg(theme.backgroundAlt.name(), theme.text.name(), theme.border.name())); + m_findBarContainer->setStyleSheet( + QStringLiteral("QToolButton { background: %1; color: %2; border: 1px solid %3; border-radius: 2px; }" + "QToolButton:hover { background: %4; }" + "QToolButton:pressed { background: %5; }") + .arg(theme.background.name(), theme.text.name(), theme.border.name(), + theme.hover.name(), theme.backgroundAlt.name())); } } @@ -919,6 +979,27 @@ void RcxEditor::applyDocument(const ComposeResult& result) { m_prevHoveredNodeId = 0; m_prevHoveredLine = -1; applyHoverHighlight(); + + // Re-apply find indicator (setText() clears all indicators) + if (m_findBarContainer && m_findBarContainer->isVisible()) { + QString needle = m_findBar->text(); + if (!needle.isEmpty()) { + QByteArray nb = needle.toUtf8(); + long docLen = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLENGTH); + m_sci->SendScintilla(QsciScintillaBase::SCI_SETSEARCHFLAGS, (long)0); + long pos = 0; + while (pos < docLen) { + m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETSTART, pos); + m_sci->SendScintilla(QsciScintillaBase::SCI_SETTARGETEND, docLen); + long found = m_sci->SendScintilla(QsciScintillaBase::SCI_SEARCHINTARGET, + (uintptr_t)nb.size(), nb.constData()); + if (found < 0) break; + m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, (long)IND_FIND); + m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORFILLRANGE, found, (long)nb.size()); + pos = found + nb.size(); + } + } + } } void RcxEditor::applyMarginText(const QVector& meta) { @@ -1300,13 +1381,18 @@ int RcxEditor::currentNodeIndex() const { } void RcxEditor::showFindBar() { - m_findBar->setVisible(true); + m_findBarContainer->setVisible(true); m_findBar->setFocus(); m_findBar->selectAll(); + m_findPos = 0; } void RcxEditor::hideFindBar() { - m_findBar->setVisible(false); + m_findBarContainer->setVisible(false); + long docLen = m_sci->SendScintilla(QsciScintillaBase::SCI_GETLENGTH); + m_sci->SendScintilla(QsciScintillaBase::SCI_SETINDICATORCURRENT, (long)IND_FIND); + m_sci->SendScintilla(QsciScintillaBase::SCI_INDICATORCLEARRANGE, (long)0, docLen); + m_findPos = 0; m_sci->setFocus(); } diff --git a/src/editor.h b/src/editor.h index e3ce0ce..36a750a 100644 --- a/src/editor.h +++ b/src/editor.h @@ -156,7 +156,9 @@ private: const NodeTree* m_disasmTree = nullptr; // ── Find bar ── + QWidget* m_findBarContainer = nullptr; QLineEdit* m_findBar = nullptr; + long m_findPos = 0; void showFindBar(); void hideFindBar(); diff --git a/src/main.cpp b/src/main.cpp index 0dfcc1d..6dc4b8e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -351,7 +351,7 @@ static void applyGlobalTheme(const rcx::Theme& theme) { pal.setColor(QPalette::Text, theme.text); pal.setColor(QPalette::Button, theme.button); pal.setColor(QPalette::ButtonText, theme.text); - pal.setColor(QPalette::Highlight, theme.selection); + pal.setColor(QPalette::Highlight, theme.hover); pal.setColor(QPalette::HighlightedText, theme.text); pal.setColor(QPalette::ToolTipBase, theme.backgroundAlt); pal.setColor(QPalette::ToolTipText, theme.text); @@ -1072,28 +1072,53 @@ MainWindow::SplitPane MainWindow::createSplitPane(TabState& tab) { setupRenderedSci(pane.rendered); rvLayout->addWidget(pane.rendered); - // Find bar (hidden by default) + // Find bar with prev/next buttons (hidden by default) + pane.findContainer = new QWidget; + auto* fcLayout = new QHBoxLayout(pane.findContainer); + fcLayout->setContentsMargins(4, 0, 0, 0); + fcLayout->setSpacing(2); + const auto& fbTheme = ThemeManager::instance().current(); + auto* ccPrevBtn = new QToolButton; + ccPrevBtn->setText(QStringLiteral("\u25C0")); + ccPrevBtn->setFixedSize(24, 24); + auto* ccNextBtn = new QToolButton; + ccNextBtn->setText(QStringLiteral("\u25B6")); + ccNextBtn->setFixedSize(24, 24); + auto* ccCloseBtn = new QToolButton; + ccCloseBtn->setText(QStringLiteral("\u2715")); + ccCloseBtn->setFixedSize(24, 24); + QString btnCss = QStringLiteral( + "QToolButton { background: %1; color: %2; border: 1px solid %3; border-radius: 2px; }" + "QToolButton:hover { background: %4; }" + "QToolButton:pressed { background: %5; }") + .arg(fbTheme.background.name(), fbTheme.text.name(), fbTheme.border.name(), + fbTheme.hover.name(), fbTheme.backgroundAlt.name()); + ccPrevBtn->setStyleSheet(btnCss); + ccNextBtn->setStyleSheet(btnCss); + ccCloseBtn->setStyleSheet(btnCss); pane.findBar = new QLineEdit; pane.findBar->setPlaceholderText("Find..."); - pane.findBar->setVisible(false); - const auto& fbTheme = ThemeManager::instance().current(); pane.findBar->setStyleSheet( QStringLiteral("QLineEdit { background: %1; color: %2; border: 1px solid %3;" " padding: 4px 8px; font-size: 13px; }") - .arg(fbTheme.backgroundAlt.name()) - .arg(fbTheme.text.name()) - .arg(fbTheme.border.name())); - rvLayout->addWidget(pane.findBar); + .arg(fbTheme.backgroundAlt.name(), fbTheme.text.name(), fbTheme.border.name())); + fcLayout->addWidget(ccPrevBtn); + fcLayout->addWidget(ccNextBtn); + fcLayout->addWidget(ccCloseBtn); + fcLayout->addWidget(pane.findBar); + pane.findContainer->setVisible(false); + rvLayout->addWidget(pane.findContainer); // Ctrl+F to show find bar QsciScintilla* sci = pane.rendered; QLineEdit* fb = pane.findBar; + QWidget* fc = pane.findContainer; auto* findAction = new QAction(pane.renderedContainer); findAction->setShortcut(QKeySequence::Find); findAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); pane.renderedContainer->addAction(findAction); - connect(findAction, &QAction::triggered, fb, [fb, sci]() { - fb->setVisible(true); + connect(findAction, &QAction::triggered, fb, [fb, fc]() { + fc->setVisible(true); fb->setFocus(); fb->selectAll(); }); @@ -1103,8 +1128,8 @@ MainWindow::SplitPane MainWindow::createSplitPane(TabState& tab) { escAction->setShortcut(QKeySequence(Qt::Key_Escape)); escAction->setShortcutContext(Qt::WidgetShortcut); fb->addAction(escAction); - connect(escAction, &QAction::triggered, fb, [fb, sci]() { - fb->setVisible(false); + connect(escAction, &QAction::triggered, fb, [fc, sci]() { + fc->setVisible(false); sci->setFocus(); }); @@ -1119,6 +1144,21 @@ MainWindow::SplitPane MainWindow::createSplitPane(TabState& tab) { if (!sci->findNext()) sci->findFirst(text, false, false, false, true, true, 0, 0); }); + connect(ccNextBtn, &QToolButton::clicked, sci, [sci, fb]() { + if (!sci->findNext()) + sci->findFirst(fb->text(), false, false, false, true, true, 0, 0); + }); + connect(ccPrevBtn, &QToolButton::clicked, sci, [sci, fb]() { + QString text = fb->text(); + if (text.isEmpty()) return; + int line, col; + sci->getCursorPosition(&line, &col); + sci->findFirst(text, false, false, false, true, false, line, col); + }); + connect(ccCloseBtn, &QToolButton::clicked, sci, [fc, sci]() { + fc->setVisible(false); + sci->setFocus(); + }); pane.tabWidget->addTab(pane.renderedContainer, "C/C++"); // index 1 diff --git a/src/mainwindow.h b/src/mainwindow.h index aaf31f0..aebb839 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -101,6 +101,7 @@ private: RcxEditor* editor = nullptr; QsciScintilla* rendered = nullptr; QLineEdit* findBar = nullptr; + QWidget* findContainer = nullptr; QWidget* renderedContainer = nullptr; ViewMode viewMode = VM_Reclass; uint64_t lastRenderedRootId = 0;