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
45QT_BEGIN_NAMESPACE
46
47// -- BookmarkManager::BookmarkWidget
48
49void 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
63BookmarkManager::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(sender: this, signal: &QTreeView::expanded, receiver: this, slot: &BookmarkTreeView::setExpandedData);
75 connect(sender: this, signal: &QTreeView::collapsed, receiver: this, slot: &BookmarkTreeView::setExpandedData);
76}
77
78void BookmarkManager::BookmarkTreeView::subclassKeyPressEvent(QKeyEvent *event)
79{
80 TRACE_OBJ
81 QTreeView::keyPressEvent(event);
82}
83
84void BookmarkManager::BookmarkTreeView::commitData(QWidget *editor)
85{
86 QTreeView::commitData(editor);
87 emit editingDone();
88}
89
90void BookmarkManager::BookmarkTreeView::setExpandedData(const QModelIndex &index)
91{
92 TRACE_OBJ
93 if (BookmarkModel *treeModel = qobject_cast<BookmarkModel*> (object: model()))
94 treeModel->setData(index, value: isExpanded(index), role: UserRoleExpanded);
95}
96
97// -- BookmarkManager
98
99QMutex BookmarkManager::mutex;
100BookmarkManager* BookmarkManager::bookmarkManager = nullptr;
101
102// -- public
103
104BookmarkManager* 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
115void BookmarkManager::destroy()
116{
117 TRACE_OBJ
118 delete bookmarkManager;
119 bookmarkManager = nullptr;
120}
121
122QWidget* BookmarkManager::bookmarkDockWidget() const
123{
124 TRACE_OBJ
125 if (bookmarkWidget)
126 return bookmarkWidget;
127 return nullptr;
128}
129
130void BookmarkManager::setBookmarksMenu(QMenu* menu)
131{
132 TRACE_OBJ
133 bookmarkMenu = menu;
134 refreshBookmarkMenu();
135}
136
137void BookmarkManager::setBookmarksToolbar(QToolBar *toolBar)
138{
139 TRACE_OBJ
140 m_toolBar = toolBar;
141 refreshBookmarkToolBar();
142}
143
144// -- public slots
145
146void BookmarkManager::addBookmark(const QString &title, const QString &url)
147{
148 TRACE_OBJ
149 showBookmarkDialog(name: title.isEmpty() ? tr(s: "Untitled") : title,
150 url: url.isEmpty() ? QLatin1String("about:blank") : url);
151
152 storeBookmarks();
153}
154
155// -- private
156
157BookmarkManager::BookmarkManager()
158 : bookmarkModel(new BookmarkModel)
159 , bookmarkWidget(new BookmarkWidget)
160 , bookmarkTreeView(new BookmarkTreeView)
161{
162 TRACE_OBJ
163 bookmarkWidget->installEventFilter(filterObj: this);
164 connect(sender: bookmarkWidget->ui.add, signal: &QAbstractButton::clicked,
165 receiver: this, slot: &BookmarkManager::addBookmarkActivated);
166 connect(sender: bookmarkWidget->ui.remove, signal: &QAbstractButton::clicked,
167 receiver: this, slot: &BookmarkManager::removeBookmarkActivated);
168 connect(sender: bookmarkWidget->ui.lineEdit, signal: &QLineEdit::textChanged,
169 receiver: this, slot: &BookmarkManager::textChanged);
170 connect(sender: bookmarkWidget, signal: &BookmarkWidget::focusInEventOccurred,
171 receiver: this, slot: &BookmarkManager::focusInEventOccurred);
172
173 bookmarkTreeView->setModel(bookmarkModel);
174 bookmarkTreeView->installEventFilter(filterObj: this);
175 bookmarkTreeView->viewport()->installEventFilter(filterObj: this);
176 bookmarkTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
177 bookmarkWidget->ui.stackedWidget->addWidget(w: bookmarkTreeView);
178
179 connect(sender: bookmarkTreeView, signal: &QAbstractItemView::activated,
180 context: this, slot: [this](const QModelIndex &index) { setSourceFromIndex(index, newTab: false); });
181 connect(sender: bookmarkTreeView, signal: &QWidget::customContextMenuRequested,
182 receiver: this, slot: &BookmarkManager::customContextMenuRequested);
183 connect(sender: bookmarkTreeView, signal: &BookmarkTreeView::editingDone,
184 receiver: this, slot: &BookmarkManager::storeBookmarks);
185
186 connect(sender: &HelpEngineWrapper::instance(), signal: &HelpEngineWrapper::setupFinished,
187 receiver: this, slot: &BookmarkManager::setupFinished);
188
189 connect(sender: bookmarkModel, signal: &QAbstractItemModel::rowsRemoved,
190 receiver: this, slot: &BookmarkManager::refreshBookmarkMenu);
191 connect(sender: bookmarkModel, signal: &QAbstractItemModel::rowsInserted,
192 receiver: this, slot: &BookmarkManager::refreshBookmarkMenu);
193 connect(sender: bookmarkModel, signal: &QAbstractItemModel::dataChanged,
194 receiver: this, slot: &BookmarkManager::refreshBookmarkMenu);
195
196 connect(sender: bookmarkModel, signal: &QAbstractItemModel::rowsRemoved,
197 receiver: this, slot: &BookmarkManager::refreshBookmarkToolBar);
198 connect(sender: bookmarkModel, signal: &QAbstractItemModel::rowsInserted,
199 receiver: this, slot: &BookmarkManager::refreshBookmarkToolBar);
200 connect(sender: bookmarkModel, signal: &QAbstractItemModel::dataChanged,
201 receiver: this, slot: &BookmarkManager::refreshBookmarkToolBar);
202
203}
204
205BookmarkManager::~BookmarkManager()
206{
207 TRACE_OBJ
208 delete bookmarkManagerWidget;
209 storeBookmarks();
210 delete bookmarkModel;
211}
212
213void 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(proxyIndex: current);
219 current = bookmarkFilterModel->mapToSource(proxyIndex: 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(parent: current)) {
225 int value = QMessageBox::question(parent: bookmarkTreeView, title: tr(s: "Remove"),
226 text: tr(s: "You are going to delete a Folder, this will also<br>"
227 "remove it's content. Are you sure to continue?"),
228 buttons: QMessageBox::Yes | QMessageBox::Cancel, defaultButton: QMessageBox::Cancel);
229 if (value == QMessageBox::Cancel)
230 return;
231 }
232 bookmarkModel->removeItem(index: current);
233
234 storeBookmarks();
235}
236
237bool BookmarkManager::eventFilter(QObject *object, QEvent *event)
238{
239 if (object != bookmarkTreeView && object != bookmarkTreeView->viewport()
240 && object != bookmarkWidget)
241 return QObject::eventFilter(watched: 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(index: bookmarkTreeView->currentIndex());
250 break;
251
252 case Qt::Key_Delete:
253 removeItem(index: 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(event: 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(index: bookmarkTreeView->currentIndex(), newTab: true);
276 break;
277
278 case Qt::MiddleButton:
279 setSourceFromIndex(index: bookmarkTreeView->currentIndex(), newTab: true);
280 break;
281
282 default: break;
283 }
284 }
285
286 return QObject::eventFilter(watched: object, event);
287}
288
289void 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>(v: index.data(arole: Qt::DecorationRole));
297 if (index.data(arole: UserRoleFolder).toBool()) {
298 if (QMenu* subMenu = menu->addMenu(icon, title: text)) {
299 for (int i = 0; i < bookmarkModel->rowCount(index); ++i)
300 buildBookmarksMenu(index: bookmarkModel->index(row: i, column: 0, index), menu: subMenu);
301 }
302 } else {
303 QAction *action = menu->addAction(icon, text);
304 action->setData(index.data(arole: UserRoleUrl).toString());
305 }
306}
307
308void 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
317void BookmarkManager::setupFinished()
318{
319 TRACE_OBJ
320 bookmarkModel->setBookmarks(HelpEngineWrapper::instance().bookmarks());
321 bookmarkModel->expandFoldersIfNeeeded(treeView: bookmarkTreeView);
322
323 refreshBookmarkMenu();
324 refreshBookmarkToolBar();
325
326 bookmarkTreeView->hideColumn(column: 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
341void BookmarkManager::storeBookmarks()
342{
343 HelpEngineWrapper::instance().setBookmarks(bookmarkModel->bookmarks());
344}
345
346void BookmarkManager::addBookmarkActivated()
347{
348 TRACE_OBJ
349 if (CentralWidget *widget = CentralWidget::instance())
350 addBookmark(title: widget->currentTitle(), url: widget->currentSource().toString());
351}
352
353void BookmarkManager::removeBookmarkActivated()
354{
355 TRACE_OBJ
356 removeItem(index: bookmarkTreeView->currentIndex());
357}
358
359void BookmarkManager::manageBookmarks()
360{
361 TRACE_OBJ
362 if (bookmarkManagerWidget == nullptr) {
363 bookmarkManagerWidget = new BookmarkManagerWidget(bookmarkModel);
364 connect(sender: bookmarkManagerWidget, signal: &BookmarkManagerWidget::setSource,
365 receiver: this, slot: &BookmarkManager::setSource);
366 connect(sender: bookmarkManagerWidget, signal: &BookmarkManagerWidget::setSourceInNewTab,
367 receiver: this, slot: &BookmarkManager::setSourceInNewTab);
368 connect(sender: bookmarkManagerWidget, signal: &BookmarkManagerWidget::managerWidgetAboutToClose,
369 receiver: this, slot: &BookmarkManager::managerWidgetAboutToClose);
370 }
371 bookmarkManagerWidget->show();
372 bookmarkManagerWidget->raise();
373}
374
375void BookmarkManager::refreshBookmarkMenu()
376{
377 TRACE_OBJ
378 if (!bookmarkMenu)
379 return;
380
381 bookmarkMenu->clear();
382
383 bookmarkMenu->addAction(text: tr(s: "Manage Bookmarks..."), object: this,
384 slot: &BookmarkManager::manageBookmarks);
385 bookmarkMenu->addAction(actionIcon: QIcon::fromTheme(name: "bookmark-new"), text: tr(s: "Add Bookmark..."),
386 object: this, slot: &BookmarkManager::addBookmarkActivated,
387 shortcut: QKeySequence(tr(s: "Ctrl+D")));
388
389 bookmarkMenu->addSeparator();
390
391 QModelIndex root = bookmarkModel->index(row: 0, column: 0, index: QModelIndex()).parent();
392 buildBookmarksMenu(index: bookmarkModel->index(row: 0, column: 0, index: root), menu: bookmarkMenu);
393
394 bookmarkMenu->addSeparator();
395
396 root = bookmarkModel->index(row: 1, column: 0, index: QModelIndex());
397 for (int i = 0; i < bookmarkModel->rowCount(index: root); ++i)
398 buildBookmarksMenu(index: bookmarkModel->index(row: i, column: 0, index: root), menu: bookmarkMenu);
399
400 connect(sender: bookmarkMenu, signal: &QMenu::triggered,
401 receiver: this, slot: &BookmarkManager::setSourceFromAction);
402}
403
404void 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(row: 0, column: 0, index: QModelIndex());
414 for (int i = 0; i < bookmarkModel->rowCount(index: root); ++i) {
415 const QModelIndex &index = bookmarkModel->index(row: i, column: 0, index: root);
416 if (index.data(arole: 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(index: bookmarkModel->index(row: j, column: 0, index), menu);
423 connect(sender: menu, signal: &QMenu::triggered,
424 receiver: this, slot: &BookmarkManager::setSourceFromAction);
425 button->setMenu(menu);
426 button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
427 button->setIcon(qvariant_cast<QIcon>(v: index.data(arole: Qt::DecorationRole)));
428 QAction *a = m_toolBar->addWidget(widget: button);
429 a->setText(index.data().toString());
430 } else {
431 QAction *action = m_toolBar->addAction(
432 actionIcon: qvariant_cast<QIcon>(v: index.data(arole: Qt::DecorationRole)),
433 text: index.data().toString(), object: this, slot: &BookmarkManager::setSourceFromAction);
434 action->setData(index.data(arole: UserRoleUrl).toString());
435 }
436 }
437}
438
439void 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
451void BookmarkManager::setSourceFromAction()
452{
453 TRACE_OBJ
454 const QAction *action = qobject_cast<QAction*>(object: 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
463void 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, role: UserRoleFolder).toBool())
471 return;
472
473 const QVariant &data = base->data(index, role: UserRoleUrl);
474 if (data.canConvert<QUrl>()) {
475 if (newTab)
476 emit setSourceInNewTab(data.toUrl());
477 else
478 emit setSource(data.toUrl());
479 }
480}
481
482void BookmarkManager::customContextMenuRequested(const QPoint &point)
483{
484 TRACE_OBJ
485 QModelIndex index = bookmarkTreeView->indexAt(p: 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, role: UserRoleFolder).toBool()) {
500 remove = menu.addAction(text: tr(s: "Delete Folder"));
501 rename = menu.addAction(text: tr(s: "Rename Folder"));
502 } else {
503 showItem = menu.addAction(text: tr(s: "Show Bookmark"));
504 showItemInNewTab = menu.addAction(text: tr(s: "Show Bookmark in New Tab"));
505 menu.addSeparator();
506 remove = menu.addAction(text: tr(s: "Delete Bookmark"));
507 rename = menu.addAction(text: tr(s: "Rename Bookmark"));
508 }
509
510 QAction *pickedAction = menu.exec(pos: 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, newTab: pickedAction == showItemInNewTab);
517}
518
519void BookmarkManager::focusInEventOccurred()
520{
521 TRACE_OBJ
522 const QModelIndex &index = bookmarkTreeView->indexAt(p: QPoint(2, 2));
523 if (index.isValid())
524 bookmarkTreeView->setCurrentIndex(index);
525}
526
527void BookmarkManager::managerWidgetAboutToClose()
528{
529 if (bookmarkManagerWidget)
530 bookmarkManagerWidget->deleteLater();
531 bookmarkManagerWidget = nullptr;
532
533 storeBookmarks();
534}
535
536void 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(treeView: bookmarkTreeView);
553 }
554}
555
556QT_END_NAMESPACE
557

source code of qttools/src/assistant/assistant/bookmarkmanager.cpp