bookmarkmanager.cpp 27.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8
9
10
11
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
12
13
14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16
17
18
19
20
21
22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
hjk's avatar
hjk committed
25

con's avatar
con committed
26
#include "bookmarkmanager.h"
hjk's avatar
hjk committed
27

con's avatar
con committed
28
29
30
31
32
#include "bookmark.h"
#include "bookmarksplugin.h"
#include "bookmarks_global.h"

#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/icon.h>
41
#include <utils/tooltip/tooltip.h>
hjk's avatar
hjk committed
42
#include <utils/qtcassert.h>
43
#include <utils/checkablemessagebox.h>
44
#include <utils/theme/theme.h>
45
#include <utils/dropsupport.h>
con's avatar
con committed
46

47
48
#include <QAction>
#include <QContextMenuEvent>
49
#include <QDebug>
50
51
#include <QDialog>
#include <QDialogButtonBox>
52
53
#include <QDir>
#include <QFileInfo>
54
55
#include <QFormLayout>
#include <QLineEdit>
56
57
#include <QMenu>
#include <QPainter>
58
#include <QSpinBox>
con's avatar
con committed
59
60
61
62

Q_DECLARE_METATYPE(Bookmarks::Internal::Bookmark*)

using namespace ProjectExplorer;
63
using namespace Core;
hjk's avatar
hjk committed
64
using namespace Utils;
con's avatar
con committed
65

66
67
68
namespace Bookmarks {
namespace Internal {

con's avatar
con committed
69
BookmarkDelegate::BookmarkDelegate(QObject *parent)
hjk's avatar
hjk committed
70
    : QStyledItemDelegate(parent)
con's avatar
con committed
71
72
73
74
75
{
}

QSize BookmarkDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
76
    QStyleOptionViewItem opt = option;
con's avatar
con committed
77
78
79
80
81
82
83
84
85
    initStyleOption(&opt, index);

    QFontMetrics fm(option.font);
    QSize s;
    s.setWidth(option.rect.width());
    s.setHeight(fm.height() * 2 + 10);
    return s;
}

86
void BookmarkDelegate::generateGradientPixmap(int width, int height, const QColor &color, bool selected) const
con's avatar
con committed
87
88
89
90
{
    QColor c = color;
    c.setAlpha(0);

hjk's avatar
hjk committed
91
92
    QPixmap pixmap(width+1, height);
    pixmap.fill(c);
con's avatar
con committed
93

hjk's avatar
hjk committed
94
    QPainter painter(&pixmap);
con's avatar
con committed
95
96
97
98
99
100
101
102
103
    painter.setPen(Qt::NoPen);

    QLinearGradient lg;
    lg.setCoordinateMode(QGradient::ObjectBoundingMode);
    lg.setFinalStop(1,0);

    lg.setColorAt(0, c);
    lg.setColorAt(0.4, color);

hjk's avatar
hjk committed
104
    painter.setBrush(lg);
con's avatar
con committed
105
106
107
108
109
110
111
112
113
114
    painter.drawRect(0, 0, width+1, height);

    if (selected)
        m_selectedPixmap = pixmap;
    else
        m_normalPixmap = pixmap;
}

void BookmarkDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
115
    QStyleOptionViewItem opt = option;
con's avatar
con committed
116
117
118
119
    initStyleOption(&opt, index);
    painter->save();

    QFontMetrics fm(opt.font);
120
    static int lwidth = fm.width(QLatin1String("8888")) + 18;
con's avatar
con committed
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150

    QColor backgroundColor;
    QColor textColor;

    bool selected = opt.state & QStyle::State_Selected;

    if (selected) {
        painter->setBrush(opt.palette.highlight().color());
        backgroundColor = opt.palette.highlight().color();
        if (!m_selectedPixmap)
            generateGradientPixmap(lwidth, fm.height()+1, backgroundColor, selected);
    } else {
        painter->setBrush(opt.palette.background().color());
        backgroundColor = opt.palette.background().color();
        if (!m_normalPixmap)
            generateGradientPixmap(lwidth, fm.height(), backgroundColor, selected);
    }
    painter->setPen(Qt::NoPen);
    painter->drawRect(opt.rect);

    // Set Text Color
    if (opt.state & QStyle::State_Selected)
        textColor = opt.palette.highlightedText().color();
    else
        textColor = opt.palette.text().color();

    painter->setPen(textColor);


    // TopLeft
hjk's avatar
hjk committed
151
    QString topLeft = index.data(BookmarkManager::Filename).toString();
con's avatar
con committed
152
153
154
    painter->drawText(6, 2 + opt.rect.top() + fm.ascent(), topLeft);

    QString topRight = index.data(BookmarkManager::LineNumber).toString();
Tobias Hunger's avatar
Tobias Hunger committed
155
    // Check whether we need to be fancy and paint some background
con's avatar
con committed
156
157
158
    int fwidth = fm.width(topLeft);
    if (fwidth + lwidth > opt.rect.width()) {
        int left = opt.rect.right() - lwidth;
hjk's avatar
hjk committed
159
        painter->drawPixmap(left, opt.rect.top(), selected ? m_selectedPixmap : m_normalPixmap);
con's avatar
con committed
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
    }
    // topRight
    painter->drawText(opt.rect.right() - fm.width(topRight) - 6 , 2 + opt.rect.top() + fm.ascent(), topRight);

    // Directory
    QColor mix;
    mix.setRgbF(0.7 * textColor.redF()   + 0.3 * backgroundColor.redF(),
                0.7 * textColor.greenF() + 0.3 * backgroundColor.greenF(),
                0.7 * textColor.blueF()  + 0.3 * backgroundColor.blueF());
    painter->setPen(mix);
//
//    QString directory = index.data(BookmarkManager::Directory).toString();
//    int availableSpace = opt.rect.width() - 12;
//    if (fm.width(directory) > availableSpace) {
//        // We need a shorter directory
//        availableSpace -= fm.width("...");
//
//        int pos = directory.size();
//        int idx;
//        forever {
//            idx = directory.lastIndexOf("/", pos-1);
hjk's avatar
hjk committed
181
//            if (idx == -1) {
con's avatar
con committed
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//                // Can't happen, this means the string did fit after all?
//                break;
//            }
//            int width = fm.width(directory.mid(idx, pos-idx));
//            if (width > availableSpace) {
//                directory = "..." + directory.mid(pos);
//                break;
//            } else {
//                pos = idx;
//                availableSpace -= width;
//            }
//        }
//    }
//
//    painter->drawText(3, opt.rect.top() + fm.ascent() + fm.height() + 6, directory);

Orgad Shaneh's avatar
Orgad Shaneh committed
198
199
200
    QString lineText = index.data(BookmarkManager::Note).toString().trimmed();
    if (lineText.isEmpty())
        lineText = index.data(BookmarkManager::LineText).toString().trimmed();
201

Orgad Shaneh's avatar
Orgad Shaneh committed
202
    painter->drawText(6, opt.rect.top() + fm.ascent() + fm.height() + 6, lineText);
con's avatar
con committed
203
204
205
206
207
208
209

    // Separator lines
    painter->setPen(QColor::fromRgb(150,150,150));
    painter->drawLine(0, opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());
    painter->restore();
}

hjk's avatar
hjk committed
210
BookmarkView::BookmarkView(BookmarkManager *manager)  :
211
    m_bookmarkContext(new IContext(this)),
hjk's avatar
hjk committed
212
    m_manager(manager)
con's avatar
con committed
213
214
215
{
    setWindowTitle(tr("Bookmarks"));

hjk's avatar
hjk committed
216
    m_bookmarkContext->setWidget(this);
217
    m_bookmarkContext->setContext(Context(Constants::BOOKMARKS_CONTEXT));
con's avatar
con committed
218

hjk's avatar
hjk committed
219
    ICore::addContextObject(m_bookmarkContext);
con's avatar
con committed
220

221
    ListView::setModel(manager);
hjk's avatar
hjk committed
222

con's avatar
con committed
223
224
225
226
    setItemDelegate(new BookmarkDelegate(this));
    setFrameStyle(QFrame::NoFrame);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setFocusPolicy(Qt::NoFocus);
hjk's avatar
hjk committed
227
228
229
    setSelectionModel(manager->selectionModel());
    setSelectionMode(QAbstractItemView::SingleSelection);
    setSelectionBehavior(QAbstractItemView::SelectRows);
230
231
    setDragEnabled(true);
    setDragDropMode(QAbstractItemView::DragOnly);
hjk's avatar
hjk committed
232

hjk's avatar
hjk committed
233
234
    connect(this, &QAbstractItemView::clicked, this, &BookmarkView::gotoBookmark);
    connect(this, &QAbstractItemView::activated, this, &BookmarkView::gotoBookmark);
con's avatar
con committed
235
236
237
238
}

BookmarkView::~BookmarkView()
{
hjk's avatar
hjk committed
239
    ICore::removeContextObject(m_bookmarkContext);
con's avatar
con committed
240
241
242
243
244
}

void BookmarkView::contextMenuEvent(QContextMenuEvent *event)
{
    QMenu menu;
245
246
    QAction *moveUp = menu.addAction(tr("Move Up"));
    QAction *moveDown = menu.addAction(tr("Move Down"));
247
    QAction *edit = menu.addAction(tr("&Edit"));
248
    menu.addSeparator();
249
    QAction *remove = menu.addAction(tr("&Remove"));
250
    menu.addSeparator();
251
    QAction *removeAll = menu.addAction(tr("Remove All"));
252

con's avatar
con committed
253
    m_contextMenuIndex = indexAt(event->pos());
254
255
256
    if (!m_contextMenuIndex.isValid()) {
        moveUp->setEnabled(false);
        moveDown->setEnabled(false);
con's avatar
con committed
257
        remove->setEnabled(false);
258
        edit->setEnabled(false);
259
    }
con's avatar
con committed
260
261
262
263

    if (model()->rowCount() == 0)
        removeAll->setEnabled(false);

hjk's avatar
hjk committed
264
265
266
267
268
    connect(moveUp, &QAction::triggered, m_manager, &BookmarkManager::moveUp);
    connect(moveDown, &QAction::triggered, m_manager, &BookmarkManager::moveDown);
    connect(remove, &QAction::triggered, this, &BookmarkView::removeFromContextMenu);
    connect(removeAll, &QAction::triggered, this, &BookmarkView::removeAll);
    connect(edit, &QAction::triggered, m_manager, &BookmarkManager::edit);
con's avatar
con committed
269
270
271
272
273
274
275
276
277
278
279

    menu.exec(mapToGlobal(event->pos()));
}

void BookmarkView::removeFromContextMenu()
{
    removeBookmark(m_contextMenuIndex);
}

void BookmarkView::removeBookmark(const QModelIndex& index)
{
280
    Bookmark *bm = m_manager->bookmarkForIndex(index);
281
    m_manager->deleteBookmark(bm);
con's avatar
con committed
282
283
}

284
285
286
287
288
289
290
void BookmarkView::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Delete) {
        removeBookmark(currentIndex());
        event->accept();
        return;
    }
291
    ListView::keyPressEvent(event);
292
293
}

con's avatar
con committed
294
295
void BookmarkView::removeAll()
{
296
    if (CheckableMessageBox::doNotAskAgainQuestion(this,
297
298
299
300
301
            tr("Remove All Bookmarks"),
            tr("Are you sure you want to remove all bookmarks from all files in the current session?"),
            ICore::settings(),
            QLatin1String("RemoveAllBookmarks")) != QDialogButtonBox::Yes)
        return;
302
303

    // The performance of this function could be greatly improved.
304
305
    while (m_manager->rowCount()) {
        QModelIndex index = m_manager->index(0, 0);
con's avatar
con committed
306
307
308
309
310
311
        removeBookmark(index);
    }
}

void BookmarkView::gotoBookmark(const QModelIndex &index)
{
312
313
    Bookmark *bk = m_manager->bookmarkForIndex(index);
    if (!m_manager->gotoBookmark(bk))
314
        m_manager->deleteBookmark(bk);
con's avatar
con committed
315
316
317
318
319
320
}

////
// BookmarkManager
////

321
BookmarkManager::BookmarkManager() :
322
323
    m_bookmarkIcon(Utils::Icon({{QLatin1String(":/bookmarks/images/bookmark.png"),
                                 Theme::Bookmarks_TextMarkColor}}, Icon::Tint).pixmap()),
324
    m_selectionModel(new QItemSelectionModel(this, this))
con's avatar
con committed
325
{
326
    connect(ICore::instance(), &ICore::contextChanged,
hjk's avatar
hjk committed
327
            this, &BookmarkManager::updateActionStatus);
con's avatar
con committed
328

hjk's avatar
hjk committed
329
330
    connect(SessionManager::instance(), &SessionManager::sessionLoaded,
            this, &BookmarkManager::loadBookmarks);
con's avatar
con committed
331
332

    updateActionStatus();
333
334
    Bookmark::setCategoryColor(Constants::BOOKMARKS_TEXT_MARK_CATEGORY,
                               Theme::Bookmarks_TextMarkColor);
con's avatar
con committed
335
336
337
338
339
340
341
342
}

BookmarkManager::~BookmarkManager()
{
    DirectoryFileBookmarksMap::iterator it, end;
    end = m_bookmarksMap.end();
    for (it = m_bookmarksMap.begin(); it != end; ++it) {
        FileNameBookmarksMap *bookmarks = it.value();
con's avatar
con committed
343
        qDeleteAll(*bookmarks);
con's avatar
con committed
344
345
346
347
348
349
350
351
352
        delete bookmarks;
    }
}

QItemSelectionModel *BookmarkManager::selectionModel() const
{
    return m_selectionModel;
}

353
354
bool BookmarkManager::hasBookmarkInPosition(const QString &fileName, int lineNumber)
{
hjk's avatar
hjk committed
355
    return findBookmark(fileName, lineNumber);
356
357
}

con's avatar
con committed
358
359
360
361
362
QModelIndex BookmarkManager::index(int row, int column, const QModelIndex &parent) const
{
    if (parent.isValid())
        return QModelIndex();
    else
363
        return createIndex(row, column);
con's avatar
con committed
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
}

QModelIndex BookmarkManager::parent(const QModelIndex &) const
{
    return QModelIndex();
}

int BookmarkManager::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;
    else
        return m_bookmarksList.count();
}

int BookmarkManager::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;
    return 3;
}

QVariant BookmarkManager::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || index.column() !=0 || index.row() < 0 || index.row() >= m_bookmarksList.count())
        return QVariant();

hjk's avatar
hjk committed
391
    Bookmark *bookMark = m_bookmarksList.at(index.row());
con's avatar
con committed
392
    if (role == BookmarkManager::Filename)
393
        return FileName::fromString(bookMark->fileName()).fileName();
hjk's avatar
hjk committed
394
    if (role == BookmarkManager::LineNumber)
hjk's avatar
hjk committed
395
        return bookMark->lineNumber();
hjk's avatar
hjk committed
396
    if (role == BookmarkManager::Directory)
hjk's avatar
hjk committed
397
        return QFileInfo(bookMark->fileName()).path();
hjk's avatar
hjk committed
398
    if (role == BookmarkManager::LineText)
hjk's avatar
hjk committed
399
        return bookMark->lineText();
hjk's avatar
hjk committed
400
    if (role == BookmarkManager::Note)
hjk's avatar
hjk committed
401
        return bookMark->note();
hjk's avatar
hjk committed
402
    if (role == Qt::ToolTipRole)
hjk's avatar
hjk committed
403
        return QDir::toNativeSeparators(bookMark->fileName());
con's avatar
con committed
404
405
406
    return QVariant();
}

407
408
409
410
411
412
413
414
415
416
417
418
419
420
Qt::ItemFlags BookmarkManager::flags(const QModelIndex &index) const
{
    if (!index.isValid() || index.column() !=0 || index.row() < 0 || index.row() >= m_bookmarksList.count())
        return Qt::NoItemFlags;
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
}

Qt::DropActions BookmarkManager::supportedDragActions() const
{
    return Qt::MoveAction;
}

QStringList BookmarkManager::mimeTypes() const
{
421
    return DropSupport::mimeTypesForFilePaths();
422
423
424
425
}

QMimeData *BookmarkManager::mimeData(const QModelIndexList &indexes) const
{
426
    auto data = new DropMimeData;
427
428
429
430
431
432
433
434
435
    foreach (const QModelIndex &index, indexes) {
        if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= m_bookmarksList.count())
            continue;
        Bookmark *bookMark = m_bookmarksList.at(index.row());
        data->addFile(bookMark->fileName(), bookMark->lineNumber());
    }
    return data;
}

hjk's avatar
hjk committed
436
437
void BookmarkManager::toggleBookmark(const QString &fileName, int lineNumber)
{
438
    if (lineNumber <= 0 || fileName.isEmpty())
hjk's avatar
hjk committed
439
        return;
con's avatar
con committed
440
441

    // Remove any existing bookmark on this line
hjk's avatar
hjk committed
442
    if (Bookmark *mark = findBookmark(fileName, lineNumber)) {
con's avatar
con committed
443
        // TODO check if the bookmark is really on the same markable Interface
444
        deleteBookmark(mark);
con's avatar
con committed
445
446
447
448
        return;
    }

    // Add a new bookmark if no bookmark existed on this line
hjk's avatar
hjk committed
449
450
451
    Bookmark *mark = new Bookmark(lineNumber, this);
    mark->updateFileName(fileName);
    addBookmark(mark);
con's avatar
con committed
452
453
454
455
}

void BookmarkManager::updateBookmark(Bookmark *bookmark)
{
456
457
458
459
    const int idx = m_bookmarksList.indexOf(bookmark);
    if (idx == -1)
        return;

con's avatar
con committed
460
461
462
463
    emit dataChanged(index(idx, 0, QModelIndex()), index(idx, 2, QModelIndex()));
    saveBookmarks();
}

464
465
466
467
468
469
470
471
472
473
474
void BookmarkManager::updateBookmarkFileName(Bookmark *bookmark, const QString &oldFileName)
{
    if (oldFileName == bookmark->fileName())
        return;

    if (removeBookmarkFromMap(bookmark, oldFileName))
        addBookmarkToMap(bookmark);

    updateBookmark(bookmark);
}

475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
void BookmarkManager::removeAllBookmarks()
{
    if (m_bookmarksList.isEmpty())
        return;
    beginRemoveRows(QModelIndex(), 0, m_bookmarksList.size() - 1);

    DirectoryFileBookmarksMap::const_iterator it, end;
    end = m_bookmarksMap.constEnd();
    for (it = m_bookmarksMap.constBegin(); it != end; ++it) {
        FileNameBookmarksMap *files = it.value();
        FileNameBookmarksMap::const_iterator jt, jend;
        jend = files->constEnd();
        for (jt = files->constBegin(); jt != jend; ++jt) {
            delete jt.value();
        }
        files->clear();
        delete files;
    }
    m_bookmarksMap.clear();
    m_bookmarksList.clear();
    endRemoveRows();
}

498
bool BookmarkManager::removeBookmarkFromMap(Bookmark *bookmark, const QString &fileName)
con's avatar
con committed
499
{
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
    bool found = false;
    const QFileInfo fi(fileName.isEmpty() ? bookmark->fileName() : fileName);
    if (FileNameBookmarksMap *files = m_bookmarksMap.value(fi.path())) {
        FileNameBookmarksMap::iterator i = files->begin();
        while (i != files->end()) {
            if (i.value() == bookmark) {
                files->erase(i);
                found = true;
                break;
            }
            ++i;
        }
        if (files->count() <= 0) {
            m_bookmarksMap.remove(fi.path());
            delete files;
con's avatar
con committed
515
516
        }
    }
517
518
    return found;
}
con's avatar
con committed
519

520
521
522
523
524
525
526
void BookmarkManager::deleteBookmark(Bookmark *bookmark)
{
    int idx = m_bookmarksList.indexOf(bookmark);
    beginRemoveRows(QModelIndex(), idx, idx);

    removeBookmarkFromMap(bookmark);
    delete bookmark;
527

con's avatar
con committed
528
529
530
531
532
533
534
535
536
537
    m_bookmarksList.removeAt(idx);
    endRemoveRows();

    if (selectionModel()->currentIndex().isValid())
        selectionModel()->setCurrentIndex(selectionModel()->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Clear);

    updateActionStatus();
    saveBookmarks();
}

hjk's avatar
hjk committed
538
Bookmark *BookmarkManager::bookmarkForIndex(const QModelIndex &index) const
con's avatar
con committed
539
540
541
542
543
544
{
    if (!index.isValid() || index.row() >= m_bookmarksList.size())
        return 0;
    return m_bookmarksList.at(index.row());
}

hjk's avatar
hjk committed
545
bool BookmarkManager::gotoBookmark(Bookmark *bookmark)
con's avatar
con committed
546
{
hjk's avatar
hjk committed
547
    if (IEditor *editor = EditorManager::openEditorAt(bookmark->fileName(), bookmark->lineNumber()))
hjk's avatar
hjk committed
548
        return editor->currentLine() == bookmark->lineNumber();
549
    return false;
con's avatar
con committed
550
551
552
553
554
555
556
557
558
559
560
561
562
563
}

void BookmarkManager::nextInDocument()
{
    documentPrevNext(true);
}

void BookmarkManager::prevInDocument()
{
    documentPrevNext(false);
}

void BookmarkManager::documentPrevNext(bool next)
{
hjk's avatar
hjk committed
564
565
566
567
568
    IEditor *editor = EditorManager::currentEditor();
    const int editorLine = editor->currentLine();
    if (editorLine <= 0)
        return;

569
    const QFileInfo fi = editor->document()->filePath().toFileInfo();
con's avatar
con committed
570
571
572
573
574
575
576
    if (!m_bookmarksMap.contains(fi.path()))
        return;

    int firstLine = -1;
    int lastLine = -1;
    int prevLine = -1;
    int nextLine = -1;
hjk's avatar
hjk committed
577
    const QList<Bookmark *> marks = m_bookmarksMap.value(fi.path())->values(fi.fileName());
con's avatar
con committed
578
579
580
581
582
583
584
585
586
587
588
589
590
    for (int i = 0; i < marks.count(); ++i) {
        int markLine = marks.at(i)->lineNumber();
        if (firstLine == -1 || firstLine > markLine)
            firstLine = markLine;
        if (lastLine < markLine)
            lastLine = markLine;
        if (markLine < editorLine && prevLine < markLine)
            prevLine = markLine;
        if (markLine > editorLine &&
            (nextLine == -1 || nextLine > markLine))
            nextLine = markLine;
    }

591
    EditorManager::addCurrentPositionToNavigationHistory();
con's avatar
con committed
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
    if (next) {
        if (nextLine == -1)
            editor->gotoLine(firstLine);
        else
            editor->gotoLine(nextLine);
    } else {
        if (prevLine == -1)
            editor->gotoLine(lastLine);
        else
            editor->gotoLine(prevLine);
    }
}

void BookmarkManager::next()
{
    QModelIndex current = selectionModel()->currentIndex();
    if (!current.isValid())
        return;
610
611
612
613
614
615
616
617
618
619
620
621
    int row = current.row();
    ++row;
    while (true) {
        if (row == m_bookmarksList.size())
            row = 0;

        Bookmark *bk = m_bookmarksList.at(row);
        if (gotoBookmark(bk)) {
            QModelIndex newIndex = current.sibling(row, current.column());
            selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select | QItemSelectionModel::Clear);
            return;
        }
622
        deleteBookmark(bk);
623
624
625
        if (m_bookmarksList.isEmpty()) // No bookmarks anymore ...
            return;
    }
con's avatar
con committed
626
627
628
629
630
631
632
}

void BookmarkManager::prev()
{
    QModelIndex current = selectionModel()->currentIndex();
    if (!current.isValid())
        return;
633

con's avatar
con committed
634
    int row = current.row();
635
636
637
638
639
640
641
642
643
644
    while (true) {
        if (row == 0)
            row = m_bookmarksList.size();
        --row;
        Bookmark *bk = m_bookmarksList.at(row);
        if (gotoBookmark(bk)) {
            QModelIndex newIndex = current.sibling(row, current.column());
            selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select | QItemSelectionModel::Clear);
            return;
        }
645
        deleteBookmark(bk);
646
647
648
        if (m_bookmarksList.isEmpty())
            return;
    }
con's avatar
con committed
649
650
651
652
653
654
655
}

BookmarkManager::State BookmarkManager::state() const
{
    if (m_bookmarksMap.empty())
        return NoBookMarks;

hjk's avatar
hjk committed
656
    IEditor *editor = EditorManager::currentEditor();
con's avatar
con committed
657
658
659
    if (!editor)
        return HasBookMarks;

660
    const QFileInfo fi = editor->document()->filePath().toFileInfo();
con's avatar
con committed
661
662
663
664
665
666
667
668
669
670

    const DirectoryFileBookmarksMap::const_iterator dit = m_bookmarksMap.constFind(fi.path());
    if (dit == m_bookmarksMap.constEnd())
        return HasBookMarks;

    return HasBookmarksInDocument;
}

void BookmarkManager::updateActionStatus()
{
671
672
673
674
    IEditor *editor = EditorManager::currentEditor();
    const bool enableToggle = editor && !editor->document()->isTemporary();

    updateActions(enableToggle, state());
con's avatar
con committed
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
}

void BookmarkManager::moveUp()
{
    QModelIndex current = selectionModel()->currentIndex();
    int row = current.row();
    if (row == 0)
        row = m_bookmarksList.size();
     --row;

    // swap current.row() and row

    Bookmark *b = m_bookmarksList.at(row);
    m_bookmarksList[row] = m_bookmarksList.at(current.row());
    m_bookmarksList[current.row()] = b;

    QModelIndex topLeft = current.sibling(row, 0);
    QModelIndex bottomRight = current.sibling(current.row(), 2);
    emit dataChanged(topLeft, bottomRight);
    selectionModel()->setCurrentIndex(current.sibling(row, 0), QItemSelectionModel::Select | QItemSelectionModel::Clear);
Vasiliy Sorokin's avatar
Vasiliy Sorokin committed
695
696

    saveBookmarks();
con's avatar
con committed
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
}

void BookmarkManager::moveDown()
{
    QModelIndex current = selectionModel()->currentIndex();
    int row = current.row();
    ++row;
    if (row == m_bookmarksList.size())
        row = 0;

    // swap current.row() and row
    Bookmark *b = m_bookmarksList.at(row);
    m_bookmarksList[row] = m_bookmarksList.at(current.row());
    m_bookmarksList[current.row()] = b;

    QModelIndex topLeft = current.sibling(current.row(), 0);
    QModelIndex bottomRight = current.sibling(row, 2);
    emit dataChanged(topLeft, bottomRight);
    selectionModel()->setCurrentIndex(current.sibling(row, 0), QItemSelectionModel::Select | QItemSelectionModel::Clear);
Vasiliy Sorokin's avatar
Vasiliy Sorokin committed
716
717

    saveBookmarks();
con's avatar
con committed
718
719
}

hjk's avatar
hjk committed
720
void BookmarkManager::editByFileAndLine(const QString &fileName, int lineNumber)
721
{
hjk's avatar
hjk committed
722
    Bookmark *b = findBookmark(fileName, lineNumber);
723
724
725
726
    QModelIndex current = selectionModel()->currentIndex();
    selectionModel()->setCurrentIndex(current.sibling(m_bookmarksList.indexOf(b), 0),
                                      QItemSelectionModel::Select | QItemSelectionModel::Clear);

727
    edit();
728
729
}

730
void BookmarkManager::edit()
731
732
733
734
{
    QModelIndex current = selectionModel()->currentIndex();
    Bookmark *b = m_bookmarksList.at(current.row());

735
736
737
738
    QDialog dlg;
    dlg.setWindowTitle(tr("Edit Bookmark"));
    auto layout = new QFormLayout(&dlg);
    auto noteEdit = new QLineEdit(b->note());
739
    noteEdit->setMinimumWidth(300);
740
741
742
    auto lineNumberSpinbox = new QSpinBox;
    lineNumberSpinbox->setRange(1, INT_MAX);
    lineNumberSpinbox->setValue(b->lineNumber());
743
    lineNumberSpinbox->setMaximumWidth(100);
744
    auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
hjk's avatar
hjk committed
745
746
    connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
    connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
747
    layout->addRow(tr("Note text:"), noteEdit);
748
    layout->addRow(tr("Line number:"), lineNumberSpinbox);
749
750
    layout->addWidget(buttonBox);
    if (dlg.exec() == QDialog::Accepted) {
751
        b->move(lineNumberSpinbox->value());
752
        b->updateNote(noteEdit->text().replace(QLatin1Char('\t'), QLatin1Char(' ')));
753
        emit dataChanged(current, current);
Vasiliy Sorokin's avatar
Vasiliy Sorokin committed
754
        saveBookmarks();
755
756
757
    }
}

con's avatar
con committed
758
/* Returns the bookmark at the given file and line number, or 0 if no such bookmark exists. */
hjk's avatar
hjk committed
759
Bookmark *BookmarkManager::findBookmark(const QString &filePath, int lineNumber)
con's avatar
con committed
760
{
hjk's avatar
hjk committed
761
762
    QFileInfo fi(filePath);
    QString path = fi.path();
con's avatar
con committed
763
    if (m_bookmarksMap.contains(path)) {
hjk's avatar
hjk committed
764
        foreach (Bookmark *bookmark, m_bookmarksMap.value(path)->values(fi.fileName())) {
con's avatar
con committed
765
766
767
768
769
770
771
            if (bookmark->lineNumber() == lineNumber)
                return bookmark;
        }
    }
    return 0;
}

772
773
774
775
776
777
778
779
780
void BookmarkManager::addBookmarkToMap(Bookmark *bookmark)
{
    const QFileInfo fi(bookmark->fileName());
    const QString &path = fi.path();
    if (!m_bookmarksMap.contains(path))
        m_bookmarksMap.insert(path, new FileNameBookmarksMap());
    m_bookmarksMap.value(path)->insert(fi.fileName(), bookmark);
}

con's avatar
con committed
781
782
783
784
785
786
787
788
/* Adds a bookmark to the internal data structures. The 'userset' parameter
 * determines whether action status should be updated and whether the bookmarks
 * should be saved to the session settings.
 */
void BookmarkManager::addBookmark(Bookmark *bookmark, bool userset)
{
    beginInsertRows(QModelIndex(), m_bookmarksList.size(), m_bookmarksList.size());

789
    addBookmarkToMap(bookmark);
con's avatar
con committed
790
791
792
793
794
795
796
797
798
799
800
801
802
803

    m_bookmarksList.append(bookmark);

    endInsertRows();
    if (userset) {
        updateActionStatus();
        saveBookmarks();
    }
    selectionModel()->setCurrentIndex(index(m_bookmarksList.size()-1 , 0, QModelIndex()), QItemSelectionModel::Select | QItemSelectionModel::Clear);
}

/* Adds a new bookmark based on information parsed from the string. */
void BookmarkManager::addBookmark(const QString &s)
{
804
    // index3 is a frontier beetween note text and other bookmarks data
805
    int index3 = s.lastIndexOf(QLatin1Char('\t'));
806
807
    if (index3 < 0)
        index3 = s.size();
808
809
    int index2 = s.lastIndexOf(QLatin1Char(':'), index3 - 1);
    int index1 = s.indexOf(QLatin1Char(':'));
810
811

    if (index3 != -1 || index2 != -1 || index1 != -1) {
con's avatar
con committed
812
        const QString &filePath = s.mid(index1+1, index2-index1-1);
813
        const QString &note = s.mid(index3 + 1);
814
        const int lineNumber = s.midRef(index2 + 1, index3 - index2 - 1).toInt();
hjk's avatar
hjk committed
815
        if (!filePath.isEmpty() && !findBookmark(filePath, lineNumber)) {
hjk's avatar
hjk committed
816
817
            Bookmark *b = new Bookmark(lineNumber, this);
            b->updateFileName(filePath);
818
            b->setNote(note);
con's avatar
con committed
819
820
821
822
823
824
825
826
827
828
829
            addBookmark(b, false);
        }
    } else {
        qDebug() << "BookmarkManager::addBookmark() Invalid bookmark string:" << s;
    }
}

/* Puts the bookmark in a string for storing it in the settings. */
QString BookmarkManager::bookmarkToString(const Bookmark *b)
{
    const QLatin1Char colon(':');
830
831
    // Using \t as delimiter because any another symbol can be a part of note.
    const QLatin1Char noteDelimiter('\t');
hjk's avatar
hjk committed
832
    return colon + b->fileName() +
833
834
            colon + QString::number(b->lineNumber()) +
            noteDelimiter + b->note();
con's avatar
con committed
835
836
837
838
839
840
}

/* Saves the bookmarks to the session settings. */
void BookmarkManager::saveBookmarks()
{
    QStringList list;
Vasiliy Sorokin's avatar
Vasiliy Sorokin committed
841
    foreach (const Bookmark *bookmark, m_bookmarksList)
hjk's avatar
hjk committed
842
        list << bookmarkToString(bookmark);
con's avatar
con committed
843

hjk's avatar
hjk committed
844
    SessionManager::setValue(QLatin1String("Bookmarks"), list);
con's avatar
con committed
845
846
}

hjk's avatar
hjk committed
847
void BookmarkManager::operateTooltip(QWidget *widget, const QPoint &pos, Bookmark *mark)
848
849
850
851
{
    if (!mark)
        return;

852
    if (mark->note().isEmpty())
hjk's avatar
hjk committed
853
        ToolTip::hide();
854
    else
855
        ToolTip::show(pos, mark->note(), widget);
856
857
}

con's avatar
con committed
858
859
860
/* Loads the bookmarks from the session settings. */
void BookmarkManager::loadBookmarks()
{
861
    removeAllBookmarks();
hjk's avatar
hjk committed
862
    const QStringList &list = SessionManager::value(QLatin1String("Bookmarks")).toStringList();
con's avatar
con committed
863
864
865
866
867
868
    foreach (const QString &bookmarkString, list)
        addBookmark(bookmarkString);

    updateActionStatus();
}

hjk's avatar
hjk committed
869
void BookmarkManager::handleBookmarkTooltipRequest(IEditor *editor, const QPoint &pos, int line)
870
{
871
    Bookmark *mark = findBookmark(editor->document()->filePath().toString(), line);
hjk's avatar
hjk committed
872
    operateTooltip(editor->widget(), pos, mark);
873
874
}

con's avatar
con committed
875
876
877
878
879
// BookmarkViewFactory

BookmarkViewFactory::BookmarkViewFactory(BookmarkManager *bm)
    : m_manager(bm)
{
880
881
882
    setDisplayName(BookmarkView::tr("Bookmarks"));
    setPriority(300);
    setId("Bookmarks");
883
    setActivationSequence(QKeySequence(UseMacShortcuts ? tr("Alt+Meta+M") : tr("Alt+M")));
con's avatar
con committed
884
885
}

886
NavigationView BookmarkViewFactory::createWidget()
con's avatar
con committed
887
{
888
    return NavigationView(new BookmarkView(m_manager));
con's avatar
con committed
889
}
890
891
892

} // namespace Internal
} // namespace Bookmarks