1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "bookmarkmanager.h"
27
28 #include "bookmark.h"
29 #include "bookmarksplugin.h"
30 #include "bookmarks_global.h"
31
32 #include <coreplugin/editormanager/editormanager.h>
33 #include <coreplugin/icore.h>
34 #include <coreplugin/idocument.h>
35 #include <coreplugin/actionmanager/actionmanager.h>
36 #include <coreplugin/actionmanager/command.h>
37 #include <projectexplorer/projectexplorer.h>
38 #include <projectexplorer/session.h>
39 #include <texteditor/texteditor.h>
40 #include <utils/algorithm.h>
41 #include <utils/icon.h>
42 #include <utils/qtcassert.h>
43 #include <utils/checkablemessagebox.h>
44 #include <utils/theme/theme.h>
45 #include <utils/dropsupport.h>
46 #include <utils/utilsicons.h>
47
48 #include <QAction>
49 #include <QContextMenuEvent>
50 #include <QDebug>
51 #include <QDialog>
52 #include <QDialogButtonBox>
53 #include <QDir>
54 #include <QFileInfo>
55 #include <QFormLayout>
56 #include <QLineEdit>
57 #include <QMenu>
58 #include <QPainter>
59 #include <QSpinBox>
60 #include <QToolButton>
61
62 Q_DECLARE_METATYPE(Bookmarks::Internal::Bookmark*)
63
64 using namespace ProjectExplorer;
65 using namespace Core;
66 using namespace Utils;
67
68 namespace Bookmarks {
69 namespace Internal {
70
BookmarkDelegate(QObject * parent)71 BookmarkDelegate::BookmarkDelegate(QObject *parent)
72 : QStyledItemDelegate(parent)
73 {
74 }
75
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const76 QSize BookmarkDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
77 {
78 QStyleOptionViewItem opt = option;
79 initStyleOption(&opt, index);
80
81 QFontMetrics fm(option.font);
82 QSize s;
83 s.setWidth(option.rect.width());
84 s.setHeight(fm.height() * 2 + 10);
85 return s;
86 }
87
generateGradientPixmap(int width,int height,const QColor & color,bool selected) const88 void BookmarkDelegate::generateGradientPixmap(int width, int height, const QColor &color, bool selected) const
89 {
90 QColor c = color;
91 c.setAlpha(0);
92
93 QPixmap pixmap(width+1, height);
94 pixmap.fill(c);
95
96 QPainter painter(&pixmap);
97 painter.setPen(Qt::NoPen);
98
99 QLinearGradient lg;
100 lg.setCoordinateMode(QGradient::ObjectBoundingMode);
101 lg.setFinalStop(1,0);
102
103 lg.setColorAt(0, c);
104 lg.setColorAt(0.4, color);
105
106 painter.setBrush(lg);
107 painter.drawRect(0, 0, width+1, height);
108
109 if (selected)
110 m_selectedPixmap = pixmap;
111 else
112 m_normalPixmap = pixmap;
113 }
114
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const115 void BookmarkDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
116 {
117 QStyleOptionViewItem opt = option;
118 initStyleOption(&opt, index);
119 painter->save();
120
121 QFontMetrics fm(opt.font);
122 static int lwidth = fm.horizontalAdvance(QLatin1String("8888")) + 18;
123
124 QColor backgroundColor;
125 QColor textColor;
126
127 bool selected = opt.state & QStyle::State_Selected;
128
129 if (selected) {
130 painter->setBrush(opt.palette.highlight().color());
131 backgroundColor = opt.palette.highlight().color();
132 if (!m_selectedPixmap)
133 generateGradientPixmap(lwidth, fm.height()+1, backgroundColor, selected);
134 } else {
135 backgroundColor = opt.palette.window().color();
136 painter->setBrush(backgroundColor);
137 if (!m_normalPixmap)
138 generateGradientPixmap(lwidth, fm.height(), backgroundColor, selected);
139 }
140 painter->setPen(Qt::NoPen);
141 painter->drawRect(opt.rect);
142
143 // Set Text Color
144 if (opt.state & QStyle::State_Selected)
145 textColor = opt.palette.highlightedText().color();
146 else
147 textColor = opt.palette.text().color();
148
149 painter->setPen(textColor);
150
151
152 // TopLeft
153 QString topLeft = index.data(BookmarkManager::Filename).toString();
154 painter->drawText(6, 2 + opt.rect.top() + fm.ascent(), topLeft);
155
156 QString topRight = index.data(BookmarkManager::LineNumber).toString();
157 // Check whether we need to be fancy and paint some background
158 int fwidth = fm.horizontalAdvance(topLeft);
159 if (fwidth + lwidth > opt.rect.width()) {
160 int left = opt.rect.right() - lwidth;
161 painter->drawPixmap(left, opt.rect.top(), selected ? m_selectedPixmap : m_normalPixmap);
162 }
163 // topRight
164 painter->drawText(opt.rect.right() - fm.horizontalAdvance(topRight) - 6,
165 2 + opt.rect.top() + fm.ascent(), topRight);
166
167 // Directory
168 QColor mix;
169 mix.setRgbF(0.7 * textColor.redF() + 0.3 * backgroundColor.redF(),
170 0.7 * textColor.greenF() + 0.3 * backgroundColor.greenF(),
171 0.7 * textColor.blueF() + 0.3 * backgroundColor.blueF());
172 painter->setPen(mix);
173
174 QString lineText = index.data(BookmarkManager::Note).toString().trimmed();
175 if (lineText.isEmpty())
176 lineText = index.data(BookmarkManager::LineText).toString();
177
178 painter->drawText(6, opt.rect.top() + fm.ascent() + fm.height() + 6, lineText);
179
180 // Separator lines
181 const QRectF innerRect = QRectF(opt.rect).adjusted(0.5, 0.5, -0.5, -0.5);
182 painter->setPen(QColor::fromRgb(150,150,150));
183 painter->drawLine(innerRect.bottomLeft(), innerRect.bottomRight());
184 painter->restore();
185 }
186
BookmarkView(BookmarkManager * manager)187 BookmarkView::BookmarkView(BookmarkManager *manager) :
188 m_bookmarkContext(new IContext(this)),
189 m_manager(manager)
190 {
191 setWindowTitle(tr("Bookmarks"));
192
193 m_bookmarkContext->setWidget(this);
194 m_bookmarkContext->setContext(Context(Constants::BOOKMARKS_CONTEXT));
195
196 ICore::addContextObject(m_bookmarkContext);
197
198 ListView::setModel(manager);
199
200 setItemDelegate(new BookmarkDelegate(this));
201 setFrameStyle(QFrame::NoFrame);
202 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
203 setAttribute(Qt::WA_MacShowFocusRect, false);
204 setSelectionModel(manager->selectionModel());
205 setSelectionMode(QAbstractItemView::SingleSelection);
206 setSelectionBehavior(QAbstractItemView::SelectRows);
207 setDragEnabled(true);
208 setDragDropMode(QAbstractItemView::DragOnly);
209
210 connect(this, &QAbstractItemView::clicked, this, &BookmarkView::gotoBookmark);
211 connect(this, &QAbstractItemView::activated, this, &BookmarkView::gotoBookmark);
212 }
213
createToolBarWidgets()214 QList<QToolButton *> BookmarkView::createToolBarWidgets()
215 {
216 Command *prevCmd = ActionManager::command(Constants::BOOKMARKS_PREV_ACTION);
217 Command *nextCmd = ActionManager::command(Constants::BOOKMARKS_NEXT_ACTION);
218 QTC_ASSERT(prevCmd && nextCmd, return {});
219 auto prevButton = new QToolButton(this);
220 prevButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
221 prevButton->setDefaultAction(prevCmd->action());
222 auto nextButton = new QToolButton(this);
223 nextButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
224 nextButton->setDefaultAction(nextCmd->action());
225 return {prevButton, nextButton};
226 }
227
contextMenuEvent(QContextMenuEvent * event)228 void BookmarkView::contextMenuEvent(QContextMenuEvent *event)
229 {
230 QMenu menu;
231 QAction *moveUp = menu.addAction(tr("Move Up"));
232 QAction *moveDown = menu.addAction(tr("Move Down"));
233 QAction *edit = menu.addAction(tr("&Edit"));
234 menu.addSeparator();
235 QAction *remove = menu.addAction(tr("&Remove"));
236 menu.addSeparator();
237 QAction *removeAll = menu.addAction(tr("Remove All"));
238
239 m_contextMenuIndex = indexAt(event->pos());
240 if (!m_contextMenuIndex.isValid()) {
241 moveUp->setEnabled(false);
242 moveDown->setEnabled(false);
243 remove->setEnabled(false);
244 edit->setEnabled(false);
245 }
246
247 if (model()->rowCount() == 0)
248 removeAll->setEnabled(false);
249
250 connect(moveUp, &QAction::triggered, m_manager, &BookmarkManager::moveUp);
251 connect(moveDown, &QAction::triggered, m_manager, &BookmarkManager::moveDown);
252 connect(remove, &QAction::triggered, this, &BookmarkView::removeFromContextMenu);
253 connect(removeAll, &QAction::triggered, this, &BookmarkView::removeAll);
254 connect(edit, &QAction::triggered, m_manager, &BookmarkManager::edit);
255
256 menu.exec(mapToGlobal(event->pos()));
257 }
258
removeFromContextMenu()259 void BookmarkView::removeFromContextMenu()
260 {
261 removeBookmark(m_contextMenuIndex);
262 }
263
removeBookmark(const QModelIndex & index)264 void BookmarkView::removeBookmark(const QModelIndex& index)
265 {
266 Bookmark *bm = m_manager->bookmarkForIndex(index);
267 m_manager->deleteBookmark(bm);
268 }
269
keyPressEvent(QKeyEvent * event)270 void BookmarkView::keyPressEvent(QKeyEvent *event)
271 {
272 if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) {
273 removeBookmark(currentIndex());
274 event->accept();
275 return;
276 }
277 ListView::keyPressEvent(event);
278 }
279
removeAll()280 void BookmarkView::removeAll()
281 {
282 if (CheckableMessageBox::doNotAskAgainQuestion(this,
283 tr("Remove All Bookmarks"),
284 tr("Are you sure you want to remove all bookmarks from all files in the current session?"),
285 ICore::settings(),
286 QLatin1String("RemoveAllBookmarks")) != QDialogButtonBox::Yes)
287 return;
288
289 // The performance of this function could be greatly improved.
290 while (m_manager->rowCount()) {
291 QModelIndex index = m_manager->index(0, 0);
292 removeBookmark(index);
293 }
294 }
295
gotoBookmark(const QModelIndex & index)296 void BookmarkView::gotoBookmark(const QModelIndex &index)
297 {
298 Bookmark *bk = m_manager->bookmarkForIndex(index);
299 if (!m_manager->gotoBookmark(bk))
300 m_manager->deleteBookmark(bk);
301 }
302
303 ////
304 // BookmarkManager
305 ////
306
BookmarkManager()307 BookmarkManager::BookmarkManager() :
308 m_selectionModel(new QItemSelectionModel(this, this))
309 {
310 connect(ICore::instance(), &ICore::contextChanged,
311 this, &BookmarkManager::updateActionStatus);
312
313 connect(SessionManager::instance(), &SessionManager::sessionLoaded,
314 this, &BookmarkManager::loadBookmarks);
315
316 updateActionStatus();
317 }
318
~BookmarkManager()319 BookmarkManager::~BookmarkManager()
320 {
321 qDeleteAll(m_bookmarksList);
322 }
323
selectionModel() const324 QItemSelectionModel *BookmarkManager::selectionModel() const
325 {
326 return m_selectionModel;
327 }
328
hasBookmarkInPosition(const Utils::FilePath & fileName,int lineNumber)329 bool BookmarkManager::hasBookmarkInPosition(const Utils::FilePath &fileName, int lineNumber)
330 {
331 return findBookmark(fileName, lineNumber);
332 }
333
index(int row,int column,const QModelIndex & parent) const334 QModelIndex BookmarkManager::index(int row, int column, const QModelIndex &parent) const
335 {
336 if (parent.isValid())
337 return QModelIndex();
338 else
339 return createIndex(row, column);
340 }
341
parent(const QModelIndex &) const342 QModelIndex BookmarkManager::parent(const QModelIndex &) const
343 {
344 return QModelIndex();
345 }
346
rowCount(const QModelIndex & parent) const347 int BookmarkManager::rowCount(const QModelIndex &parent) const
348 {
349 if (parent.isValid())
350 return 0;
351 else
352 return m_bookmarksList.count();
353 }
354
columnCount(const QModelIndex & parent) const355 int BookmarkManager::columnCount(const QModelIndex &parent) const
356 {
357 if (parent.isValid())
358 return 0;
359 return 3;
360 }
361
data(const QModelIndex & index,int role) const362 QVariant BookmarkManager::data(const QModelIndex &index, int role) const
363 {
364 if (!index.isValid() || index.column() !=0 || index.row() < 0 || index.row() >= m_bookmarksList.count())
365 return QVariant();
366
367 Bookmark *bookMark = m_bookmarksList.at(index.row());
368 if (role == BookmarkManager::Filename)
369 return bookMark->fileName().fileName();
370 if (role == BookmarkManager::LineNumber)
371 return bookMark->lineNumber();
372 if (role == BookmarkManager::Directory)
373 return bookMark->fileName().toFileInfo().path();
374 if (role == BookmarkManager::LineText)
375 return bookMark->lineText();
376 if (role == BookmarkManager::Note)
377 return bookMark->note();
378 if (role == Qt::ToolTipRole)
379 return bookMark->fileName().toUserOutput();
380 return QVariant();
381 }
382
flags(const QModelIndex & index) const383 Qt::ItemFlags BookmarkManager::flags(const QModelIndex &index) const
384 {
385 if (!index.isValid() || index.column() !=0 || index.row() < 0 || index.row() >= m_bookmarksList.count())
386 return Qt::NoItemFlags;
387 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
388 }
389
supportedDragActions() const390 Qt::DropActions BookmarkManager::supportedDragActions() const
391 {
392 return Qt::MoveAction;
393 }
394
mimeTypes() const395 QStringList BookmarkManager::mimeTypes() const
396 {
397 return DropSupport::mimeTypesForFilePaths();
398 }
399
mimeData(const QModelIndexList & indexes) const400 QMimeData *BookmarkManager::mimeData(const QModelIndexList &indexes) const
401 {
402 auto data = new DropMimeData;
403 foreach (const QModelIndex &index, indexes) {
404 if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= m_bookmarksList.count())
405 continue;
406 Bookmark *bookMark = m_bookmarksList.at(index.row());
407 data->addFile(bookMark->fileName().toString(), bookMark->lineNumber());
408 }
409 return data;
410 }
411
toggleBookmark(const FilePath & fileName,int lineNumber)412 void BookmarkManager::toggleBookmark(const FilePath &fileName, int lineNumber)
413 {
414 if (lineNumber <= 0 || fileName.isEmpty())
415 return;
416
417 // Remove any existing bookmark on this line
418 if (Bookmark *mark = findBookmark(fileName, lineNumber)) {
419 // TODO check if the bookmark is really on the same markable Interface
420 deleteBookmark(mark);
421 return;
422 }
423
424 // Add a new bookmark if no bookmark existed on this line
425 auto mark = new Bookmark(lineNumber, this);
426 mark->updateFileName(fileName);
427 const QModelIndex currentIndex = selectionModel()->currentIndex();
428 const int insertionIndex = currentIndex.isValid() ? currentIndex.row() + 1
429 : m_bookmarksList.size();
430 insertBookmark(insertionIndex, mark);
431 }
432
updateBookmark(Bookmark * bookmark)433 void BookmarkManager::updateBookmark(Bookmark *bookmark)
434 {
435 const int idx = m_bookmarksList.indexOf(bookmark);
436 if (idx == -1)
437 return;
438
439 emit dataChanged(index(idx, 0, QModelIndex()), index(idx, 2, QModelIndex()));
440 saveBookmarks();
441 }
442
updateBookmarkFileName(Bookmark * bookmark,const QString & oldFileName)443 void BookmarkManager::updateBookmarkFileName(Bookmark *bookmark, const QString &oldFileName)
444 {
445 if (oldFileName == bookmark->fileName().toString())
446 return;
447
448 m_bookmarksMap[Utils::FilePath::fromString(oldFileName)].removeAll(bookmark);
449 m_bookmarksMap[bookmark->fileName()].append(bookmark);
450 updateBookmark(bookmark);
451 }
452
removeAllBookmarks()453 void BookmarkManager::removeAllBookmarks()
454 {
455 if (m_bookmarksList.isEmpty())
456 return;
457 beginRemoveRows(QModelIndex(), 0, m_bookmarksList.size() - 1);
458
459 qDeleteAll(m_bookmarksList);
460 m_bookmarksMap.clear();
461 m_bookmarksList.clear();
462 endRemoveRows();
463 }
464
deleteBookmark(Bookmark * bookmark)465 void BookmarkManager::deleteBookmark(Bookmark *bookmark)
466 {
467 int idx = m_bookmarksList.indexOf(bookmark);
468 beginRemoveRows(QModelIndex(), idx, idx);
469
470 m_bookmarksMap[bookmark->fileName()].removeAll(bookmark);
471 delete bookmark;
472
473 m_bookmarksList.removeAt(idx);
474 endRemoveRows();
475
476 if (selectionModel()->currentIndex().isValid())
477 selectionModel()->setCurrentIndex(selectionModel()->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Clear);
478
479 updateActionStatus();
480 saveBookmarks();
481 }
482
bookmarkForIndex(const QModelIndex & index) const483 Bookmark *BookmarkManager::bookmarkForIndex(const QModelIndex &index) const
484 {
485 if (!index.isValid() || index.row() >= m_bookmarksList.size())
486 return nullptr;
487 return m_bookmarksList.at(index.row());
488 }
489
gotoBookmark(const Bookmark * bookmark) const490 bool BookmarkManager::gotoBookmark(const Bookmark *bookmark) const
491 {
492 if (IEditor *editor = EditorManager::openEditorAt(
493 Utils::Link(bookmark->fileName(), bookmark->lineNumber()))) {
494 return editor->currentLine() == bookmark->lineNumber();
495 }
496 return false;
497 }
498
nextInDocument()499 void BookmarkManager::nextInDocument()
500 {
501 documentPrevNext(true);
502 }
503
prevInDocument()504 void BookmarkManager::prevInDocument()
505 {
506 documentPrevNext(false);
507 }
508
documentPrevNext(bool next)509 void BookmarkManager::documentPrevNext(bool next)
510 {
511 IEditor *editor = EditorManager::currentEditor();
512 const int editorLine = editor->currentLine();
513 if (editorLine <= 0)
514 return;
515
516 const FilePath filePath = editor->document()->filePath();
517 if (!m_bookmarksMap.contains(filePath))
518 return;
519
520 int firstLine = -1;
521 int lastLine = -1;
522 int prevLine = -1;
523 int nextLine = -1;
524 const QVector<Bookmark *> marks = m_bookmarksMap[filePath];
525 for (int i = 0; i < marks.count(); ++i) {
526 int markLine = marks.at(i)->lineNumber();
527 if (firstLine == -1 || firstLine > markLine)
528 firstLine = markLine;
529 if (lastLine < markLine)
530 lastLine = markLine;
531 if (markLine < editorLine && prevLine < markLine)
532 prevLine = markLine;
533 if (markLine > editorLine &&
534 (nextLine == -1 || nextLine > markLine))
535 nextLine = markLine;
536 }
537
538 EditorManager::addCurrentPositionToNavigationHistory();
539 if (next) {
540 if (nextLine == -1)
541 editor->gotoLine(firstLine);
542 else
543 editor->gotoLine(nextLine);
544 } else {
545 if (prevLine == -1)
546 editor->gotoLine(lastLine);
547 else
548 editor->gotoLine(prevLine);
549 }
550 }
551
next()552 void BookmarkManager::next()
553 {
554 QModelIndex current = selectionModel()->currentIndex();
555 if (!current.isValid())
556 return;
557 int row = current.row();
558 ++row;
559 while (true) {
560 if (row == m_bookmarksList.size())
561 row = 0;
562
563 Bookmark *bk = m_bookmarksList.at(row);
564 if (gotoBookmark(bk)) {
565 QModelIndex newIndex = current.sibling(row, current.column());
566 selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select | QItemSelectionModel::Clear);
567 return;
568 }
569 deleteBookmark(bk);
570 if (m_bookmarksList.isEmpty()) // No bookmarks anymore ...
571 return;
572 }
573 }
574
prev()575 void BookmarkManager::prev()
576 {
577 QModelIndex current = selectionModel()->currentIndex();
578 if (!current.isValid())
579 return;
580 if (!isAtCurrentBookmark() && gotoBookmark(bookmarkForIndex(current)))
581 return;
582 int row = current.row();
583 while (true) {
584 if (row == 0)
585 row = m_bookmarksList.size();
586 --row;
587 Bookmark *bk = m_bookmarksList.at(row);
588 if (gotoBookmark(bk)) {
589 QModelIndex newIndex = current.sibling(row, current.column());
590 selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select | QItemSelectionModel::Clear);
591 return;
592 }
593 deleteBookmark(bk);
594 if (m_bookmarksList.isEmpty())
595 return;
596 }
597 }
598
state() const599 BookmarkManager::State BookmarkManager::state() const
600 {
601 if (m_bookmarksList.empty())
602 return NoBookMarks;
603
604 IEditor *editor = EditorManager::currentEditor();
605 if (!editor)
606 return HasBookMarks;
607
608 return m_bookmarksMap.value(editor->document()->filePath()).isEmpty() ? HasBookMarks
609 : HasBookmarksInDocument;
610 }
611
updateActionStatus()612 void BookmarkManager::updateActionStatus()
613 {
614 IEditor *editor = EditorManager::currentEditor();
615 const bool enableToggle = editor && !editor->document()->isTemporary();
616
617 emit updateActions(enableToggle, state());
618 }
619
moveUp()620 void BookmarkManager::moveUp()
621 {
622 QModelIndex current = selectionModel()->currentIndex();
623 int row = current.row();
624 if (row == 0)
625 row = m_bookmarksList.size();
626 --row;
627
628 // swap current.row() and row
629
630 Bookmark *b = m_bookmarksList.at(row);
631 m_bookmarksList[row] = m_bookmarksList.at(current.row());
632 m_bookmarksList[current.row()] = b;
633
634 QModelIndex topLeft = current.sibling(row, 0);
635 QModelIndex bottomRight = current.sibling(current.row(), 2);
636 emit dataChanged(topLeft, bottomRight);
637 selectionModel()->setCurrentIndex(current.sibling(row, 0), QItemSelectionModel::Select | QItemSelectionModel::Clear);
638
639 saveBookmarks();
640 }
641
moveDown()642 void BookmarkManager::moveDown()
643 {
644 QModelIndex current = selectionModel()->currentIndex();
645 int row = current.row();
646 ++row;
647 if (row == m_bookmarksList.size())
648 row = 0;
649
650 // swap current.row() and row
651 Bookmark *b = m_bookmarksList.at(row);
652 m_bookmarksList[row] = m_bookmarksList.at(current.row());
653 m_bookmarksList[current.row()] = b;
654
655 QModelIndex topLeft = current.sibling(current.row(), 0);
656 QModelIndex bottomRight = current.sibling(row, 2);
657 emit dataChanged(topLeft, bottomRight);
658 selectionModel()->setCurrentIndex(current.sibling(row, 0), QItemSelectionModel::Select | QItemSelectionModel::Clear);
659
660 saveBookmarks();
661 }
662
editByFileAndLine(const FilePath & fileName,int lineNumber)663 void BookmarkManager::editByFileAndLine(const FilePath &fileName, int lineNumber)
664 {
665 Bookmark *b = findBookmark(fileName, lineNumber);
666 QModelIndex current = selectionModel()->currentIndex();
667 selectionModel()->setCurrentIndex(current.sibling(m_bookmarksList.indexOf(b), 0),
668 QItemSelectionModel::Select | QItemSelectionModel::Clear);
669
670 edit();
671 }
672
edit()673 void BookmarkManager::edit()
674 {
675 QModelIndex current = selectionModel()->currentIndex();
676 Bookmark *b = m_bookmarksList.at(current.row());
677
678 QDialog dlg;
679 dlg.setWindowTitle(tr("Edit Bookmark"));
680 auto layout = new QFormLayout(&dlg);
681 auto noteEdit = new QLineEdit(b->note());
682 noteEdit->setMinimumWidth(300);
683 auto lineNumberSpinbox = new QSpinBox;
684 lineNumberSpinbox->setRange(1, INT_MAX);
685 lineNumberSpinbox->setValue(b->lineNumber());
686 lineNumberSpinbox->setMaximumWidth(100);
687 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
688 connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
689 connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
690 layout->addRow(tr("Note text:"), noteEdit);
691 layout->addRow(tr("Line number:"), lineNumberSpinbox);
692 layout->addWidget(buttonBox);
693 if (dlg.exec() == QDialog::Accepted) {
694 b->move(lineNumberSpinbox->value());
695 b->updateNote(noteEdit->text().replace(QLatin1Char('\t'), QLatin1Char(' ')));
696 emit dataChanged(current, current);
697 saveBookmarks();
698 }
699 }
700
701 /* Returns the bookmark at the given file and line number, or 0 if no such bookmark exists. */
findBookmark(const FilePath & filePath,int lineNumber)702 Bookmark *BookmarkManager::findBookmark(const FilePath &filePath, int lineNumber)
703 {
704 return Utils::findOrDefault(m_bookmarksMap.value(filePath),
705 Utils::equal(&Bookmark::lineNumber, lineNumber));
706 }
707
insertBookmark(int idx,Bookmark * bookmark,bool userset)708 void BookmarkManager::insertBookmark(int idx, Bookmark *bookmark, bool userset)
709 {
710 idx = qBound(0, idx, m_bookmarksList.size());
711 beginInsertRows(QModelIndex(), idx, idx);
712
713 m_bookmarksMap[bookmark->fileName()].append(bookmark);
714 m_bookmarksList.insert(idx, bookmark);
715
716 endInsertRows();
717 if (userset) {
718 updateActionStatus();
719 saveBookmarks();
720 }
721 selectionModel()->setCurrentIndex(index(idx, 0, QModelIndex()),
722 QItemSelectionModel::Select | QItemSelectionModel::Clear);
723 }
724
725 /* Adds a bookmark to the internal data structures. The 'userset' parameter
726 * determines whether action status should be updated and whether the bookmarks
727 * should be saved to the session settings.
728 */
addBookmark(Bookmark * bookmark,bool userset)729 void BookmarkManager::addBookmark(Bookmark *bookmark, bool userset)
730 {
731 insertBookmark(m_bookmarksList.size(), bookmark, userset);
732 }
733
734 /* Adds a new bookmark based on information parsed from the string. */
addBookmark(const QString & s)735 void BookmarkManager::addBookmark(const QString &s)
736 {
737 // index3 is a frontier beetween note text and other bookmarks data
738 int index3 = s.lastIndexOf(QLatin1Char('\t'));
739 if (index3 < 0)
740 index3 = s.size();
741 int index2 = s.lastIndexOf(QLatin1Char(':'), index3 - 1);
742 int index1 = s.indexOf(QLatin1Char(':'));
743
744 if (index3 != -1 || index2 != -1 || index1 != -1) {
745 const QString &filePath = s.mid(index1+1, index2-index1-1);
746 const QString ¬e = s.mid(index3 + 1);
747 const int lineNumber = s.mid(index2 + 1, index3 - index2 - 1).toInt();
748 if (!filePath.isEmpty() && !findBookmark(FilePath::fromString(filePath), lineNumber)) {
749 auto b = new Bookmark(lineNumber, this);
750 b->updateFileName(FilePath::fromString(filePath));
751 b->setNote(note);
752 addBookmark(b, false);
753 }
754 } else {
755 qDebug() << "BookmarkManager::addBookmark() Invalid bookmark string:" << s;
756 }
757 }
758
759 /* Puts the bookmark in a string for storing it in the settings. */
bookmarkToString(const Bookmark * b)760 QString BookmarkManager::bookmarkToString(const Bookmark *b)
761 {
762 const QLatin1Char colon(':');
763 // Using \t as delimiter because any another symbol can be a part of note.
764 const QLatin1Char noteDelimiter('\t');
765 return colon + b->fileName().toString() +
766 colon + QString::number(b->lineNumber()) +
767 noteDelimiter + b->note();
768 }
769
770 /* Saves the bookmarks to the session settings. */
saveBookmarks()771 void BookmarkManager::saveBookmarks()
772 {
773 QStringList list;
774 foreach (const Bookmark *bookmark, m_bookmarksList)
775 list << bookmarkToString(bookmark);
776
777 SessionManager::setValue(QLatin1String("Bookmarks"), list);
778 }
779
780 /* Loads the bookmarks from the session settings. */
loadBookmarks()781 void BookmarkManager::loadBookmarks()
782 {
783 removeAllBookmarks();
784 const QStringList &list = SessionManager::value(QLatin1String("Bookmarks")).toStringList();
785 foreach (const QString &bookmarkString, list)
786 addBookmark(bookmarkString);
787
788 updateActionStatus();
789 }
790
isAtCurrentBookmark() const791 bool BookmarkManager::isAtCurrentBookmark() const
792 {
793 Bookmark *bk = bookmarkForIndex(selectionModel()->currentIndex());
794 if (!bk)
795 return true;
796 IEditor *currentEditor = EditorManager::currentEditor();
797 return currentEditor
798 && currentEditor->document()->filePath() == bk->fileName()
799 && currentEditor->currentLine() == bk->lineNumber();
800 }
801
802 // BookmarkViewFactory
803
BookmarkViewFactory(BookmarkManager * bm)804 BookmarkViewFactory::BookmarkViewFactory(BookmarkManager *bm)
805 : m_manager(bm)
806 {
807 setDisplayName(BookmarkView::tr("Bookmarks"));
808 setPriority(300);
809 setId("Bookmarks");
810 setActivationSequence(QKeySequence(useMacShortcuts ? tr("Alt+Meta+M") : tr("Alt+M")));
811 }
812
createWidget()813 NavigationView BookmarkViewFactory::createWidget()
814 {
815 auto view = new BookmarkView(m_manager);
816 return {view, view->createToolBarWidgets()};
817 }
818
819 } // namespace Internal
820 } // namespace Bookmarks
821