1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "bookmarkmanager.h"
37 #include "bookmarksplugin.h"
38 #include "bookmarks_global.h"
40 #include <coreplugin/editormanager/editormanager.h>
41 #include <coreplugin/icore.h>
42 #include <coreplugin/actionmanager/actionmanager.h>
43 #include <projectexplorer/projectexplorer.h>
44 #include <projectexplorer/session.h>
45 #include <texteditor/basetexteditor.h>
46 #include <utils/qtcassert.h>
48 #include <QtCore/QDebug>
49 #include <QtCore/QFileInfo>
51 #include <QtGui/QAction>
52 #include <QtGui/QContextMenuEvent>
53 #include <QtGui/QMenu>
54 #include <QtGui/QPainter>
56 Q_DECLARE_METATYPE(Bookmarks::Internal::Bookmark*)
58 using namespace Bookmarks;
59 using namespace Bookmarks::Internal;
60 using namespace ProjectExplorer;
63 BookmarkDelegate::BookmarkDelegate(QObject *parent)
64 : QStyledItemDelegate(parent), m_normalPixmap(0), m_selectedPixmap(0)
68 BookmarkDelegate::~BookmarkDelegate()
70 delete m_normalPixmap;
71 delete m_selectedPixmap;
74 QSize BookmarkDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
76 QStyleOptionViewItemV4 opt = option;
77 initStyleOption(&opt, index);
79 QFontMetrics fm(option.font);
81 s.setWidth(option.rect.width());
82 s.setHeight(fm.height() * 2 + 10);
86 void BookmarkDelegate::generateGradientPixmap(int width, int height, QColor color, bool selected) const
92 QPixmap *pixmap = new QPixmap(width+1, height);
95 QPainter painter(pixmap);
96 painter.setPen(Qt::NoPen);
99 lg.setCoordinateMode(QGradient::ObjectBoundingMode);
100 lg.setFinalStop(1,0);
103 lg.setColorAt(0.4, color);
105 painter.setBrush(lg);
106 painter.drawRect(0, 0, width+1, height);
109 m_selectedPixmap = pixmap;
111 m_normalPixmap = pixmap;
114 void BookmarkDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
116 QStyleOptionViewItemV4 opt = option;
117 initStyleOption(&opt, index);
120 QFontMetrics fm(opt.font);
121 static int lwidth = fm.width("8888") + 18;
123 QColor backgroundColor;
126 bool selected = opt.state & QStyle::State_Selected;
129 painter->setBrush(opt.palette.highlight().color());
130 backgroundColor = opt.palette.highlight().color();
131 if (!m_selectedPixmap)
132 generateGradientPixmap(lwidth, fm.height()+1, backgroundColor, selected);
134 painter->setBrush(opt.palette.background().color());
135 backgroundColor = opt.palette.background().color();
137 generateGradientPixmap(lwidth, fm.height(), backgroundColor, selected);
139 painter->setPen(Qt::NoPen);
140 painter->drawRect(opt.rect);
143 if (opt.state & QStyle::State_Selected)
144 textColor = opt.palette.highlightedText().color();
146 textColor = opt.palette.text().color();
148 painter->setPen(textColor);
152 QString topLeft = index.data(BookmarkManager::Filename ).toString();
153 painter->drawText(6, 2 + opt.rect.top() + fm.ascent(), topLeft);
155 QString topRight = index.data(BookmarkManager::LineNumber).toString();
156 // Check whether we need to be fancy and paint some background
157 int fwidth = fm.width(topLeft);
158 if (fwidth + lwidth > opt.rect.width()) {
159 int left = opt.rect.right() - lwidth;
160 painter->drawPixmap(left, opt.rect.top(), selected? *m_selectedPixmap : *m_normalPixmap);
163 painter->drawText(opt.rect.right() - fm.width(topRight) - 6 , 2 + opt.rect.top() + fm.ascent(), topRight);
167 mix.setRgbF(0.7 * textColor.redF() + 0.3 * backgroundColor.redF(),
168 0.7 * textColor.greenF() + 0.3 * backgroundColor.greenF(),
169 0.7 * textColor.blueF() + 0.3 * backgroundColor.blueF());
170 painter->setPen(mix);
172 // QString directory = index.data(BookmarkManager::Directory).toString();
173 // int availableSpace = opt.rect.width() - 12;
174 // if (fm.width(directory) > availableSpace) {
175 // // We need a shorter directory
176 // availableSpace -= fm.width("...");
178 // int pos = directory.size();
181 // idx = directory.lastIndexOf("/", pos-1);
183 // // Can't happen, this means the string did fit after all?
186 // int width = fm.width(directory.mid(idx, pos-idx));
187 // if (width > availableSpace) {
188 // directory = "..." + directory.mid(pos);
192 // availableSpace -= width;
197 // painter->drawText(3, opt.rect.top() + fm.ascent() + fm.height() + 6, directory);
199 QString lineText = index.data(BookmarkManager::LineText).toString().trimmed();
200 painter->drawText(6, opt.rect.top() + fm.ascent() + fm.height() + 6, lineText);
203 painter->setPen(QColor::fromRgb(150,150,150));
204 painter->drawLine(0, opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());
208 BookmarkView::BookmarkView(QWidget *parent) :
210 m_bookmarkContext(new BookmarkContext(this)),
213 setWindowTitle(tr("Bookmarks"));
215 connect(this, SIGNAL(clicked(const QModelIndex &)),
216 this, SLOT(gotoBookmark(const QModelIndex &)));
218 ICore::instance()->addContextObject(m_bookmarkContext);
220 setItemDelegate(new BookmarkDelegate(this));
221 setFrameStyle(QFrame::NoFrame);
222 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
223 setFocusPolicy(Qt::NoFocus);
226 BookmarkView::~BookmarkView()
228 ICore::instance()->removeContextObject(m_bookmarkContext);
231 void BookmarkView::contextMenuEvent(QContextMenuEvent *event)
234 QAction *moveUp = menu.addAction(tr("Move Up"));
235 QAction *moveDown = menu.addAction(tr("Move Down"));
236 QAction *remove = menu.addAction(tr("&Remove"));
237 QAction *removeAll = menu.addAction(tr("Remove All"));
238 m_contextMenuIndex = indexAt(event->pos());
239 if (!m_contextMenuIndex.isValid()) {
240 moveUp->setEnabled(false);
241 moveDown->setEnabled(false);
242 remove->setEnabled(false);
245 if (model()->rowCount() == 0)
246 removeAll->setEnabled(false);
248 connect(moveUp, SIGNAL(triggered()),
249 m_manager, SLOT(moveUp()));
250 connect(moveDown, SIGNAL(triggered()),
251 m_manager, SLOT(moveDown()));
252 connect(remove, SIGNAL(triggered()),
253 this, SLOT(removeFromContextMenu()));
254 connect(removeAll, SIGNAL(triggered()),
255 this, SLOT(removeAll()));
257 menu.exec(mapToGlobal(event->pos()));
260 void BookmarkView::removeFromContextMenu()
262 removeBookmark(m_contextMenuIndex);
265 void BookmarkView::removeBookmark(const QModelIndex& index)
267 Bookmark *bm = m_manager->bookmarkForIndex(index);
268 m_manager->removeBookmark(bm);
271 // The perforcemance of this function could be greatly improved.
273 void BookmarkView::removeAll()
275 while (m_manager->rowCount()) {
276 QModelIndex index = m_manager->index(0, 0);
277 removeBookmark(index);
281 void BookmarkView::setModel(QAbstractItemModel *model)
283 BookmarkManager *manager = qobject_cast<BookmarkManager *>(model);
284 QTC_ASSERT(manager, return);
286 QListView::setModel(model);
287 setSelectionModel(manager->selectionModel());
288 setSelectionMode(QAbstractItemView::SingleSelection);
289 setSelectionBehavior(QAbstractItemView::SelectRows);
292 void BookmarkView::gotoBookmark(const QModelIndex &index)
294 Bookmark *bk = m_manager->bookmarkForIndex(index);
295 if (!m_manager->gotoBookmark(bk))
296 m_manager->removeBookmark(bk);
303 BookmarkContext::BookmarkContext(BookmarkView *widget)
304 : Core::IContext(widget),
305 m_bookmarkView(widget),
306 m_context(Constants::BOOKMARKS_CONTEXT)
310 Context BookmarkContext::context() const
315 QWidget *BookmarkContext::widget()
317 return m_bookmarkView;
324 BookmarkManager::BookmarkManager() :
325 m_bookmarkIcon(QLatin1String(":/bookmarks/images/bookmark.png")),
326 m_selectionModel(new QItemSelectionModel(this, this))
328 connect(Core::ICore::instance(), SIGNAL(contextChanged(Core::IContext*,Core::Context)),
329 this, SLOT(updateActionStatus()));
331 connect(ProjectExplorerPlugin::instance()->session(), SIGNAL(sessionLoaded()),
332 this, SLOT(loadBookmarks()));
334 updateActionStatus();
337 BookmarkManager::~BookmarkManager()
339 DirectoryFileBookmarksMap::iterator it, end;
340 end = m_bookmarksMap.end();
341 for (it = m_bookmarksMap.begin(); it != end; ++it) {
342 FileNameBookmarksMap *bookmarks = it.value();
343 qDeleteAll(*bookmarks);
348 QItemSelectionModel *BookmarkManager::selectionModel() const
350 return m_selectionModel;
353 QModelIndex BookmarkManager::index(int row, int column, const QModelIndex &parent) const
355 if (parent.isValid())
356 return QModelIndex();
358 return createIndex(row, column, 0);
361 QModelIndex BookmarkManager::parent(const QModelIndex &) const
363 return QModelIndex();
366 int BookmarkManager::rowCount(const QModelIndex &parent) const
368 if (parent.isValid())
371 return m_bookmarksList.count();
374 int BookmarkManager::columnCount(const QModelIndex &parent) const
376 if (parent.isValid())
381 QVariant BookmarkManager::data(const QModelIndex &index, int role) const
383 if (!index.isValid() || index.column() !=0 || index.row() < 0 || index.row() >= m_bookmarksList.count())
386 if (role == BookmarkManager::Filename)
387 return m_bookmarksList.at(index.row())->fileName();
388 else if (role == BookmarkManager::LineNumber)
389 return m_bookmarksList.at(index.row())->lineNumber();
390 else if (role == BookmarkManager::Directory)
391 return m_bookmarksList.at(index.row())->path();
392 else if (role == BookmarkManager::LineText)
393 return m_bookmarksList.at(index.row())->lineText();
394 else if (role == Qt::ToolTipRole)
395 return m_bookmarksList.at(index.row())->filePath();
400 void BookmarkManager::toggleBookmark()
402 TextEditor::ITextEditor *editor = currentTextEditor();
406 toggleBookmark(editor->file()->fileName(), editor->currentLine());
409 void BookmarkManager::toggleBookmark(const QString &fileName, int lineNumber)
411 const QFileInfo fi(fileName);
412 const int editorLine = lineNumber;
414 // Remove any existing bookmark on this line
415 if (Bookmark *mark = findBookmark(fi.path(), fi.fileName(), lineNumber)) {
416 // TODO check if the bookmark is really on the same markable Interface
417 removeBookmark(mark);
421 // Add a new bookmark if no bookmark existed on this line
422 Bookmark *bookmark = new Bookmark(fi.filePath(), editorLine, this);
423 addBookmark(bookmark);
426 void BookmarkManager::updateBookmark(Bookmark *bookmark)
428 int idx = m_bookmarksList.indexOf(bookmark);
429 emit dataChanged(index(idx, 0, QModelIndex()), index(idx, 2, QModelIndex()));
433 void BookmarkManager::removeAllBookmarks()
435 if (m_bookmarksList.isEmpty())
437 beginRemoveRows(QModelIndex(), 0, m_bookmarksList.size() - 1);
439 DirectoryFileBookmarksMap::const_iterator it, end;
440 end = m_bookmarksMap.constEnd();
441 for (it = m_bookmarksMap.constBegin(); it != end; ++it) {
442 FileNameBookmarksMap *files = it.value();
443 FileNameBookmarksMap::const_iterator jt, jend;
444 jend = files->constEnd();
445 for (jt = files->constBegin(); jt != jend; ++jt) {
451 m_bookmarksMap.clear();
452 m_bookmarksList.clear();
456 void BookmarkManager::removeBookmark(Bookmark *bookmark)
458 int idx = m_bookmarksList.indexOf(bookmark);
459 beginRemoveRows(QModelIndex(), idx, idx);
461 const QFileInfo fi(bookmark->filePath() );
462 FileNameBookmarksMap *files = m_bookmarksMap.value(fi.path());
464 FileNameBookmarksMap::iterator i = files->begin();
465 while (i != files->end()) {
466 if (i.value() == bookmark) {
473 if (files->count() <= 0) {
474 m_bookmarksMap.remove(fi.path());
479 m_bookmarksList.removeAt(idx);
482 if (selectionModel()->currentIndex().isValid())
483 selectionModel()->setCurrentIndex(selectionModel()->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Clear);
485 updateActionStatus();
489 Bookmark *BookmarkManager::bookmarkForIndex(QModelIndex index)
491 if (!index.isValid() || index.row() >= m_bookmarksList.size())
493 return m_bookmarksList.at(index.row());
497 bool BookmarkManager::gotoBookmark(Bookmark* bookmark)
499 using namespace TextEditor;
500 if (ITextEditor *editor = BaseTextEditorWidget::openEditorAt(bookmark->filePath(), bookmark->lineNumber()))
501 return (editor->currentLine() == bookmark->lineNumber());
505 void BookmarkManager::nextInDocument()
507 documentPrevNext(true);
510 void BookmarkManager::prevInDocument()
512 documentPrevNext(false);
515 void BookmarkManager::documentPrevNext(bool next)
517 TextEditor::ITextEditor *editor = currentTextEditor();
518 int editorLine = editor->currentLine();
519 QFileInfo fi(editor->file()->fileName());
520 if (!m_bookmarksMap.contains(fi.path()))
527 const QList<Bookmark*> marks = m_bookmarksMap.value(fi.path())->values(fi.fileName());
528 for (int i = 0; i < marks.count(); ++i) {
529 int markLine = marks.at(i)->lineNumber();
530 if (firstLine == -1 || firstLine > markLine)
531 firstLine = markLine;
532 if (lastLine < markLine)
534 if (markLine < editorLine && prevLine < markLine)
536 if (markLine > editorLine &&
537 (nextLine == -1 || nextLine > markLine))
541 Core::EditorManager *em = Core::EditorManager::instance();
542 em->addCurrentPositionToNavigationHistory();
545 editor->gotoLine(firstLine);
547 editor->gotoLine(nextLine);
550 editor->gotoLine(lastLine);
552 editor->gotoLine(prevLine);
556 void BookmarkManager::next()
558 QModelIndex current = selectionModel()->currentIndex();
559 if (!current.isValid())
561 int row = current.row();
564 if (row == m_bookmarksList.size())
567 Bookmark *bk = m_bookmarksList.at(row);
568 if (gotoBookmark(bk)) {
569 QModelIndex newIndex = current.sibling(row, current.column());
570 selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select | QItemSelectionModel::Clear);
574 if (m_bookmarksList.isEmpty()) // No bookmarks anymore ...
579 void BookmarkManager::prev()
581 QModelIndex current = selectionModel()->currentIndex();
582 if (!current.isValid())
585 int row = current.row();
588 row = m_bookmarksList.size();
590 Bookmark *bk = m_bookmarksList.at(row);
591 if (gotoBookmark(bk)) {
592 QModelIndex newIndex = current.sibling(row, current.column());
593 selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select | QItemSelectionModel::Clear);
597 if (m_bookmarksList.isEmpty())
602 TextEditor::ITextEditor *BookmarkManager::currentTextEditor() const
604 Core::EditorManager *em = Core::EditorManager::instance();
605 Core::IEditor *currEditor = em->currentEditor();
608 return qobject_cast<TextEditor::ITextEditor *>(currEditor);
611 /* Returns the current session. */
612 SessionManager *BookmarkManager::sessionManager() const
614 return ProjectExplorerPlugin::instance()->session();
617 BookmarkManager::State BookmarkManager::state() const
619 if (m_bookmarksMap.empty())
622 TextEditor::ITextEditor *editor = currentTextEditor();
626 const QFileInfo fi(editor->file()->fileName());
628 const DirectoryFileBookmarksMap::const_iterator dit = m_bookmarksMap.constFind(fi.path());
629 if (dit == m_bookmarksMap.constEnd())
632 return HasBookmarksInDocument;
635 void BookmarkManager::updateActionStatus()
637 emit updateActions(state());
640 void BookmarkManager::moveUp()
642 QModelIndex current = selectionModel()->currentIndex();
643 int row = current.row();
645 row = m_bookmarksList.size();
648 // swap current.row() and row
650 Bookmark *b = m_bookmarksList.at(row);
651 m_bookmarksList[row] = m_bookmarksList.at(current.row());
652 m_bookmarksList[current.row()] = b;
654 QModelIndex topLeft = current.sibling(row, 0);
655 QModelIndex bottomRight = current.sibling(current.row(), 2);
656 emit dataChanged(topLeft, bottomRight);
657 selectionModel()->setCurrentIndex(current.sibling(row, 0), QItemSelectionModel::Select | QItemSelectionModel::Clear);
660 void BookmarkManager::moveDown()
662 QModelIndex current = selectionModel()->currentIndex();
663 int row = current.row();
665 if (row == m_bookmarksList.size())
668 // swap current.row() and row
669 Bookmark *b = m_bookmarksList.at(row);
670 m_bookmarksList[row] = m_bookmarksList.at(current.row());
671 m_bookmarksList[current.row()] = b;
673 QModelIndex topLeft = current.sibling(current.row(), 0);
674 QModelIndex bottomRight = current.sibling(row, 2);
675 emit dataChanged(topLeft, bottomRight);
676 selectionModel()->setCurrentIndex(current.sibling(row, 0), QItemSelectionModel::Select | QItemSelectionModel::Clear);
679 /* Returns the bookmark at the given file and line number, or 0 if no such bookmark exists. */
680 Bookmark* BookmarkManager::findBookmark(const QString &path, const QString &fileName, int lineNumber)
682 if (m_bookmarksMap.contains(path)) {
683 foreach (Bookmark *bookmark, m_bookmarksMap.value(path)->values(fileName)) {
684 if (bookmark->lineNumber() == lineNumber)
691 /* Adds a bookmark to the internal data structures. The 'userset' parameter
692 * determines whether action status should be updated and whether the bookmarks
693 * should be saved to the session settings.
695 void BookmarkManager::addBookmark(Bookmark *bookmark, bool userset)
697 beginInsertRows(QModelIndex(), m_bookmarksList.size(), m_bookmarksList.size());
698 const QFileInfo fi(bookmark->filePath());
699 const QString &path = fi.path();
701 if (!m_bookmarksMap.contains(path))
702 m_bookmarksMap.insert(path, new FileNameBookmarksMap());
703 m_bookmarksMap.value(path)->insert(fi.fileName(), bookmark);
705 m_bookmarksList.append(bookmark);
709 updateActionStatus();
712 selectionModel()->setCurrentIndex(index(m_bookmarksList.size()-1 , 0, QModelIndex()), QItemSelectionModel::Select | QItemSelectionModel::Clear);
715 /* Adds a new bookmark based on information parsed from the string. */
716 void BookmarkManager::addBookmark(const QString &s)
718 int index2 = s.lastIndexOf(':');
719 int index1 = s.indexOf(':');
720 if (index2 != -1 || index1 != -1) {
721 const QString &filePath = s.mid(index1+1, index2-index1-1);
722 const int lineNumber = s.mid(index2 + 1).toInt();
723 const QFileInfo fi(filePath);
725 if (!filePath.isEmpty() && !findBookmark(fi.path(), fi.fileName(), lineNumber)) {
726 Bookmark *b = new Bookmark(filePath, lineNumber, this);
727 addBookmark(b, false);
730 qDebug() << "BookmarkManager::addBookmark() Invalid bookmark string:" << s;
734 /* Puts the bookmark in a string for storing it in the settings. */
735 QString BookmarkManager::bookmarkToString(const Bookmark *b)
737 const QLatin1Char colon(':');
738 // Empty string was the name of the bookmark, which now is always ""
739 return QLatin1String("") + colon + b->filePath() + colon + QString::number(b->lineNumber());
742 /* Saves the bookmarks to the session settings. */
743 void BookmarkManager::saveBookmarks()
745 SessionManager *s = sessionManager();
750 foreach (const FileNameBookmarksMap *bookmarksMap, m_bookmarksMap)
751 foreach (const Bookmark *bookmark, *bookmarksMap)
752 list << bookmarkToString(bookmark);
754 s->setValue("Bookmarks", list);
757 /* Loads the bookmarks from the session settings. */
758 void BookmarkManager::loadBookmarks()
760 removeAllBookmarks();
761 SessionManager *s = sessionManager();
765 const QStringList &list = s->value("Bookmarks").toStringList();
766 foreach (const QString &bookmarkString, list)
767 addBookmark(bookmarkString);
769 updateActionStatus();
772 // BookmarkViewFactory
774 BookmarkViewFactory::BookmarkViewFactory(BookmarkManager *bm)
780 QString BookmarkViewFactory::displayName() const
782 return BookmarkView::tr("Bookmarks");
785 int BookmarkViewFactory::priority() const
790 QString BookmarkViewFactory::id() const
792 return QLatin1String("Bookmarks");
795 QKeySequence BookmarkViewFactory::activationSequence() const
797 return QKeySequence(Qt::ALT + Qt::Key_M);
800 Core::NavigationView BookmarkViewFactory::createWidget()
802 BookmarkView *bookmarkView = new BookmarkView();
803 bookmarkView->setModel(m_manager);
804 Core::NavigationView view;
805 view.widget = bookmarkView;