1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Assistant of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 #include "tracer.h"
29
30 #include "bookmarkmanager.h"
31 #include "bookmarkmanagerwidget.h"
32 #include "bookmarkdialog.h"
33 #include "bookmarkfiltermodel.h"
34 #include "bookmarkitem.h"
35 #include "bookmarkmodel.h"
36 #include "centralwidget.h"
37 #include "helpenginewrapper.h"
38
39 #include <QtWidgets/QMenu>
40 #include <QtGui/QKeyEvent>
41 #include <QtWidgets/QMessageBox>
42 #include <QtCore/QSortFilterProxyModel>
43 #include <QtWidgets/QToolBar>
44
45 QT_BEGIN_NAMESPACE
46
47 // -- BookmarkManager::BookmarkWidget
48
focusInEvent(QFocusEvent * event)49 void BookmarkManager::BookmarkWidget::focusInEvent(QFocusEvent *event)
50 {
51 TRACE_OBJ
52 if (event->reason() != Qt::MouseFocusReason) {
53 ui.lineEdit->selectAll();
54 ui.lineEdit->setFocus();
55
56 // force the focus in event on bookmark manager
57 emit focusInEventOccurred();
58 }
59 }
60
61 // -- BookmarkManager::BookmarkTreeView
62
BookmarkTreeView(QWidget * parent)63 BookmarkManager::BookmarkTreeView::BookmarkTreeView(QWidget *parent)
64 : QTreeView(parent)
65 {
66 TRACE_OBJ
67 setAcceptDrops(true);
68 setDragEnabled(true);
69 setAutoExpandDelay(1000);
70 setUniformRowHeights(true);
71 setDropIndicatorShown(true);
72 setExpandsOnDoubleClick(true);
73
74 connect(this, &QTreeView::expanded, this, &BookmarkTreeView::setExpandedData);
75 connect(this, &QTreeView::collapsed, this, &BookmarkTreeView::setExpandedData);
76 }
77
subclassKeyPressEvent(QKeyEvent * event)78 void BookmarkManager::BookmarkTreeView::subclassKeyPressEvent(QKeyEvent *event)
79 {
80 TRACE_OBJ
81 QTreeView::keyPressEvent(event);
82 }
83
commitData(QWidget * editor)84 void BookmarkManager::BookmarkTreeView::commitData(QWidget *editor)
85 {
86 QTreeView::commitData(editor);
87 emit editingDone();
88 }
89
setExpandedData(const QModelIndex & index)90 void BookmarkManager::BookmarkTreeView::setExpandedData(const QModelIndex &index)
91 {
92 TRACE_OBJ
93 if (BookmarkModel *treeModel = qobject_cast<BookmarkModel*> (model()))
94 treeModel->setData(index, isExpanded(index), UserRoleExpanded);
95 }
96
97 // -- BookmarkManager
98
99 QMutex BookmarkManager::mutex;
100 BookmarkManager* BookmarkManager::bookmarkManager = nullptr;
101
102 // -- public
103
instance()104 BookmarkManager* BookmarkManager::instance()
105 {
106 TRACE_OBJ
107 if (!bookmarkManager) {
108 QMutexLocker _(&mutex);
109 if (!bookmarkManager)
110 bookmarkManager = new BookmarkManager();
111 }
112 return bookmarkManager;
113 }
114
destroy()115 void BookmarkManager::destroy()
116 {
117 TRACE_OBJ
118 delete bookmarkManager;
119 bookmarkManager = nullptr;
120 }
121
bookmarkDockWidget() const122 QWidget* BookmarkManager::bookmarkDockWidget() const
123 {
124 TRACE_OBJ
125 if (bookmarkWidget)
126 return bookmarkWidget;
127 return nullptr;
128 }
129
setBookmarksMenu(QMenu * menu)130 void BookmarkManager::setBookmarksMenu(QMenu* menu)
131 {
132 TRACE_OBJ
133 bookmarkMenu = menu;
134 refreshBookmarkMenu();
135 }
136
setBookmarksToolbar(QToolBar * toolBar)137 void BookmarkManager::setBookmarksToolbar(QToolBar *toolBar)
138 {
139 TRACE_OBJ
140 m_toolBar = toolBar;
141 refreshBookmarkToolBar();
142 }
143
144 // -- public slots
145
addBookmark(const QString & title,const QString & url)146 void BookmarkManager::addBookmark(const QString &title, const QString &url)
147 {
148 TRACE_OBJ
149 showBookmarkDialog(title.isEmpty() ? tr("Untitled") : title,
150 url.isEmpty() ? QLatin1String("about:blank") : url);
151
152 storeBookmarks();
153 }
154
155 // -- private
156
BookmarkManager()157 BookmarkManager::BookmarkManager()
158 : bookmarkModel(new BookmarkModel)
159 , bookmarkWidget(new BookmarkWidget)
160 , bookmarkTreeView(new BookmarkTreeView)
161 {
162 TRACE_OBJ
163 bookmarkWidget->installEventFilter(this);
164 connect(bookmarkWidget->ui.add, &QAbstractButton::clicked,
165 this, &BookmarkManager::addBookmarkActivated);
166 connect(bookmarkWidget->ui.remove, &QAbstractButton::clicked,
167 this, &BookmarkManager::removeBookmarkActivated);
168 connect(bookmarkWidget->ui.lineEdit, &QLineEdit::textChanged,
169 this, &BookmarkManager::textChanged);
170 connect(bookmarkWidget, &BookmarkWidget::focusInEventOccurred,
171 this, &BookmarkManager::focusInEventOccurred);
172
173 bookmarkTreeView->setModel(bookmarkModel);
174 bookmarkTreeView->installEventFilter(this);
175 bookmarkTreeView->viewport()->installEventFilter(this);
176 bookmarkTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
177 bookmarkWidget->ui.stackedWidget->addWidget(bookmarkTreeView);
178
179 connect(bookmarkTreeView, &QAbstractItemView::activated,
180 this, [this](const QModelIndex &index) { setSourceFromIndex(index, false); });
181 connect(bookmarkTreeView, &QWidget::customContextMenuRequested,
182 this, &BookmarkManager::customContextMenuRequested);
183 connect(bookmarkTreeView, &BookmarkTreeView::editingDone,
184 this, &BookmarkManager::storeBookmarks);
185
186 connect(&HelpEngineWrapper::instance(), &HelpEngineWrapper::setupFinished,
187 this, &BookmarkManager::setupFinished);
188
189 connect(bookmarkModel, &QAbstractItemModel::rowsRemoved,
190 this, &BookmarkManager::refreshBookmarkMenu);
191 connect(bookmarkModel, &QAbstractItemModel::rowsInserted,
192 this, &BookmarkManager::refreshBookmarkMenu);
193 connect(bookmarkModel, &QAbstractItemModel::dataChanged,
194 this, &BookmarkManager::refreshBookmarkMenu);
195
196 connect(bookmarkModel, &QAbstractItemModel::rowsRemoved,
197 this, &BookmarkManager::refreshBookmarkToolBar);
198 connect(bookmarkModel, &QAbstractItemModel::rowsInserted,
199 this, &BookmarkManager::refreshBookmarkToolBar);
200 connect(bookmarkModel, &QAbstractItemModel::dataChanged,
201 this, &BookmarkManager::refreshBookmarkToolBar);
202
203 }
204
~BookmarkManager()205 BookmarkManager::~BookmarkManager()
206 {
207 TRACE_OBJ
208 delete bookmarkManagerWidget;
209 storeBookmarks();
210 delete bookmarkModel;
211 }
212
removeItem(const QModelIndex & index)213 void BookmarkManager::removeItem(const QModelIndex &index)
214 {
215 TRACE_OBJ
216 QModelIndex current = index;
217 if (typeAndSearch) { // need to map because of proxy
218 current = typeAndSearchModel->mapToSource(current);
219 current = bookmarkFilterModel->mapToSource(current);
220 } else if (!bookmarkModel->parent(index).isValid()) {
221 return; // check if we should delete the "Bookmarks Menu", bail
222 }
223
224 if (bookmarkModel->hasChildren(current)) {
225 int value = QMessageBox::question(bookmarkTreeView, tr("Remove"),
226 tr("You are going to delete a Folder, this will also<br>"
227 "remove it's content. Are you sure to continue?"),
228 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
229 if (value == QMessageBox::Cancel)
230 return;
231 }
232 bookmarkModel->removeItem(current);
233
234 storeBookmarks();
235 }
236
eventFilter(QObject * object,QEvent * event)237 bool BookmarkManager::eventFilter(QObject *object, QEvent *event)
238 {
239 if (object != bookmarkTreeView && object != bookmarkTreeView->viewport()
240 && object != bookmarkWidget)
241 return QObject::eventFilter(object, event);
242
243 TRACE_OBJ
244 const bool isWidget = object == bookmarkWidget;
245 if (event->type() == QEvent::KeyPress) {
246 QKeyEvent *ke = static_cast<QKeyEvent*>(event);
247 switch (ke->key()) {
248 case Qt::Key_F2:
249 renameBookmark(bookmarkTreeView->currentIndex());
250 break;
251
252 case Qt::Key_Delete:
253 removeItem(bookmarkTreeView->currentIndex());
254 return true;
255
256 case Qt::Key_Up: // needs event filter on widget
257 case Qt::Key_Down:
258 if (isWidget)
259 bookmarkTreeView->subclassKeyPressEvent(ke);
260 break;
261
262 case Qt::Key_Escape:
263 emit escapePressed();
264 break;
265
266 default: break;
267 }
268 }
269
270 if (event->type() == QEvent::MouseButtonRelease && !isWidget) {
271 QMouseEvent *me = static_cast<QMouseEvent*>(event);
272 switch (me->button()) {
273 case Qt::LeftButton:
274 if (me->modifiers() & Qt::ControlModifier)
275 setSourceFromIndex(bookmarkTreeView->currentIndex(), true);
276 break;
277
278 case Qt::MiddleButton:
279 setSourceFromIndex(bookmarkTreeView->currentIndex(), true);
280 break;
281
282 default: break;
283 }
284 }
285
286 return QObject::eventFilter(object, event);
287 }
288
buildBookmarksMenu(const QModelIndex & index,QMenu * menu)289 void BookmarkManager::buildBookmarksMenu(const QModelIndex &index, QMenu* menu)
290 {
291 TRACE_OBJ
292 if (!index.isValid())
293 return;
294
295 const QString &text = index.data().toString();
296 const QIcon &icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
297 if (index.data(UserRoleFolder).toBool()) {
298 if (QMenu* subMenu = menu->addMenu(icon, text)) {
299 for (int i = 0; i < bookmarkModel->rowCount(index); ++i)
300 buildBookmarksMenu(bookmarkModel->index(i, 0, index), subMenu);
301 }
302 } else {
303 QAction *action = menu->addAction(icon, text);
304 action->setData(index.data(UserRoleUrl).toString());
305 }
306 }
307
showBookmarkDialog(const QString & name,const QString & url)308 void BookmarkManager::showBookmarkDialog(const QString &name, const QString &url)
309 {
310 TRACE_OBJ
311 BookmarkDialog dialog(bookmarkModel, name, url, bookmarkTreeView);
312 dialog.exec();
313 }
314
315 // -- private slots
316
setupFinished()317 void BookmarkManager::setupFinished()
318 {
319 TRACE_OBJ
320 bookmarkModel->setBookmarks(HelpEngineWrapper::instance().bookmarks());
321 bookmarkModel->expandFoldersIfNeeeded(bookmarkTreeView);
322
323 refreshBookmarkMenu();
324 refreshBookmarkToolBar();
325
326 bookmarkTreeView->hideColumn(1);
327 bookmarkTreeView->header()->setVisible(false);
328 bookmarkTreeView->header()->setStretchLastSection(true);
329
330 if (!bookmarkFilterModel)
331 bookmarkFilterModel = new BookmarkFilterModel(this);
332 bookmarkFilterModel->setSourceModel(bookmarkModel);
333 bookmarkFilterModel->filterBookmarkFolders();
334
335 if (!typeAndSearchModel)
336 typeAndSearchModel = new QSortFilterProxyModel(this);
337 typeAndSearchModel->setDynamicSortFilter(true);
338 typeAndSearchModel->setSourceModel(bookmarkFilterModel);
339 }
340
storeBookmarks()341 void BookmarkManager::storeBookmarks()
342 {
343 HelpEngineWrapper::instance().setBookmarks(bookmarkModel->bookmarks());
344 }
345
addBookmarkActivated()346 void BookmarkManager::addBookmarkActivated()
347 {
348 TRACE_OBJ
349 if (CentralWidget *widget = CentralWidget::instance())
350 addBookmark(widget->currentTitle(), widget->currentSource().toString());
351 }
352
removeBookmarkActivated()353 void BookmarkManager::removeBookmarkActivated()
354 {
355 TRACE_OBJ
356 removeItem(bookmarkTreeView->currentIndex());
357 }
358
manageBookmarks()359 void BookmarkManager::manageBookmarks()
360 {
361 TRACE_OBJ
362 if (bookmarkManagerWidget == nullptr) {
363 bookmarkManagerWidget = new BookmarkManagerWidget(bookmarkModel);
364 connect(bookmarkManagerWidget, &BookmarkManagerWidget::setSource,
365 this, &BookmarkManager::setSource);
366 connect(bookmarkManagerWidget, &BookmarkManagerWidget::setSourceInNewTab,
367 this, &BookmarkManager::setSourceInNewTab);
368 connect(bookmarkManagerWidget, &BookmarkManagerWidget::managerWidgetAboutToClose,
369 this, &BookmarkManager::managerWidgetAboutToClose);
370 }
371 bookmarkManagerWidget->show();
372 bookmarkManagerWidget->raise();
373 }
374
refreshBookmarkMenu()375 void BookmarkManager::refreshBookmarkMenu()
376 {
377 TRACE_OBJ
378 if (!bookmarkMenu)
379 return;
380
381 bookmarkMenu->clear();
382
383 bookmarkMenu->addAction(tr("Manage Bookmarks..."), this,
384 &BookmarkManager::manageBookmarks);
385 bookmarkMenu->addAction(QIcon::fromTheme("bookmark-new"), tr("Add Bookmark..."),
386 this, &BookmarkManager::addBookmarkActivated,
387 QKeySequence(tr("Ctrl+D")));
388
389 bookmarkMenu->addSeparator();
390
391 QModelIndex root = bookmarkModel->index(0, 0, QModelIndex()).parent();
392 buildBookmarksMenu(bookmarkModel->index(0, 0, root), bookmarkMenu);
393
394 bookmarkMenu->addSeparator();
395
396 root = bookmarkModel->index(1, 0, QModelIndex());
397 for (int i = 0; i < bookmarkModel->rowCount(root); ++i)
398 buildBookmarksMenu(bookmarkModel->index(i, 0, root), bookmarkMenu);
399
400 connect(bookmarkMenu, &QMenu::triggered,
401 this, &BookmarkManager::setSourceFromAction);
402 }
403
refreshBookmarkToolBar()404 void BookmarkManager::refreshBookmarkToolBar()
405 {
406 TRACE_OBJ
407 if (!m_toolBar)
408 return;
409
410 m_toolBar->clear();
411 m_toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
412
413 const QModelIndex &root = bookmarkModel->index(0, 0, QModelIndex());
414 for (int i = 0; i < bookmarkModel->rowCount(root); ++i) {
415 const QModelIndex &index = bookmarkModel->index(i, 0, root);
416 if (index.data(UserRoleFolder).toBool()) {
417 QToolButton *button = new QToolButton(m_toolBar);
418 button->setPopupMode(QToolButton::InstantPopup);
419 button->setText(index.data().toString());
420 QMenu *menu = new QMenu(button);
421 for (int j = 0; j < bookmarkModel->rowCount(index); ++j)
422 buildBookmarksMenu(bookmarkModel->index(j, 0, index), menu);
423 connect(menu, &QMenu::triggered,
424 this, &BookmarkManager::setSourceFromAction);
425 button->setMenu(menu);
426 button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
427 button->setIcon(qvariant_cast<QIcon>(index.data(Qt::DecorationRole)));
428 QAction *a = m_toolBar->addWidget(button);
429 a->setText(index.data().toString());
430 } else {
431 QAction *action = m_toolBar->addAction(
432 qvariant_cast<QIcon>(index.data(Qt::DecorationRole)),
433 index.data().toString(), this, &BookmarkManager::setSourceFromAction);
434 action->setData(index.data(UserRoleUrl).toString());
435 }
436 }
437 }
438
renameBookmark(const QModelIndex & index)439 void BookmarkManager::renameBookmark(const QModelIndex &index)
440 {
441 // check if we should rename the "Bookmarks Menu", bail
442 if (!typeAndSearch && !bookmarkModel->parent(index).isValid())
443 return;
444
445 bookmarkModel->setItemsEditable(true);
446 bookmarkTreeView->edit(index);
447 bookmarkModel->setItemsEditable(false);
448 }
449
450
setSourceFromAction()451 void BookmarkManager::setSourceFromAction()
452 {
453 TRACE_OBJ
454 const QAction *action = qobject_cast<QAction*>(sender());
455 if (!action)
456 return;
457
458 const QVariant &data = action->data();
459 if (data.canConvert<QUrl>())
460 emit setSource(data.toUrl());
461 }
462
setSourceFromIndex(const QModelIndex & index,bool newTab)463 void BookmarkManager::setSourceFromIndex(const QModelIndex &index, bool newTab)
464 {
465 TRACE_OBJ
466 QAbstractItemModel *base = bookmarkModel;
467 if (typeAndSearch)
468 base = typeAndSearchModel;
469
470 if (base->data(index, UserRoleFolder).toBool())
471 return;
472
473 const QVariant &data = base->data(index, UserRoleUrl);
474 if (data.canConvert<QUrl>()) {
475 if (newTab)
476 emit setSourceInNewTab(data.toUrl());
477 else
478 emit setSource(data.toUrl());
479 }
480 }
481
customContextMenuRequested(const QPoint & point)482 void BookmarkManager::customContextMenuRequested(const QPoint &point)
483 {
484 TRACE_OBJ
485 QModelIndex index = bookmarkTreeView->indexAt(point);
486 if (!index.isValid())
487 return;
488
489 // check if we should open the menu on "Bookmarks Menu", bail
490 if (!typeAndSearch && !bookmarkModel->parent(index).isValid())
491 return;
492
493 QAction *remove = nullptr;
494 QAction *rename = nullptr;
495 QAction *showItem = nullptr;
496 QAction *showItemInNewTab = nullptr;
497
498 QMenu menu;
499 if (!typeAndSearch && bookmarkModel->data(index, UserRoleFolder).toBool()) {
500 remove = menu.addAction(tr("Delete Folder"));
501 rename = menu.addAction(tr("Rename Folder"));
502 } else {
503 showItem = menu.addAction(tr("Show Bookmark"));
504 showItemInNewTab = menu.addAction(tr("Show Bookmark in New Tab"));
505 menu.addSeparator();
506 remove = menu.addAction(tr("Delete Bookmark"));
507 rename = menu.addAction(tr("Rename Bookmark"));
508 }
509
510 QAction *pickedAction = menu.exec(bookmarkTreeView->mapToGlobal(point));
511 if (pickedAction == rename)
512 renameBookmark(index);
513 else if (pickedAction == remove)
514 removeItem(index);
515 else if (pickedAction == showItem || pickedAction == showItemInNewTab)
516 setSourceFromIndex(index, pickedAction == showItemInNewTab);
517 }
518
focusInEventOccurred()519 void BookmarkManager::focusInEventOccurred()
520 {
521 TRACE_OBJ
522 const QModelIndex &index = bookmarkTreeView->indexAt(QPoint(2, 2));
523 if (index.isValid())
524 bookmarkTreeView->setCurrentIndex(index);
525 }
526
managerWidgetAboutToClose()527 void BookmarkManager::managerWidgetAboutToClose()
528 {
529 if (bookmarkManagerWidget)
530 bookmarkManagerWidget->deleteLater();
531 bookmarkManagerWidget = nullptr;
532
533 storeBookmarks();
534 }
535
textChanged(const QString & text)536 void BookmarkManager::textChanged(const QString &text)
537 {
538 TRACE_OBJ
539 if (!bookmarkWidget->ui.lineEdit->text().isEmpty()) {
540 if (!typeAndSearch) {
541 typeAndSearch = true;
542 bookmarkTreeView->setItemsExpandable(false);
543 bookmarkTreeView->setRootIsDecorated(false);
544 bookmarkTreeView->setModel(typeAndSearchModel);
545 }
546 typeAndSearchModel->setFilterRegExp(QRegExp(text));
547 } else {
548 typeAndSearch = false;
549 bookmarkTreeView->setModel(bookmarkModel);
550 bookmarkTreeView->setItemsExpandable(true);
551 bookmarkTreeView->setRootIsDecorated(true);
552 bookmarkModel->expandFoldersIfNeeeded(bookmarkTreeView);
553 }
554 }
555
556 QT_END_NAMESPACE
557