File indexing completed on 2024-04-14 03:55:28
0001 /* 0002 SPDX-FileCopyrightText: 2002, 2003, 2004 Anders Lund <anders.lund@lund.tdcadsl.dk> 0003 SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "katebookmarks.h" 0009 0010 #include "kateabstractinputmode.h" 0011 #include "katedocument.h" 0012 #include "kateview.h" 0013 0014 #include <KActionCollection> 0015 #include <KActionMenu> 0016 #include <KGuiItem> 0017 #include <KLocalizedString> 0018 #include <KStringHandler> 0019 #include <KToggleAction> 0020 #include <KXMLGUIClient> 0021 #include <KXMLGUIFactory> 0022 0023 #include <QEvent> 0024 #include <QList> 0025 #include <QMenu> 0026 #include <QRegularExpression> 0027 0028 namespace KTextEditor 0029 { 0030 class Document; 0031 } 0032 0033 KateBookmarks::KateBookmarks(KTextEditor::ViewPrivate *view, Sorting sort) 0034 : QObject(view) 0035 , m_view(view) 0036 , m_bookmarkClear(nullptr) 0037 , m_sorting(sort) 0038 { 0039 setObjectName(QStringLiteral("kate bookmarks")); 0040 connect(view->doc(), &KTextEditor::DocumentPrivate::marksChanged, this, &KateBookmarks::marksChanged); 0041 _tries = 0; 0042 m_bookmarksMenu = nullptr; 0043 } 0044 0045 KateBookmarks::~KateBookmarks() = default; 0046 0047 void KateBookmarks::createActions(KActionCollection *ac) 0048 { 0049 m_bookmarkToggle = new KToggleAction(i18n("Set &Bookmark"), this); 0050 ac->addAction(QStringLiteral("bookmarks_toggle"), m_bookmarkToggle); 0051 m_bookmarkToggle->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-new"))); 0052 ac->setDefaultShortcut(m_bookmarkToggle, Qt::CTRL | Qt::Key_B); 0053 m_bookmarkToggle->setWhatsThis(i18n("If a line has no bookmark then add one, otherwise remove it.")); 0054 connect(m_bookmarkToggle, &QAction::triggered, this, &KateBookmarks::toggleBookmark); 0055 0056 m_bookmarkClear = new QAction(i18n("Clear &All Bookmarks"), this); 0057 ac->addAction(QStringLiteral("bookmarks_clear"), m_bookmarkClear); 0058 m_bookmarkClear->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-remove"))); 0059 m_bookmarkClear->setWhatsThis(i18n("Remove all bookmarks of the current document.")); 0060 connect(m_bookmarkClear, &QAction::triggered, this, &KateBookmarks::clearBookmarks); 0061 0062 m_goNext = new QAction(i18n("Next Bookmark"), this); 0063 ac->addAction(QStringLiteral("bookmarks_next"), m_goNext); 0064 m_goNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); 0065 ac->setDefaultShortcut(m_goNext, Qt::ALT | Qt::Key_PageDown); 0066 m_goNext->setWhatsThis(i18n("Go to the next bookmark.")); 0067 connect(m_goNext, &QAction::triggered, this, &KateBookmarks::goNext); 0068 0069 m_goPrevious = new QAction(i18n("Previous Bookmark"), this); 0070 ac->addAction(QStringLiteral("bookmarks_previous"), m_goPrevious); 0071 m_goPrevious->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search"))); 0072 ac->setDefaultShortcut(m_goPrevious, Qt::ALT | Qt::Key_PageUp); 0073 m_goPrevious->setWhatsThis(i18n("Go to the previous bookmark.")); 0074 connect(m_goPrevious, &QAction::triggered, this, &KateBookmarks::goPrevious); 0075 0076 KActionMenu *actionMenu = new KActionMenu(i18n("&Bookmarks"), this); 0077 actionMenu->setPopupMode(QToolButton::InstantPopup); 0078 actionMenu->setIcon(QIcon::fromTheme(QStringLiteral("bookmarks"))); 0079 ac->addAction(QStringLiteral("bookmarks"), actionMenu); 0080 m_bookmarksMenu = actionMenu->menu(); 0081 0082 connect(m_bookmarksMenu, &QMenu::aboutToShow, this, &KateBookmarks::bookmarkMenuAboutToShow); 0083 // Ensure the bookmarks menu is populated with at least the basic actions, otherwise macOS will not show it in the global menu bar. 0084 bookmarkMenuAboutToShow(); 0085 0086 marksChanged(); 0087 0088 // Always want the actions with shortcuts plugged into something so their shortcuts can work 0089 m_view->addAction(m_bookmarkToggle); 0090 m_view->addAction(m_bookmarkClear); 0091 m_view->addAction(m_goNext); 0092 m_view->addAction(m_goPrevious); 0093 } 0094 0095 void KateBookmarks::toggleBookmark() 0096 { 0097 uint mark = m_view->doc()->mark(m_view->cursorPosition().line()); 0098 if (mark & KTextEditor::Document::markType01) { 0099 m_view->doc()->removeMark(m_view->cursorPosition().line(), KTextEditor::Document::markType01); 0100 } else { 0101 m_view->doc()->addMark(m_view->cursorPosition().line(), KTextEditor::Document::markType01); 0102 } 0103 } 0104 0105 void KateBookmarks::clearBookmarks() 0106 { 0107 // work on a COPY of the hash, the removing will modify it otherwise! 0108 const auto hash = m_view->doc()->marks(); 0109 for (auto it = hash.cbegin(); it != hash.cend(); ++it) { 0110 m_view->doc()->removeMark(it.value()->line, KTextEditor::Document::markType01); 0111 } 0112 } 0113 0114 void KateBookmarks::insertBookmarks(QMenu &menu) 0115 { 0116 const int line = m_view->cursorPosition().line(); 0117 static const QRegularExpression re(QStringLiteral("&(?!&)")); 0118 int next = -1; // -1 means next bookmark doesn't exist 0119 int prev = -1; // -1 means previous bookmark doesn't exist 0120 0121 // reference ok, not modified 0122 const auto &hash = m_view->doc()->marks(); 0123 if (hash.isEmpty()) { 0124 return; 0125 } 0126 0127 std::vector<int> bookmarkLineArray; // Array of line numbers which have bookmarks 0128 0129 // Find line numbers where bookmarks are set & store those line numbers in bookmarkLineArray 0130 for (auto it = hash.cbegin(); it != hash.cend(); ++it) { 0131 if (it.value()->type & KTextEditor::Document::markType01) { 0132 bookmarkLineArray.push_back(it.value()->line); 0133 } 0134 } 0135 0136 if (m_sorting == Position) { 0137 std::sort(bookmarkLineArray.begin(), bookmarkLineArray.end()); 0138 } 0139 0140 QAction *firstNewAction = menu.addSeparator(); 0141 // Consider each line with a bookmark one at a time 0142 for (size_t i = 0; i < bookmarkLineArray.size(); ++i) { 0143 const int lineNo = bookmarkLineArray.at(i); 0144 // Get text in this particular line in a QString 0145 QFontMetrics fontMetrics(menu.fontMetrics()); 0146 QString bText = fontMetrics.elidedText(m_view->doc()->line(lineNo), Qt::ElideRight, fontMetrics.maxWidth() * 32); 0147 bText.replace(re, QStringLiteral("&&")); // kill undesired accellerators! 0148 bText.replace(QLatin1Char('\t'), QLatin1Char(' ')); // kill tabs, as they are interpreted as shortcuts 0149 0150 QAction *before = nullptr; 0151 if (m_sorting == Position) { 0152 // 3 actions already present 0153 if (size_t(menu.actions().size()) <= i + 3) { 0154 before = nullptr; 0155 } else { 0156 before = menu.actions().at(i + 3); 0157 } 0158 } 0159 0160 const QString actionText(QStringLiteral("%1 %2 - \"%3\"").arg(QString::number(lineNo + 1), m_view->currentInputMode()->bookmarkLabel(lineNo), bText)); 0161 // Adding action for this bookmark in menu 0162 if (before) { 0163 QAction *a = new QAction(actionText, &menu); 0164 menu.insertAction(before, a); 0165 connect(a, &QAction::triggered, this, [this, lineNo]() { 0166 gotoLine(lineNo); 0167 }); 0168 0169 if (!firstNewAction) { 0170 firstNewAction = a; 0171 } 0172 } else { 0173 menu.addAction(actionText, this, [this, lineNo]() { 0174 gotoLine(lineNo); 0175 }); 0176 } 0177 0178 // Find the line number of previous & next bookmark (if present) in relation to the cursor 0179 if (lineNo < line) { 0180 if (prev == -1 || prev < lineNo) { 0181 prev = lineNo; 0182 } 0183 } else if (lineNo > line) { 0184 if (next == -1 || next > lineNo) { 0185 next = lineNo; 0186 } 0187 } 0188 } 0189 0190 if (next != -1) { 0191 // Insert action for next bookmark 0192 m_goNext->setText(i18n("&Next: %1 - \"%2\"", next + 1, KStringHandler::rsqueeze(m_view->doc()->line(next), 24))); 0193 menu.insertAction(firstNewAction, m_goNext); 0194 firstNewAction = m_goNext; 0195 } 0196 if (prev != -1) { 0197 // Insert action for previous bookmark 0198 m_goPrevious->setText(i18n("&Previous: %1 - \"%2\"", prev + 1, KStringHandler::rsqueeze(m_view->doc()->line(prev), 24))); 0199 menu.insertAction(firstNewAction, m_goPrevious); 0200 firstNewAction = m_goPrevious; 0201 } 0202 0203 if (next != -1 || prev != -1) { 0204 menu.insertSeparator(firstNewAction); 0205 } 0206 } 0207 0208 void KateBookmarks::gotoLine(int line) 0209 { 0210 m_view->setCursorPosition(KTextEditor::Cursor(line, 0)); 0211 } 0212 0213 void KateBookmarks::bookmarkMenuAboutToShow() 0214 { 0215 m_bookmarksMenu->clear(); 0216 m_bookmarkToggle->setChecked(m_view->doc()->mark(m_view->cursorPosition().line()) & KTextEditor::Document::markType01); 0217 m_bookmarksMenu->addAction(m_bookmarkToggle); 0218 m_bookmarksMenu->addAction(m_bookmarkClear); 0219 0220 m_goNext->setText(i18n("Next Bookmark")); 0221 m_goPrevious->setText(i18n("Previous Bookmark")); 0222 0223 insertBookmarks(*m_bookmarksMenu); 0224 } 0225 0226 void KateBookmarks::goNext() 0227 { 0228 // reference ok, not modified 0229 const auto &hash = m_view->doc()->marks(); 0230 if (hash.isEmpty()) { 0231 return; 0232 } 0233 0234 int line = m_view->cursorPosition().line(); 0235 int found = -1; 0236 int firstBookmarkLine = -1; 0237 for (auto it = hash.cbegin(); it != hash.cend(); ++it) { 0238 const int markLine = it.value()->line; 0239 if (markLine > line && (found == -1 || found > markLine)) { 0240 found = markLine; 0241 } 0242 if (markLine < firstBookmarkLine || firstBookmarkLine == -1) { 0243 firstBookmarkLine = markLine; 0244 } 0245 } 0246 0247 // either go to next bookmark or the first in the document, bug 472354 0248 if (found != -1) { 0249 gotoLine(found); 0250 } else { 0251 gotoLine(firstBookmarkLine); 0252 } 0253 } 0254 0255 void KateBookmarks::goPrevious() 0256 { 0257 // reference ok, not modified 0258 const auto &hash = m_view->doc()->marks(); 0259 if (hash.isEmpty()) { 0260 return; 0261 } 0262 0263 int line = m_view->cursorPosition().line(); 0264 int found = -1; 0265 int lastBookmarkLine = -1; 0266 for (auto it = hash.cbegin(); it != hash.cend(); ++it) { 0267 const int markLine = it.value()->line; 0268 if (markLine < line && (found == -1 || found < markLine)) { 0269 found = markLine; 0270 } 0271 if (markLine > lastBookmarkLine) { 0272 lastBookmarkLine = markLine; 0273 } 0274 } 0275 0276 // either go to previous bookmark or the last in the document, bug 472354 0277 if (found != -1) { 0278 gotoLine(found); 0279 } else { 0280 gotoLine(lastBookmarkLine); 0281 } 0282 } 0283 0284 void KateBookmarks::marksChanged() 0285 { 0286 const bool bookmarks = !m_view->doc()->marks().isEmpty(); 0287 if (m_bookmarkClear) { 0288 m_bookmarkClear->setEnabled(bookmarks); 0289 } 0290 if (m_goNext) { 0291 m_goNext->setEnabled(bookmarks); 0292 } 0293 if (m_goPrevious) { 0294 m_goPrevious->setEnabled(bookmarks); 0295 } 0296 }