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