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