1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
4 SPDX-FileCopyrightText: 2006 Daniel Teske <teske@squorn.de>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "kbookmarkmenu.h"
10#include "kbookmarkmenu_p.h"
11
12#include "../kbookmarksettings_p.h"
13#include "kbookmarkaction.h"
14#include "kbookmarkaction_p.h"
15#include "kbookmarkactionmenu.h"
16#include "kbookmarkcontextmenu.h"
17#include "kbookmarkdialog.h"
18#include "kbookmarkmanager.h"
19#include "kbookmarkowner.h"
20#include "kbookmarkswidgets_debug.h"
21#include "keditbookmarks_p.h"
22
23#include <KAuthorized>
24#include <KStandardActions>
25
26#include <QApplication>
27#include <QMenu>
28#include <QMessageBox>
29#include <QMouseEvent>
30#include <QPointer>
31#include <QStandardPaths>
32
33class KBookmarkMenuEventFilter : public QObject
34{
35public:
36 KBookmarkMenuEventFilter(KBookmarkMenu *bMenu, QMenu *const qMenu)
37 : bookmarkMenu(bMenu)
38 , parentMenu(qMenu)
39 {
40 parentMenu->installEventFilter(filterObj: this);
41 }
42
43 ~KBookmarkMenuEventFilter()
44 {
45 if (parentMenu) {
46 parentMenu->removeEventFilter(obj: this);
47 }
48 }
49
50 bool eventFilter(QObject *watched, QEvent *event) override
51 {
52 Q_ASSERT(watched == parentMenu);
53
54 if (inFilter) {
55 return false;
56 }
57
58 if (event->type() != QEvent::MouseButtonRelease) {
59 return false;
60 }
61
62 const auto *me = static_cast<QMouseEvent *>(event);
63
64 QAction *action = parentMenu->actionAt(me->position().toPoint());
65 KBookmarkAction *bAction = dynamic_cast<KBookmarkAction *>(action);
66 if (!bAction) {
67 return false;
68 }
69
70 KBookmarkActionPrivate *bActionPrivate = KBookmarkActionPrivate::get(a: bAction);
71 bActionPrivate->buttons = me->button() | me->buttons();
72 inFilter = true;
73 QCoreApplication::sendEvent(receiver: parentMenu, event);
74 inFilter = false;
75 bActionPrivate->buttons = Qt::NoButton;
76
77 return true;
78 }
79
80 bool inFilter = false;
81 KBookmarkMenu *const bookmarkMenu;
82 QPointer<QMenu> parentMenu;
83};
84
85class KBookmarkMenuPrivate
86{
87public:
88 KBookmarkMenuPrivate(KBookmarkMenu *bookmarkMenu, QMenu *const menu)
89 : parentMenu(menu)
90 , eventFilter(bookmarkMenu, menu)
91 {
92 }
93
94 QAction *newBookmarkFolderAction = nullptr;
95 QAction *addBookmarkAction = nullptr;
96 QAction *bookmarksToFolderAction = nullptr;
97 QAction *editBookmarksAction = nullptr;
98 bool browserMode = false;
99 bool isRoot;
100 bool dirty;
101 KBookmarkManager *manager;
102 KBookmarkOwner *owner;
103 QMenu *const parentMenu;
104 QString parentAddress;
105 KBookmarkMenuEventFilter eventFilter;
106};
107
108KBookmarkMenu::KBookmarkMenu(KBookmarkManager *manager, KBookmarkOwner *_owner, QMenu *_parentMenu)
109 : QObject()
110 , d(new KBookmarkMenuPrivate(this, _parentMenu))
111{
112 d->isRoot = true;
113 d->manager = manager;
114 d->owner = _owner;
115 d->parentAddress = QString(); // TODO KBookmarkAdress::root
116 // TODO KDE5 find a QMenu equvalnet for this one
117 // m_parentMenu->setKeyboardShortcutsEnabled( true );
118
119 init();
120}
121
122void KBookmarkMenu::init()
123{
124 connect(sender: d->parentMenu, signal: &QMenu::aboutToShow, context: this, slot: &KBookmarkMenu::slotAboutToShow);
125
126 if (KBookmarkSettings::self()->m_contextmenu) {
127 d->parentMenu->setContextMenuPolicy(Qt::CustomContextMenu);
128 connect(sender: d->parentMenu, signal: &QWidget::customContextMenuRequested, context: this, slot: &KBookmarkMenu::slotCustomContextMenu);
129 }
130
131 connect(sender: d->manager, signal: &KBookmarkManager::changed, context: this, slot: &KBookmarkMenu::slotBookmarksChanged);
132
133 d->dirty = true;
134 addActions();
135}
136
137void KBookmarkMenu::addActions()
138{
139 if (d->isRoot) {
140 addAddBookmark();
141 addAddBookmarksList();
142 addNewFolder();
143 addEditBookmarks();
144 } else {
145 if (!d->parentMenu->actions().isEmpty()) {
146 d->parentMenu->addSeparator();
147 }
148
149 addOpenInTabs();
150 addAddBookmark();
151 addAddBookmarksList();
152 addNewFolder();
153 }
154}
155
156KBookmarkMenu::KBookmarkMenu(KBookmarkManager *mgr, KBookmarkOwner *_owner, QMenu *_parentMenu, const QString &parentAddress)
157 : QObject()
158 , d(new KBookmarkMenuPrivate(this, _parentMenu))
159{
160 d->isRoot = false;
161 d->manager = mgr;
162 d->owner = _owner;
163 d->parentAddress = parentAddress;
164
165 connect(sender: _parentMenu, signal: &QMenu::aboutToShow, context: this, slot: &KBookmarkMenu::slotAboutToShow);
166 if (KBookmarkSettings::self()->m_contextmenu) {
167 d->parentMenu->setContextMenuPolicy(Qt::CustomContextMenu);
168 connect(sender: d->parentMenu, signal: &QWidget::customContextMenuRequested, context: this, slot: &KBookmarkMenu::slotCustomContextMenu);
169 }
170 d->dirty = true;
171}
172
173KBookmarkMenu::~KBookmarkMenu()
174{
175 qDeleteAll(c: m_lstSubMenus);
176 qDeleteAll(c: m_actions);
177}
178
179void KBookmarkMenu::ensureUpToDate()
180{
181 slotAboutToShow();
182}
183
184void KBookmarkMenu::slotAboutToShow()
185{
186 // Did the bookmarks change since the last time we showed them ?
187 if (d->dirty) {
188 d->dirty = false;
189 clear();
190 refill();
191 d->parentMenu->adjustSize();
192 }
193}
194
195void KBookmarkMenu::slotCustomContextMenu(const QPoint &pos)
196{
197 QAction *action = d->parentMenu->actionAt(pos);
198 QMenu *menu = contextMenu(action);
199 if (!menu) {
200 return;
201 }
202 menu->setAttribute(Qt::WA_DeleteOnClose);
203 menu->popup(pos: d->parentMenu->mapToGlobal(pos));
204}
205
206QMenu *KBookmarkMenu::contextMenu(QAction *action)
207{
208 KBookmarkActionInterface *act = dynamic_cast<KBookmarkActionInterface *>(action);
209 if (!act) {
210 return nullptr;
211 }
212 return new KBookmarkContextMenu(act->bookmark(), d->manager, d->owner);
213}
214
215bool KBookmarkMenu::isRoot() const
216{
217 return d->isRoot;
218}
219
220bool KBookmarkMenu::isDirty() const
221{
222 return d->dirty;
223}
224
225QString KBookmarkMenu::parentAddress() const
226{
227 return d->parentAddress;
228}
229
230KBookmarkManager *KBookmarkMenu::manager() const
231{
232 return d->manager;
233}
234
235KBookmarkOwner *KBookmarkMenu::owner() const
236{
237 return d->owner;
238}
239
240QMenu *KBookmarkMenu::parentMenu() const
241{
242 return d->parentMenu;
243}
244
245void KBookmarkMenu::slotBookmarksChanged(const QString &groupAddress)
246{
247 qCDebug(KBOOKMARKSWIDGETS_LOG) << "KBookmarkMenu::slotBookmarksChanged groupAddress: " << groupAddress;
248 if (groupAddress == d->parentAddress) {
249 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkMenu::slotBookmarksChanged -> setting m_bDirty on " << groupAddress;
250 d->dirty = true;
251 } else {
252 // Iterate recursively into child menus
253 for (QList<KBookmarkMenu *>::iterator it = m_lstSubMenus.begin(), end = m_lstSubMenus.end(); it != end; ++it) {
254 (*it)->slotBookmarksChanged(groupAddress);
255 }
256 }
257}
258
259void KBookmarkMenu::clear()
260{
261 qDeleteAll(c: m_lstSubMenus);
262 m_lstSubMenus.clear();
263
264 for (QList<QAction *>::iterator it = m_actions.begin(), end = m_actions.end(); it != end; ++it) {
265 d->parentMenu->removeAction(action: *it);
266 delete *it;
267 }
268
269 d->parentMenu->clear();
270 m_actions.clear();
271}
272
273void KBookmarkMenu::refill()
274{
275 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkMenu::refill()";
276 if (d->isRoot) {
277 addActions();
278 }
279 fillBookmarks();
280 if (!d->isRoot) {
281 addActions();
282 }
283}
284
285void KBookmarkMenu::addOpenInTabs()
286{
287 if (!d->owner || !d->owner->supportsTabs() || !KAuthorized::authorizeAction(QStringLiteral("bookmarks"))) {
288 return;
289 }
290
291 const QString title = tr(s: "Open Folder in Tabs", c: "@action:inmenu");
292
293 QAction *paOpenFolderInTabs = new QAction(title, this);
294 paOpenFolderInTabs->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
295 paOpenFolderInTabs->setToolTip(tr(s: "Open all bookmarks in this folder as a new tab", c: "@info:tooltip"));
296 paOpenFolderInTabs->setStatusTip(paOpenFolderInTabs->toolTip());
297 connect(sender: paOpenFolderInTabs, signal: &QAction::triggered, context: this, slot: &KBookmarkMenu::slotOpenFolderInTabs);
298
299 d->parentMenu->addAction(action: paOpenFolderInTabs);
300 m_actions.append(t: paOpenFolderInTabs);
301}
302
303void KBookmarkMenu::addAddBookmarksList()
304{
305 if (!d->owner || !d->owner->enableOption(option: KBookmarkOwner::ShowAddBookmark) || !d->owner->supportsTabs()
306 || !KAuthorized::authorizeAction(QStringLiteral("bookmarks"))) {
307 return;
308 }
309
310 if (!d->bookmarksToFolderAction) {
311 const QString title = tr(s: "Bookmark Tabs as Folder…", c: "@action:inmenu");
312 d->bookmarksToFolderAction = new QAction(title, this);
313
314 if (d->isRoot) {
315 d->bookmarksToFolderAction->setObjectName(QStringLiteral("add_bookmarks_list"));
316 }
317
318 d->bookmarksToFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-new-list")));
319 d->bookmarksToFolderAction->setToolTip(tr(s: "Add a folder of bookmarks for all open tabs", c: "@info:tooltip"));
320 d->bookmarksToFolderAction->setStatusTip(d->bookmarksToFolderAction->toolTip());
321 connect(sender: d->bookmarksToFolderAction, signal: &QAction::triggered, context: this, slot: &KBookmarkMenu::slotAddBookmarksList);
322 }
323
324 d->parentMenu->addAction(action: d->bookmarksToFolderAction);
325}
326
327void KBookmarkMenu::addAddBookmark()
328{
329 if (!d->owner || !d->owner->enableOption(option: KBookmarkOwner::ShowAddBookmark) || !KAuthorized::authorizeAction(QStringLiteral("bookmarks"))) {
330 return;
331 }
332
333 if (!d->addBookmarkAction) {
334 d->addBookmarkAction = KStandardActions::addBookmark(recvr: this, slot: &KBookmarkMenu::slotAddBookmark, parent: this);
335 if (d->isRoot) {
336 d->addBookmarkAction->setObjectName(QStringLiteral("add_bookmark"));
337 }
338
339 if (!d->isRoot) {
340 d->addBookmarkAction->setShortcut(QKeySequence());
341 }
342 }
343
344 d->parentMenu->addAction(action: d->addBookmarkAction);
345}
346
347void KBookmarkMenu::addEditBookmarks()
348{
349 if ((d->owner && !d->owner->enableOption(option: KBookmarkOwner::ShowEditBookmark))
350 || QStandardPaths::findExecutable(QStringLiteral(KEDITBOOKMARKS_BINARY)).isEmpty() || !KAuthorized::authorizeAction(QStringLiteral("bookmarks"))) {
351 return;
352 }
353
354 d->editBookmarksAction = KStandardActions::editBookmarks(recvr: this, slot: &KBookmarkMenu::slotEditBookmarks, parent: this);
355 d->editBookmarksAction->setObjectName(QStringLiteral("edit_bookmarks"));
356
357 d->parentMenu->addAction(action: d->editBookmarksAction);
358 d->editBookmarksAction->setToolTip(tr(s: "Edit your bookmark collection in a separate window", c: "@info:tooltip"));
359 d->editBookmarksAction->setStatusTip(d->editBookmarksAction->toolTip());
360}
361
362void KBookmarkMenu::slotEditBookmarks()
363{
364 KEditBookmarks editBookmarks;
365 editBookmarks.setBrowserMode(d->browserMode);
366 auto result = editBookmarks.openForFile(file: d->manager->path());
367
368 if (!result.sucess()) {
369 QMessageBox::critical(parent: QApplication::activeWindow(), title: QApplication::applicationDisplayName(), text: result.errorMessage());
370 }
371}
372
373void KBookmarkMenu::addNewFolder()
374{
375 if (!d->owner || !d->owner->enableOption(option: KBookmarkOwner::ShowAddBookmark) || !KAuthorized::authorizeAction(QStringLiteral("bookmarks"))) {
376 return;
377 }
378
379 if (!d->newBookmarkFolderAction) {
380 d->newBookmarkFolderAction = new QAction(tr(s: "New Bookmark Folder…", c: "@action:inmenu"), this);
381 d->newBookmarkFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-new")));
382 d->newBookmarkFolderAction->setToolTip(tr(s: "Create a new bookmark folder in this menu", c: "@info:tooltip"));
383 d->newBookmarkFolderAction->setStatusTip(d->newBookmarkFolderAction->toolTip());
384
385 if (d->isRoot) {
386 d->newBookmarkFolderAction->setObjectName(QStringLiteral("new_bookmark_folder"));
387 }
388
389 connect(sender: d->newBookmarkFolderAction, signal: &QAction::triggered, context: this, slot: &KBookmarkMenu::slotNewFolder);
390 }
391
392 d->parentMenu->addAction(action: d->newBookmarkFolderAction);
393}
394
395void KBookmarkMenu::fillBookmarks()
396{
397 KBookmarkGroup parentBookmark = d->manager->findByAddress(address: d->parentAddress).toGroup();
398 Q_ASSERT(!parentBookmark.isNull());
399
400 if (d->isRoot && !parentBookmark.first().isNull()) { // at least one bookmark
401 d->parentMenu->addSeparator();
402 }
403
404 for (KBookmark bm = parentBookmark.first(); !bm.isNull(); bm = parentBookmark.next(current: bm)) {
405 d->parentMenu->addAction(action: actionForBookmark(bm));
406 }
407}
408
409QAction *KBookmarkMenu::actionForBookmark(const KBookmark &bm)
410{
411 if (bm.isGroup()) {
412 // qCDebug(KBOOKMARKS_LOG) << "Creating bookmark submenu named " << bm.text();
413 KActionMenu *actionMenu = new KBookmarkActionMenu(bm, this);
414 m_actions.append(t: actionMenu);
415 KBookmarkMenu *subMenu = new KBookmarkMenu(d->manager, d->owner, actionMenu->menu(), bm.address());
416 m_lstSubMenus.append(t: subMenu);
417 return actionMenu;
418 } else if (bm.isSeparator()) {
419 QAction *sa = new QAction(this);
420 sa->setSeparator(true);
421 m_actions.append(t: sa);
422 return sa;
423 } else {
424 // qCDebug(KBOOKMARKS_LOG) << "Creating bookmark menu item for " << bm.text();
425 QAction *action = new KBookmarkAction(bm, d->owner, this);
426 m_actions.append(t: action);
427 return action;
428 }
429}
430
431void KBookmarkMenu::slotAddBookmarksList()
432{
433 if (!d->owner || !d->owner->supportsTabs()) {
434 return;
435 }
436
437 KBookmarkGroup parentBookmark = d->manager->findByAddress(address: d->parentAddress).toGroup();
438
439 KBookmarkDialog *dlg = new KBookmarkDialog(d->manager, QApplication::activeWindow());
440 dlg->addBookmarks(list: d->owner->currentBookmarkList(), name: QLatin1String(""), parent: parentBookmark);
441 delete dlg;
442}
443
444void KBookmarkMenu::slotAddBookmark()
445{
446 if (!d->owner) {
447 return;
448 }
449 if (d->owner->currentTitle().isEmpty() && d->owner->currentUrl().isEmpty()) {
450 return;
451 }
452 KBookmarkGroup parentBookmark = d->manager->findByAddress(address: d->parentAddress).toGroup();
453
454 if (KBookmarkSettings::self()->m_advancedaddbookmark) {
455 KBookmarkDialog *dlg = new KBookmarkDialog(d->manager, QApplication::activeWindow());
456 dlg->addBookmark(title: d->owner->currentTitle(), url: d->owner->currentUrl(), icon: d->owner->currentIcon(), parent: parentBookmark);
457 delete dlg;
458 } else {
459 parentBookmark.addBookmark(text: d->owner->currentTitle(), url: d->owner->currentUrl(), icon: d->owner->currentIcon());
460 d->manager->emitChanged(group: parentBookmark);
461 }
462}
463
464void KBookmarkMenu::slotOpenFolderInTabs()
465{
466 d->owner->openFolderinTabs(bm: d->manager->findByAddress(address: d->parentAddress).toGroup());
467}
468
469void KBookmarkMenu::slotNewFolder()
470{
471 if (!d->owner) {
472 return; // this view doesn't handle bookmarks...
473 }
474 KBookmarkGroup parentBookmark = d->manager->findByAddress(address: d->parentAddress).toGroup();
475 Q_ASSERT(!parentBookmark.isNull());
476 KBookmarkDialog *dlg = new KBookmarkDialog(d->manager, QApplication::activeWindow());
477 dlg->createNewFolder(name: QLatin1String(""), parent: parentBookmark);
478 delete dlg;
479}
480
481QAction *KBookmarkMenu::addBookmarkAction() const
482{
483 return d->addBookmarkAction;
484}
485
486QAction *KBookmarkMenu::bookmarkTabsAsFolderAction() const
487{
488 return d->bookmarksToFolderAction;
489}
490
491QAction *KBookmarkMenu::newBookmarkFolderAction() const
492{
493 return d->newBookmarkFolderAction;
494}
495
496QAction *KBookmarkMenu::editBookmarksAction() const
497{
498 return d->editBookmarksAction;
499}
500
501void KBookmarkMenu::setBrowserMode(bool browserMode)
502{
503 d->browserMode = browserMode;
504}
505
506bool KBookmarkMenu::browserMode() const
507{
508 return d->browserMode;
509}
510
511#include "moc_kbookmarkmenu.cpp"
512

source code of kbookmarks/src/widgets/kbookmarkmenu.cpp