Skip to content

Commit a2a8764

Browse files
committed
Mark token occurrences with specific colors
Closes #748
1 parent 80bf0aa commit a2a8764

File tree

7 files changed

+285
-3
lines changed

7 files changed

+285
-3
lines changed

src/NotepadNext/ActionUtils.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
#include <QObject>
2222
#include <QStringList>
2323
#include <QAction>
24-
#include <QDebug>
24+
#include <QApplication>
25+
#include <QStyle>
26+
#include <QPainter>
2527

2628
namespace ActionUtils {
2729

@@ -43,4 +45,19 @@ void populateActionContainer(Container* container, QObject* context, const QStri
4345
}
4446
}
4547

48+
QIcon createSolidIcon(QColor color)
49+
{
50+
int iconSize = qApp->style()->pixelMetric(QStyle::PM_SmallIconSize);
51+
52+
QPixmap pixmap(iconSize, iconSize);
53+
pixmap.fill(color);
54+
55+
QPainter painter(&pixmap);
56+
painter.setPen(Qt::black); // 1-pixel black pen by default
57+
painter.drawRect(0, 0, iconSize - 1, iconSize - 1); // draw border inside the pixmap
58+
painter.end();
59+
60+
return QIcon(pixmap);
61+
}
62+
4663
}

src/NotepadNext/NotepadNext.pro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ SOURCES += \
7272
EditorManager.cpp \
7373
EditorPrintPreviewRenderer.cpp \
7474
SearchResultHighlighterDelegate.cpp \
75+
decorators/MarkerAppDecorator.cpp \
7576
widgets/FadingIndicator.cpp \
7677
FileDialogHelpers.cpp \
7778
Finder.cpp \
@@ -152,6 +153,7 @@ HEADERS += \
152153
EditorPrintPreviewRenderer.h \
153154
SearchResultData.h \
154155
SearchResultHighlighterDelegate.h \
156+
decorators/MarkerAppDecorator.h \
155157
widgets/FadingIndicator.h \
156158
FileDialogHelpers.h \
157159
Finder.h \

src/NotepadNext/NotepadNextApplication.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "MainWindow.h"
2121
#include "NotepadNextApplication.h"
22+
#include "MarkerAppDecorator.h"
2223
#include "RecentFilesListManager.h"
2324
#include "EditorManager.h"
2425
#include "LuaExtension.h"
@@ -142,6 +143,8 @@ bool NotepadNextApplication::init()
142143

143144
EditorConfigAppDecorator *ecad = new EditorConfigAppDecorator(this);
144145
ecad->setEnabled(true);
146+
MarkerAppDecorator *mad = new MarkerAppDecorator(this);
147+
mad->setEnabled(true);
145148

146149
luaState->executeFile(":/scripts/init.lua");
147150
LuaExtension::Instance().Initialise(luaState->L, Q_NULLPTR);
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* This file is part of Notepad Next.
3+
* Copyright 2025 Justin Dailey
4+
*
5+
* Notepad Next is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* Notepad Next is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with Notepad Next. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "MarkerAppDecorator.h"
20+
#include "EditorManager.h"
21+
#include "ScintillaNext.h"
22+
23+
24+
static QList<QColor> marker_colors = {
25+
QColor(0x00, 0xFF, 0xFF),
26+
QColor(0xFF, 0x80, 0x00),
27+
QColor(0xFF, 0xFF, 0x00)
28+
};
29+
30+
static int QColorToScintillaColour(QColor c)
31+
{
32+
return c.red() | (c.green() << 8) | (c.blue() << 16);
33+
}
34+
35+
MarkerAppDecorator::MarkerAppDecorator(NotepadNextApplication *app)
36+
: ApplicationDecorator(app)
37+
{
38+
// Any time an editor is created go ahead and allocate/set the required indicators
39+
connect(app->getEditorManager(), &EditorManager::editorCreated, this, [](ScintillaNext *editor) {
40+
for (int i = 0; i < marker_colors.size(); i++) {
41+
int indicator = editor->allocateIndicator(QString("marker_%1").arg(i));
42+
editor->indicSetFore(indicator, QColorToScintillaColour(marker_colors[i]));
43+
editor->indicSetStyle(indicator, INDIC_ROUNDBOX);
44+
editor->indicSetOutlineAlpha(indicator, 150);
45+
editor->indicSetAlpha(indicator, 100);
46+
editor->indicSetUnder(indicator, true);
47+
}
48+
});
49+
}
50+
51+
QColor MarkerAppDecorator::markerColor(int i) const
52+
{
53+
return marker_colors[i];
54+
}
55+
56+
void MarkerAppDecorator::mark(ScintillaNext *editor, int i)
57+
{
58+
//const int mainSelection = editor->mainSelection();
59+
//const int selectionStart = editor->selectionNStart(mainSelection);
60+
//const int selectionEnd = editor->selectionNEnd(mainSelection);
61+
62+
const int curPos = editor->currentPos();
63+
const int wordStart = editor->wordStartPosition(curPos, true);
64+
const int wordEnd = editor->wordEndPosition(wordStart, true);
65+
66+
// Make sure the selection is on word boundaries
67+
if (wordStart == wordEnd) {
68+
return;
69+
}
70+
71+
int indicator = editor->allocateIndicator(QString("marker_%1").arg(i));
72+
editor->setIndicatorCurrent(indicator);
73+
74+
const QByteArray selText = editor->get_text_range(wordStart, wordEnd);
75+
Sci_TextToFind ttf {{0, (Sci_PositionCR)editor->length()}, selText.constData(), {-1, -1}};
76+
const int flags = SCFIND_WHOLEWORD;
77+
78+
while (editor->send(SCI_FINDTEXT, flags, (sptr_t)&ttf) != -1) {
79+
editor->indicatorFillRange(ttf.chrgText.cpMin, ttf.chrgText.cpMax - ttf.chrgText.cpMin);
80+
ttf.chrg.cpMin = ttf.chrgText.cpMax;
81+
}
82+
}
83+
84+
void MarkerAppDecorator::clear(ScintillaNext *editor, int i)
85+
{
86+
int indicator = editor->allocateIndicator(QString("marker_%1").arg(i));
87+
editor->setIndicatorCurrent(indicator);
88+
editor->indicatorClearRange(0, editor->length());
89+
}
90+
91+
void MarkerAppDecorator::clearAll(ScintillaNext *editor)
92+
{
93+
for (int i = 0; i < marker_colors.size(); i++) {
94+
clear(editor, i);
95+
}
96+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* This file is part of Notepad Next.
3+
* Copyright 2025 Justin Dailey
4+
*
5+
* Notepad Next is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* Notepad Next is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with Notepad Next. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
#include "ApplicationDecorator.h"
22+
23+
class MarkerAppDecorator : public ApplicationDecorator
24+
{
25+
Q_OBJECT
26+
27+
public:
28+
explicit MarkerAppDecorator(NotepadNextApplication *app);
29+
30+
QColor markerColor(int i) const;
31+
32+
void mark(ScintillaNext *editor, int i);
33+
void clear(ScintillaNext *editor, int i);
34+
void clearAll(ScintillaNext *editor);
35+
};

src/NotepadNext/dialogs/MainWindow.cpp

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "MainWindow.h"
2121
#include "BookMarkDecorator.h"
22+
#include "MarkerAppDecorator.h"
2223
#include "URLFinder.h"
2324
#include "SessionManager.h"
2425
#include "UndoAction.h"
@@ -371,6 +372,53 @@ MainWindow::MainWindow(NotepadNextApplication *app) :
371372
}
372373
});
373374

375+
// Style all actions that have a MarkerNumber and interpret that as the color needed
376+
MarkerAppDecorator *markerAppDecorator = app->findChild<MarkerAppDecorator*>(QString(), Qt::FindDirectChildrenOnly);
377+
for (QAction* action : findChildren<QAction*>()) {
378+
if (action->property("MarkerNumber").isValid()) {
379+
int markerNumber = action->property("MarkerNumber").toInt();
380+
action->setIcon(ActionUtils::createSolidIcon(markerAppDecorator->markerColor(markerNumber)));
381+
}
382+
}
383+
384+
auto mark_callback = [=]() {
385+
MarkerAppDecorator *markerAppDecorator = app->findChild<MarkerAppDecorator*>(QString(), Qt::FindDirectChildrenOnly);
386+
387+
if (markerAppDecorator && markerAppDecorator->isEnabled()) {
388+
if (sender()->property("MarkerNumber").isValid()) {
389+
ScintillaNext *editor = currentEditor();
390+
markerAppDecorator->mark(editor, sender()->property("MarkerNumber").toInt());
391+
}
392+
}
393+
};
394+
395+
auto clear_mark_callback = [=]() {
396+
MarkerAppDecorator *markerAppDecorator = app->findChild<MarkerAppDecorator*>(QString(), Qt::FindDirectChildrenOnly);
397+
398+
if (markerAppDecorator && markerAppDecorator->isEnabled()) {
399+
if (sender()->property("MarkerNumber").isValid()) {
400+
ScintillaNext *editor = currentEditor();
401+
markerAppDecorator->clear(editor, sender()->property("MarkerNumber").toInt());
402+
}
403+
}
404+
};
405+
406+
connect(ui->actionMarkStyle1, &QAction::triggered, this, mark_callback);
407+
connect(ui->actionMarkStyle2, &QAction::triggered, this, mark_callback);
408+
connect(ui->actionMarkStyle3, &QAction::triggered, this, mark_callback);
409+
410+
connect(ui->actionClearStyle1, &QAction::triggered, this, clear_mark_callback);
411+
connect(ui->actionClearStyle2, &QAction::triggered, this, clear_mark_callback);
412+
connect(ui->actionClearStyle3, &QAction::triggered, this, clear_mark_callback);
413+
414+
connect(ui->actionClearAllStyles, &QAction::triggered, this, [=]() {
415+
MarkerAppDecorator *markerAppDecorator = app->findChild<MarkerAppDecorator*>(QString(), Qt::FindDirectChildrenOnly);
416+
417+
if (markerAppDecorator && markerAppDecorator->isEnabled()) {
418+
markerAppDecorator->clearAll(currentEditor());
419+
}
420+
});
421+
374422
connect(ui->actionToggleBookmark, &QAction::triggered, this, [=]() {
375423
ScintillaNext *editor = currentEditor();
376424
BookMarkDecorator *bookMarkDecorator = editor->findChild<BookMarkDecorator*>(QString(), Qt::FindDirectChildrenOnly);
@@ -1923,7 +1971,7 @@ void MainWindow::addEditor(ScintillaNext *editor)
19231971

19241972
editor->setContextMenuPolicy(Qt::CustomContextMenu);
19251973
connect(editor, &ScintillaNext::customContextMenuRequested, this, [=](const QPoint &pos) {
1926-
contextMenuPos = editor->send(SCI_POSITIONFROMPOINT, pos.x(), pos.y());
1974+
contextMenuPos = editor->positionFromPoint(pos.x(), pos.y());
19271975

19281976
QStringList actionNames = {
19291977
"Cut",
@@ -1953,7 +2001,12 @@ void MainWindow::addEditor(ScintillaNext *editor)
19532001
actionNames.prepend("CopyURL");
19542002
}
19552003

1956-
buildMenu(actionNames)->popup(QCursor::pos());
2004+
auto menu = buildMenu(actionNames);
2005+
menu->addSeparator();
2006+
menu->addMenu(ui->menuMarkAllOccurrences);
2007+
menu->addMenu(ui->menuClearMarks);
2008+
2009+
menu->popup(QCursor::pos());
19572010
});
19582011

19592012
// The editor has been entirely configured at this point, so add it to the docked editor

src/NotepadNext/dialogs/MainWindow.ui

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,23 @@
204204
<addaction name="actionCopyBookmarkedLines"/>
205205
<addaction name="actionDeleteBookmarkedLines"/>
206206
</widget>
207+
<widget class="QMenu" name="menuMarkAllOccurrences">
208+
<property name="title">
209+
<string>Mark All Occurrences</string>
210+
</property>
211+
<addaction name="actionMarkStyle1"/>
212+
<addaction name="actionMarkStyle2"/>
213+
<addaction name="actionMarkStyle3"/>
214+
</widget>
215+
<widget class="QMenu" name="menuClearMarks">
216+
<property name="title">
217+
<string>Clear Marks</string>
218+
</property>
219+
<addaction name="actionClearStyle1"/>
220+
<addaction name="actionClearStyle2"/>
221+
<addaction name="actionClearStyle3"/>
222+
<addaction name="actionClearAllStyles"/>
223+
</widget>
207224
<addaction name="actionFind"/>
208225
<addaction name="actionFindInFiles"/>
209226
<addaction name="actionFindNext"/>
@@ -213,6 +230,9 @@
213230
<addaction name="actionQuickFind"/>
214231
<addaction name="actionGoToLine"/>
215232
<addaction name="separator"/>
233+
<addaction name="menuMarkAllOccurrences"/>
234+
<addaction name="menuClearMarks"/>
235+
<addaction name="separator"/>
216236
<addaction name="menuBookmark"/>
217237
</widget>
218238
<widget class="QMenu" name="menuView">
@@ -1396,6 +1416,62 @@
13961416
<string>Delete Bookmarked Lines</string>
13971417
</property>
13981418
</action>
1419+
<action name="actionMarkStyle1">
1420+
<property name="text">
1421+
<string>Mark Style 1</string>
1422+
</property>
1423+
<property name="MarkerNumber" stdset="0">
1424+
<number>0</number>
1425+
</property>
1426+
</action>
1427+
<action name="actionMarkStyle2">
1428+
<property name="text">
1429+
<string>Mark Style 2</string>
1430+
</property>
1431+
<property name="MarkerNumber" stdset="0">
1432+
<number>1</number>
1433+
</property>
1434+
</action>
1435+
<action name="actionClearStyle1">
1436+
<property name="text">
1437+
<string>Clear Style 1</string>
1438+
</property>
1439+
<property name="MarkerNumber" stdset="0">
1440+
<number>0</number>
1441+
</property>
1442+
</action>
1443+
<action name="actionClearStyle2">
1444+
<property name="text">
1445+
<string>Clear Style 2</string>
1446+
</property>
1447+
<property name="MarkerNumber" stdset="0">
1448+
<number>1</number>
1449+
</property>
1450+
</action>
1451+
<action name="actionMarkStyle3">
1452+
<property name="text">
1453+
<string>Mark Style 3</string>
1454+
</property>
1455+
<property name="MarkerNumber" stdset="0">
1456+
<number>2</number>
1457+
</property>
1458+
</action>
1459+
<action name="actionClearStyle3">
1460+
<property name="text">
1461+
<string>Clear Style 3</string>
1462+
</property>
1463+
<property name="MarkerNumber" stdset="0">
1464+
<number>2</number>
1465+
</property>
1466+
</action>
1467+
<action name="actionClearAllStyles">
1468+
<property name="text">
1469+
<string>Clear All Styles</string>
1470+
</property>
1471+
<property name="toolTip">
1472+
<string>Clear All Styles</string>
1473+
</property>
1474+
</action>
13991475
</widget>
14001476
<layoutdefault spacing="6" margin="11"/>
14011477
<customwidgets>

0 commit comments

Comments
 (0)