KTextEditor

katebookmarks.cpp
1/*
2 SPDX-FileCopyrightText: 2002, 2003, 2004 Anders Lund <anders.lund@lund.tdcadsl.dk>
3 SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "katebookmarks.h"
9
10#include "kateabstractinputmode.h"
11#include "katedocument.h"
12#include "kateview.h"
13
14#include <KActionCollection>
15#include <KActionMenu>
16#include <KGuiItem>
17#include <KLocalizedString>
18#include <KStringHandler>
19#include <KToggleAction>
20#include <KXMLGUIClient>
21#include <KXMLGUIFactory>
22
23#include <QEvent>
24#include <QList>
25#include <QMenu>
26#include <QRegularExpression>
27
28namespace KTextEditor
29{
30class Document;
31}
32
33KateBookmarks::KateBookmarks(KTextEditor::ViewPrivate *view, Sorting sort)
34 : QObject(view)
35 , m_view(view)
36 , m_bookmarkClear(nullptr)
37 , m_sorting(sort)
38{
39 setObjectName(QStringLiteral("kate bookmarks"));
40 connect(view->doc(), &KTextEditor::DocumentPrivate::marksChanged, this, &KateBookmarks::marksChanged);
41 _tries = 0;
42 m_bookmarksMenu = nullptr;
43}
44
45KateBookmarks::~KateBookmarks() = default;
46
47void KateBookmarks::createActions(KActionCollection *ac)
48{
49 m_bookmarkToggle = new KToggleAction(i18n("Set &Bookmark"), this);
50 ac->addAction(QStringLiteral("bookmarks_toggle"), m_bookmarkToggle);
51 m_bookmarkToggle->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-new")));
52 ac->setDefaultShortcut(m_bookmarkToggle, Qt::CTRL | Qt::Key_B);
53 m_bookmarkToggle->setWhatsThis(i18n("If a line has no bookmark then add one, otherwise remove it."));
54 connect(m_bookmarkToggle, &QAction::triggered, this, &KateBookmarks::toggleBookmark);
55
56 m_bookmarkClear = new QAction(i18n("Clear &All Bookmarks"), this);
57 ac->addAction(QStringLiteral("bookmarks_clear"), m_bookmarkClear);
58 m_bookmarkClear->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-remove")));
59 m_bookmarkClear->setWhatsThis(i18n("Remove all bookmarks of the current document."));
60 connect(m_bookmarkClear, &QAction::triggered, this, &KateBookmarks::clearBookmarks);
61
62 m_goNext = new QAction(i18n("Next Bookmark"), this);
63 ac->addAction(QStringLiteral("bookmarks_next"), m_goNext);
64 m_goNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search")));
66 m_goNext->setWhatsThis(i18n("Go to the next bookmark."));
67 connect(m_goNext, &QAction::triggered, this, &KateBookmarks::goNext);
68
69 m_goPrevious = new QAction(i18n("Previous Bookmark"), this);
70 ac->addAction(QStringLiteral("bookmarks_previous"), m_goPrevious);
71 m_goPrevious->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search")));
72 ac->setDefaultShortcut(m_goPrevious, Qt::ALT | Qt::Key_PageUp);
73 m_goPrevious->setWhatsThis(i18n("Go to the previous bookmark."));
74 connect(m_goPrevious, &QAction::triggered, this, &KateBookmarks::goPrevious);
75
76 KActionMenu *actionMenu = new KActionMenu(i18n("&Bookmarks"), this);
78 actionMenu->setIcon(QIcon::fromTheme(QStringLiteral("bookmarks")));
79 ac->addAction(QStringLiteral("bookmarks"), actionMenu);
80 m_bookmarksMenu = actionMenu->menu();
81
82 connect(m_bookmarksMenu, &QMenu::aboutToShow, this, &KateBookmarks::bookmarkMenuAboutToShow);
83 // Ensure the bookmarks menu is populated with at least the basic actions, otherwise macOS will not show it in the global menu bar.
84 bookmarkMenuAboutToShow();
85
86 marksChanged();
87
88 // Always want the actions with shortcuts plugged into something so their shortcuts can work
89 m_view->addAction(m_bookmarkToggle);
90 m_view->addAction(m_bookmarkClear);
91 m_view->addAction(m_goNext);
92 m_view->addAction(m_goPrevious);
93}
94
95void KateBookmarks::toggleBookmark()
96{
97 uint mark = m_view->doc()->mark(m_view->cursorPosition().line());
99 m_view->doc()->removeMark(m_view->cursorPosition().line(), KTextEditor::Document::markType01);
100 } else {
101 m_view->doc()->addMark(m_view->cursorPosition().line(), KTextEditor::Document::markType01);
102 }
103}
104
105void KateBookmarks::clearBookmarks()
106{
107 // work on a COPY of the hash, the removing will modify it otherwise!
108 const auto hash = m_view->doc()->marks();
109 for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
110 m_view->doc()->removeMark(it.value()->line, KTextEditor::Document::markType01);
111 }
112}
113
114void KateBookmarks::insertBookmarks(QMenu &menu)
115{
116 const int line = m_view->cursorPosition().line();
117 static const QRegularExpression re(QStringLiteral("&(?!&)"));
118 int next = -1; // -1 means next bookmark doesn't exist
119 int prev = -1; // -1 means previous bookmark doesn't exist
120
121 // reference ok, not modified
122 const auto &hash = m_view->doc()->marks();
123 if (hash.isEmpty()) {
124 return;
125 }
126
127 std::vector<int> bookmarkLineArray; // Array of line numbers which have bookmarks
128
129 // Find line numbers where bookmarks are set & store those line numbers in bookmarkLineArray
130 for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
131 if (it.value()->type & KTextEditor::Document::markType01) {
132 bookmarkLineArray.push_back(it.value()->line);
133 }
134 }
135
136 if (m_sorting == Position) {
137 std::sort(bookmarkLineArray.begin(), bookmarkLineArray.end());
138 }
139
140 QAction *firstNewAction = menu.addSeparator();
141 // Consider each line with a bookmark one at a time
142 for (size_t i = 0; i < bookmarkLineArray.size(); ++i) {
143 const int lineNo = bookmarkLineArray.at(i);
144 // Get text in this particular line in a QString
145 QFontMetrics fontMetrics(menu.fontMetrics());
146 QString bText = fontMetrics.elidedText(m_view->doc()->line(lineNo), Qt::ElideRight, fontMetrics.maxWidth() * 32);
147 bText.replace(re, QStringLiteral("&&")); // kill undesired accellerators!
148 bText.replace(QLatin1Char('\t'), QLatin1Char(' ')); // kill tabs, as they are interpreted as shortcuts
149
150 QAction *before = nullptr;
151 if (m_sorting == Position) {
152 // 3 actions already present
153 if (size_t(menu.actions().size()) <= i + 3) {
154 before = nullptr;
155 } else {
156 before = menu.actions().at(i + 3);
157 }
158 }
159
160 const QString actionText(QStringLiteral("%1 %2 - \"%3\"").arg(QString::number(lineNo + 1), m_view->currentInputMode()->bookmarkLabel(lineNo), bText));
161 // Adding action for this bookmark in menu
162 if (before) {
163 QAction *a = new QAction(actionText, &menu);
164 menu.insertAction(before, a);
165 connect(a, &QAction::triggered, this, [this, lineNo]() {
166 gotoLine(lineNo);
167 });
168
169 if (!firstNewAction) {
170 firstNewAction = a;
171 }
172 } else {
173 menu.addAction(actionText, this, [this, lineNo]() {
174 gotoLine(lineNo);
175 });
176 }
177
178 // Find the line number of previous & next bookmark (if present) in relation to the cursor
179 if (lineNo < line) {
180 if (prev == -1 || prev < lineNo) {
181 prev = lineNo;
182 }
183 } else if (lineNo > line) {
184 if (next == -1 || next > lineNo) {
185 next = lineNo;
186 }
187 }
188 }
189
190 if (next != -1) {
191 // Insert action for next bookmark
192 m_goNext->setText(i18n("&Next: %1 - \"%2\"", next + 1, KStringHandler::rsqueeze(m_view->doc()->line(next), 24)));
193 menu.insertAction(firstNewAction, m_goNext);
194 firstNewAction = m_goNext;
195 }
196 if (prev != -1) {
197 // Insert action for previous bookmark
198 m_goPrevious->setText(i18n("&Previous: %1 - \"%2\"", prev + 1, KStringHandler::rsqueeze(m_view->doc()->line(prev), 24)));
199 menu.insertAction(firstNewAction, m_goPrevious);
200 firstNewAction = m_goPrevious;
201 }
202
203 if (next != -1 || prev != -1) {
204 menu.insertSeparator(firstNewAction);
205 }
206}
207
208void KateBookmarks::gotoLine(int line)
209{
210 m_view->setCursorPosition(KTextEditor::Cursor(line, 0));
211}
212
213void KateBookmarks::bookmarkMenuAboutToShow()
214{
215 m_bookmarksMenu->clear();
216 m_bookmarkToggle->setChecked(m_view->doc()->mark(m_view->cursorPosition().line()) & KTextEditor::Document::markType01);
217 m_bookmarksMenu->addAction(m_bookmarkToggle);
218 m_bookmarksMenu->addAction(m_bookmarkClear);
219
220 m_goNext->setText(i18n("Next Bookmark"));
221 m_goPrevious->setText(i18n("Previous Bookmark"));
222
223 insertBookmarks(*m_bookmarksMenu);
224}
225
226void KateBookmarks::goNext()
227{
228 // reference ok, not modified
229 const auto &hash = m_view->doc()->marks();
230 if (hash.isEmpty()) {
231 return;
232 }
233
234 int line = m_view->cursorPosition().line();
235 int found = -1;
236 int firstBookmarkLine = -1;
237 for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
238 const int markLine = it.value()->line;
239 if (markLine > line && (found == -1 || found > markLine)) {
240 found = markLine;
241 }
242 if (markLine < firstBookmarkLine || firstBookmarkLine == -1) {
243 firstBookmarkLine = markLine;
244 }
245 }
246
247 // either go to next bookmark or the first in the document, bug 472354
248 if (found != -1) {
249 gotoLine(found);
250 } else {
251 gotoLine(firstBookmarkLine);
252 }
253}
254
255void KateBookmarks::goPrevious()
256{
257 // reference ok, not modified
258 const auto &hash = m_view->doc()->marks();
259 if (hash.isEmpty()) {
260 return;
261 }
262
263 int line = m_view->cursorPosition().line();
264 int found = -1;
265 int lastBookmarkLine = -1;
266 for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
267 const int markLine = it.value()->line;
268 if (markLine < line && (found == -1 || found < markLine)) {
269 found = markLine;
270 }
271 if (markLine > lastBookmarkLine) {
272 lastBookmarkLine = markLine;
273 }
274 }
275
276 // either go to previous bookmark or the last in the document, bug 472354
277 if (found != -1) {
278 gotoLine(found);
279 } else {
280 gotoLine(lastBookmarkLine);
281 }
282}
283
284void KateBookmarks::marksChanged()
285{
286 const bool bookmarks = !m_view->doc()->marks().isEmpty();
287 if (m_bookmarkClear) {
288 m_bookmarkClear->setEnabled(bookmarks);
289 }
290 if (m_goNext) {
291 m_goNext->setEnabled(bookmarks);
292 }
293 if (m_goPrevious) {
294 m_goPrevious->setEnabled(bookmarks);
295 }
296}
QAction * addAction(const QString &name, const QObject *receiver=nullptr, const char *member=nullptr)
static void setDefaultShortcut(QAction *action, const QKeySequence &shortcut)
void setPopupMode(QToolButton::ToolButtonPopupMode popupMode)
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
QString line(int line) const override
Get a single text line.
uint mark(int line) override
Get all marks set on the line.
const QHash< int, KTextEditor::Mark * > & marks() override
Get a hash holding all marks in the document.
void marksChanged(KTextEditor::Document *document)
The document emits this signal whenever a mark mask changed.
@ markType01
Bookmark.
Definition document.h:1559
QString i18n(const char *text, const TYPE &arg...)
const QList< QKeySequence > & next()
KCOREADDONS_EXPORT QString rsqueeze(const QString &str, int maxlen=40)
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
void setChecked(bool)
void setEnabled(bool)
void setIcon(const QIcon &icon)
QMenu * menu() const const
void setText(const QString &text)
void triggered(bool checked)
void setWhatsThis(const QString &what)
bool isEmpty() const const
QIcon fromTheme(const QString &name)
const_reference at(qsizetype i) const const
qsizetype size() const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
void aboutToShow()
QAction * addSeparator()
void clear()
QAction * insertSeparator(QAction *before)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void setObjectName(QAnyStringView name)
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
ElideRight
QList< QAction * > actions() const const
QAction * addAction(const QIcon &icon, const QString &text)
QFontMetrics fontMetrics() const const
void insertAction(QAction *before, QAction *action)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:11:27 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.