diff --git a/src/NotepadNext/NotepadNext.pro b/src/NotepadNext/NotepadNext.pro index 74cd8dfea..598b9fd79 100644 --- a/src/NotepadNext/NotepadNext.pro +++ b/src/NotepadNext/NotepadNext.pro @@ -71,6 +71,7 @@ SOURCES += \ EditorHexViewerTableModel.cpp \ EditorManager.cpp \ EditorPrintPreviewRenderer.cpp \ + SearchResultHighlighterDelegate.cpp \ widgets/FadingIndicator.cpp \ FileDialogHelpers.cpp \ Finder.cpp \ @@ -149,6 +150,8 @@ HEADERS += \ EditorHexViewerTableModel.h \ EditorManager.h \ EditorPrintPreviewRenderer.h \ + SearchResultData.h \ + SearchResultHighlighterDelegate.h \ widgets/FadingIndicator.h \ FileDialogHelpers.h \ Finder.h \ diff --git a/src/NotepadNext/SearchResultData.h b/src/NotepadNext/SearchResultData.h new file mode 100644 index 000000000..23b2894ea --- /dev/null +++ b/src/NotepadNext/SearchResultData.h @@ -0,0 +1,30 @@ +/* + * This file is part of Notepad Next. + * Copyright 2025 Justin Dailey + * + * Notepad Next is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Notepad Next is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Notepad Next. If not, see . + */ + +#pragma once + +#include + +namespace SearchResultData { + enum Role + { + LineNumber = Qt::UserRole, + LinePosStart, + LinePosEnd + }; +} diff --git a/src/NotepadNext/SearchResultHighlighterDelegate.cpp b/src/NotepadNext/SearchResultHighlighterDelegate.cpp new file mode 100644 index 000000000..87cad5490 --- /dev/null +++ b/src/NotepadNext/SearchResultHighlighterDelegate.cpp @@ -0,0 +1,84 @@ +/* + * This file is part of Notepad Next. + * Copyright 2025 Justin Dailey + * + * Notepad Next is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Notepad Next is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Notepad Next. If not, see . + */ + +#include "SearchResultHighlighterDelegate.h" +#include "SearchResultData.h" + +#include + +void SearchResultHighlighterDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + // Only custom paint for column 1 with highlight flag set + if (index.column() != 1 || !index.data(SearchResultData::LineNumber).isValid()) { + QStyledItemDelegate::paint(painter, option, index); + return; + } + + const QString text = index.data(Qt::DisplayRole).toString(); + int start = index.data(SearchResultData::LinePosStart).toInt(); + int end = index.data(SearchResultData::LinePosEnd).toInt(); + + QStyleOptionViewItem opt(option); + initStyleOption(&opt, index); + + painter->save(); + painter->setClipRect(opt.rect); + + // Optional: draw background or selection as usual + if (opt.state & QStyle::State_Selected) { + painter->fillRect(opt.rect, opt.palette.highlight()); + } else { + painter->fillRect(opt.rect, opt.backgroundBrush); + } + + QRect textRect = opt.rect.adjusted(4, 0, -4, 0); // small padding + QFontMetrics fm(opt.font); + int y = textRect.top() + (textRect.height() + fm.ascent() - fm.descent()) / 2; + int x = textRect.left(); + + // Split the text + const QString before = text.left(start); + const QString match = text.mid(start, end - start); + const QString after = text.mid(end); + + // Draw 'before' text (normal) + painter->setFont(opt.font); + painter->setPen(opt.palette.color(QPalette::Text)); + painter->drawText(x, y, before); + x += fm.horizontalAdvance(before); + + // Draw highlighted 'match' text (bold, red, yellow bg) + QFont boldFont = opt.font; + boldFont.setBold(true); + painter->setFont(boldFont); + + int matchWidth = fm.horizontalAdvance(match); + QRect highlightRect(x, textRect.top(), matchWidth, textRect.height()); + painter->fillRect(highlightRect, QColor(Qt::yellow)); + + painter->setPen(Qt::red); + painter->drawText(x, y, match); + x += matchWidth; + + // Draw 'after' text (normal) + painter->setFont(opt.font); + painter->setPen(opt.palette.color(QPalette::Text)); + painter->drawText(x, y, after); + + painter->restore(); +} diff --git a/src/NotepadNext/SearchResultHighlighterDelegate.h b/src/NotepadNext/SearchResultHighlighterDelegate.h new file mode 100644 index 000000000..cbd8414e3 --- /dev/null +++ b/src/NotepadNext/SearchResultHighlighterDelegate.h @@ -0,0 +1,28 @@ +/* + * This file is part of Notepad Next. + * Copyright 2025 Justin Dailey + * + * Notepad Next is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Notepad Next is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Notepad Next. If not, see . + */ + +#pragma once + +#include + +class SearchResultHighlighterDelegate : public QStyledItemDelegate { +public: + SearchResultHighlighterDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {} + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; diff --git a/src/NotepadNext/docks/SearchResultsDock.cpp b/src/NotepadNext/docks/SearchResultsDock.cpp index 08bacd8a0..f686ccee5 100644 --- a/src/NotepadNext/docks/SearchResultsDock.cpp +++ b/src/NotepadNext/docks/SearchResultsDock.cpp @@ -17,6 +17,8 @@ */ +#include "SearchResultHighlighterDelegate.h" +#include "SearchResultData.h" #include "SearchResultsDock.h" #include "ScintillaNext.h" #include "ui_SearchResultsDock.h" @@ -27,12 +29,6 @@ #include #include -enum SearchResultData -{ - LineNumber = Qt::UserRole, - LinePosStart, - LinePosEnd -}; SearchResultsDock::SearchResultsDock(QWidget *parent) : QDockWidget(parent), @@ -71,6 +67,8 @@ SearchResultsDock::SearchResultsDock(QWidget *parent) : menu.exec(QCursor::pos()); }); + + ui->treeWidget->setItemDelegate(new SearchResultHighlighterDelegate(ui->treeWidget)); } SearchResultsDock::~SearchResultsDock() @@ -125,12 +123,13 @@ void SearchResultsDock::newResultsEntry(const QString line, int lineNumber, int // Scintilla internally references line numbers starting at 0, however it needs displayed starting at 1 item->setText(0, QString::number(lineNumber + 1)); - item->setData(0, SearchResultData::LineNumber, lineNumber); - item->setData(0, SearchResultData::LinePosStart, startPositionFromBeginning); - item->setData(0, SearchResultData::LinePosEnd, endPositionFromBeginning); item->setBackground(0, QBrush(QColor(220, 220, 220))); item->setTextAlignment(0, Qt::AlignRight); + item->setData(1, SearchResultData::LineNumber, lineNumber); + item->setData(1, SearchResultData::LinePosStart, startPositionFromBeginning); + item->setData(1, SearchResultData::LinePosEnd, endPositionFromBeginning); + item->setData(1, Qt::UserRole + 3, true); // <- Flag to enable highlight item->setText(1, line); totalFileHitCount += hitCount; @@ -191,9 +190,9 @@ void SearchResultsDock::itemActivated(QTreeWidgetItem *item, int column) // The editor may no longer exist if (editor) { - int lineNumber = item->data(0, SearchResultData::LineNumber).toInt(); - int startPositionFromBeginning = item->data(0, SearchResultData::LinePosStart).toInt(); - int endPositionFromBeginning = item->data(0, SearchResultData::LinePosEnd).toInt(); + int lineNumber = item->data(1, SearchResultData::LineNumber).toInt(); + int startPositionFromBeginning = item->data(1, SearchResultData::LinePosStart).toInt(); + int endPositionFromBeginning = item->data(1, SearchResultData::LinePosEnd).toInt(); emit searchResultActivated(editor, lineNumber, startPositionFromBeginning, endPositionFromBeginning); }