File indexing completed on 2024-04-28 17:05:53

0001 /*
0002     SPDX-FileCopyrightText: 2002 Shie Erlich <erlich@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2002 Rafi Yanai <yanai@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "krbookmarkhandler.h"
0010 #include "kraddbookmarkdlg.h"
0011 
0012 #include "../Dialogs/popularurls.h"
0013 #include "../FileSystem/filesystem.h"
0014 #include "../Panel/krpanel.h"
0015 #include "../Panel/listpanelactions.h"
0016 #include "../icon.h"
0017 #include "../kractions.h"
0018 #include "../krglobal.h"
0019 #include "../krmainwindow.h"
0020 #include "../krslots.h"
0021 
0022 // QtCore
0023 #include <QDebug>
0024 #include <QEvent>
0025 #include <QFile>
0026 #include <QStandardPaths>
0027 #include <QTextStream>
0028 #include <QTimer>
0029 // QtGui
0030 #include <QCursor>
0031 #include <QMouseEvent>
0032 
0033 #include <KBookmarks/KBookmarkManager>
0034 #include <KConfigCore/KSharedConfig>
0035 #include <KI18n/KLocalizedString>
0036 #include <KWidgetsAddons/KMessageBox>
0037 #include <KXmlGui/KActionCollection>
0038 #include <utility>
0039 
0040 #define SPECIAL_BOOKMARKS true
0041 
0042 // ------------------------ for internal use
0043 #define BOOKMARKS_FILE "krusader/krbookmarks.xml"
0044 #define CONNECT_BM(X)                                                                                                                                          \
0045     {                                                                                                                                                          \
0046         disconnect(X, SIGNAL(activated(QUrl)), nullptr, nullptr);                                                                                              \
0047         connect(X, SIGNAL(activated(QUrl)), this, SLOT(slotActivated(QUrl)));                                                                                  \
0048     }
0049 
0050 KrBookmarkHandler::KrBookmarkHandler(KrMainWindow *mainWindow)
0051     : QObject(mainWindow->widget())
0052     , _mainWindow(mainWindow)
0053     , _middleClick(false)
0054     , _mainBookmarkPopup(nullptr)
0055     , _quickSearchAction(nullptr)
0056     , _quickSearchBar(nullptr)
0057     , _quickSearchMenu(nullptr)
0058 {
0059     // create our own action collection and make the shortcuts apply only to parent
0060     _privateCollection = new KActionCollection(this);
0061     _collection = _mainWindow->actions();
0062 
0063     // create _root: father of all bookmarks. it is a dummy bookmark and never shown
0064     _root = new KrBookmark(i18n("Bookmarks"));
0065     _root->setParent(this);
0066 
0067     // load bookmarks
0068     importFromFile();
0069 
0070     // create bookmark manager
0071     QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + BOOKMARKS_FILE;
0072     manager = KBookmarkManager::managerForFile(filename, QStringLiteral("krusader"));
0073     connect(manager, &KBookmarkManager::changed, this, &KrBookmarkHandler::bookmarksChanged);
0074 
0075     // create the quick search bar and action
0076     _quickSearchAction = new QWidgetAction(this);
0077     _quickSearchBar = new QLineEdit();
0078     _quickSearchBar->setPlaceholderText(i18n("Type to search..."));
0079     _quickSearchAction->setDefaultWidget(_quickSearchBar); // ownership of the bar is transferred to the action
0080     _quickSearchAction->setEnabled(false);
0081     _setQuickSearchText("");
0082 
0083     // fill a dummy menu to properly init actions (allows toolbar bookmark buttons to work properly)
0084     auto menu = new QMenu(mainWindow->widget());
0085     populate(menu);
0086     menu->deleteLater();
0087 }
0088 
0089 KrBookmarkHandler::~KrBookmarkHandler()
0090 {
0091     delete manager;
0092     delete _privateCollection;
0093 }
0094 
0095 void KrBookmarkHandler::bookmarkCurrent(QUrl url)
0096 {
0097     QPointer<KrAddBookmarkDlg> dlg = new KrAddBookmarkDlg(_mainWindow->widget(), std::move(url));
0098     if (dlg->exec() == QDialog::Accepted) {
0099         KrBookmark *bm = new KrBookmark(dlg->name(), dlg->url(), _collection);
0100         addBookmark(bm, dlg->folder());
0101     }
0102     delete dlg;
0103 }
0104 
0105 void KrBookmarkHandler::addBookmark(KrBookmark *bm, KrBookmark *folder)
0106 {
0107     if (folder == nullptr)
0108         folder = _root;
0109 
0110     // add to the list (bottom)
0111     folder->children().append(bm);
0112 
0113     exportToFile();
0114 }
0115 
0116 void KrBookmarkHandler::deleteBookmark(KrBookmark *bm)
0117 {
0118     if (bm->isFolder())
0119         clearBookmarks(bm); // remove the child bookmarks
0120     removeReferences(_root, bm);
0121     foreach (QWidget *w, bm->associatedWidgets())
0122         w->removeAction(bm);
0123     delete bm;
0124 
0125     exportToFile();
0126 }
0127 
0128 void KrBookmarkHandler::removeReferences(KrBookmark *root, KrBookmark *bmToRemove)
0129 {
0130     int index = root->children().indexOf(bmToRemove);
0131     if (index >= 0)
0132         root->children().removeAt(index);
0133 
0134     QListIterator<KrBookmark *> it(root->children());
0135     while (it.hasNext()) {
0136         KrBookmark *bm = it.next();
0137         if (bm->isFolder())
0138             removeReferences(bm, bmToRemove);
0139     }
0140 }
0141 
0142 void KrBookmarkHandler::exportToFileBookmark(QDomDocument &doc, QDomElement &where, KrBookmark *bm)
0143 {
0144     if (bm->isSeparator()) {
0145         QDomElement bookmark = doc.createElement("separator");
0146         where.appendChild(bookmark);
0147     } else {
0148         QDomElement bookmark = doc.createElement("bookmark");
0149         // url
0150         bookmark.setAttribute("href", bm->url().toDisplayString());
0151         // icon
0152         bookmark.setAttribute("icon", bm->iconName());
0153         // title
0154         QDomElement title = doc.createElement("title");
0155         title.appendChild(doc.createTextNode(bm->text()));
0156         bookmark.appendChild(title);
0157 
0158         where.appendChild(bookmark);
0159     }
0160 }
0161 
0162 void KrBookmarkHandler::exportToFileFolder(QDomDocument &doc, QDomElement &parent, KrBookmark *folder)
0163 {
0164     QListIterator<KrBookmark *> it(folder->children());
0165     while (it.hasNext()) {
0166         KrBookmark *bm = it.next();
0167 
0168         if (bm->isFolder()) {
0169             QDomElement newFolder = doc.createElement("folder");
0170             newFolder.setAttribute("icon", bm->iconName());
0171             parent.appendChild(newFolder);
0172             QDomElement title = doc.createElement("title");
0173             title.appendChild(doc.createTextNode(bm->text()));
0174             newFolder.appendChild(title);
0175             exportToFileFolder(doc, newFolder, bm);
0176         } else {
0177             exportToFileBookmark(doc, parent, bm);
0178         }
0179     }
0180 }
0181 
0182 // export to file using the xbel standard
0183 //
0184 //  <xbel>
0185 //    <bookmark href="https://techbase.kde.org/"><title>Developer Web Site</title></bookmark>
0186 //    <folder folded="no">
0187 //      <title>Title of this folder</title>
0188 //      <bookmark icon="kde" href="https://www.kde.org"><title>KDE Web Site</title></bookmark>
0189 //      <folder toolbar="yes">
0190 //        <title>My own bookmarks</title>
0191 //        <bookmark href="https://www.calligra.org/"><title>Calligra Suite Web Site</title></bookmark>
0192 //        <separator/>
0193 //        <bookmark href="https://www.kdevelop.org/"><title>KDevelop Web Site</title></bookmark>
0194 //      </folder>
0195 //    </folder>
0196 //  </xbel>
0197 void KrBookmarkHandler::exportToFile()
0198 {
0199     QDomDocument doc("xbel");
0200     QDomElement root = doc.createElement("xbel");
0201     doc.appendChild(root);
0202 
0203     exportToFileFolder(doc, root, _root);
0204     if (!doc.firstChild().isProcessingInstruction()) {
0205         // adding: <?xml version="1.0" encoding="UTF-8" ?> if not already present
0206         QDomProcessingInstruction instr = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\" ");
0207         doc.insertBefore(instr, doc.firstChild());
0208     }
0209 
0210     QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + BOOKMARKS_FILE;
0211     QFile file(filename);
0212     if (file.open(QIODevice::WriteOnly)) {
0213         QTextStream stream(&file);
0214         stream.setCodec("UTF-8");
0215         stream << doc.toString();
0216         file.close();
0217     } else {
0218         KMessageBox::error(_mainWindow->widget(), i18n("Unable to write to %1", filename), i18n("Error"));
0219     }
0220 }
0221 
0222 bool KrBookmarkHandler::importFromFileBookmark(QDomElement &e, KrBookmark *parent, const QString &path, QString *errorMsg)
0223 {
0224     QString url, name, iconName;
0225     // verify tag
0226     if (e.tagName() != "bookmark") {
0227         *errorMsg = i18n("%1 instead of %2", e.tagName(), QLatin1String("bookmark"));
0228         return false;
0229     }
0230     // verify href
0231     if (!e.hasAttribute("href")) {
0232         *errorMsg = i18n("missing tag %1", QLatin1String("href"));
0233         return false;
0234     } else
0235         url = e.attribute("href");
0236     // verify title
0237     QDomElement te = e.firstChild().toElement();
0238     if (te.tagName() != "title") {
0239         *errorMsg = i18n("missing tag %1", QLatin1String("title"));
0240         return false;
0241     } else
0242         name = te.text();
0243     // do we have an icon?
0244     if (e.hasAttribute("icon")) {
0245         iconName = e.attribute("icon");
0246     }
0247     // ok: got name and url, let's add a bookmark
0248     KrBookmark *bm = KrBookmark::getExistingBookmark(path + name, _collection);
0249     if (!bm) {
0250         bm = new KrBookmark(name, QUrl(url), _collection, iconName, path + name);
0251     } else {
0252         bm->setURL(QUrl(url));
0253         bm->setIconName(iconName);
0254     }
0255     parent->children().append(bm);
0256 
0257     return true;
0258 }
0259 
0260 bool KrBookmarkHandler::importFromFileFolder(QDomNode &first, KrBookmark *parent, const QString &path, QString *errorMsg)
0261 {
0262     QString name;
0263     QDomNode n = first;
0264     while (!n.isNull()) {
0265         QDomElement e = n.toElement();
0266         if (e.tagName() == "bookmark") {
0267             if (!importFromFileBookmark(e, parent, path, errorMsg))
0268                 return false;
0269         } else if (e.tagName() == "folder") {
0270             QString iconName = "";
0271             if (e.hasAttribute("icon"))
0272                 iconName = e.attribute("icon");
0273             // the title is the first child of the folder
0274             QDomElement tmp = e.firstChild().toElement();
0275             if (tmp.tagName() != "title") {
0276                 *errorMsg = i18n("missing tag %1", QLatin1String("title"));
0277                 return false;
0278             } else
0279                 name = tmp.text();
0280             KrBookmark *folder = new KrBookmark(name, iconName);
0281             parent->children().append(folder);
0282 
0283             QDomNode nextOne = tmp.nextSibling();
0284             if (!importFromFileFolder(nextOne, folder, path + name + '/', errorMsg))
0285                 return false;
0286         } else if (e.tagName() == "separator") {
0287             parent->children().append(KrBookmark::separator());
0288         }
0289         n = n.nextSibling();
0290     }
0291     return true;
0292 }
0293 
0294 void KrBookmarkHandler::importFromFile()
0295 {
0296     clearBookmarks(_root, false);
0297 
0298     QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + BOOKMARKS_FILE;
0299     QFile file(filename);
0300     if (!file.open(QIODevice::ReadOnly))
0301         return; // no bookmarks file
0302 
0303     QString errorMsg;
0304     QDomNode n;
0305     QDomElement e;
0306     QDomDocument doc("xbel");
0307     if (!doc.setContent(&file, &errorMsg)) {
0308         goto BM_ERROR;
0309     }
0310     // iterate through the document: first child should be "xbel" (skip all until we find it)
0311     n = doc.firstChild();
0312     while (!n.isNull() && n.toElement().tagName() != "xbel")
0313         n = n.nextSibling();
0314 
0315     if (n.isNull() || n.toElement().tagName() != "xbel") {
0316         errorMsg = i18n("%1 does not seem to be a valid bookmarks file", filename);
0317         goto BM_ERROR;
0318     } else
0319         n = n.firstChild(); // skip the xbel part
0320     importFromFileFolder(n, _root, "", &errorMsg);
0321     goto BM_SUCCESS;
0322 
0323 BM_ERROR:
0324     KMessageBox::error(_mainWindow->widget(), i18n("Error reading bookmarks file: %1", errorMsg), i18n("Error"));
0325 
0326 BM_SUCCESS:
0327     file.close();
0328 }
0329 
0330 void KrBookmarkHandler::_setQuickSearchText(const QString &text)
0331 {
0332     bool isEmptyQuickSearchBarVisible = KConfigGroup(krConfig, "Look&Feel").readEntry("Always show search bar", true);
0333 
0334     _quickSearchBar->setText(text);
0335 
0336     auto length = text.length();
0337     bool isVisible = isEmptyQuickSearchBarVisible || length > 0;
0338     _quickSearchAction->setVisible(isVisible);
0339     _quickSearchBar->setVisible(isVisible);
0340 
0341     if (length == 0) {
0342         qDebug() << "Bookmark search: reset";
0343         _resetActionTextAndHighlighting();
0344     } else {
0345         qDebug() << "Bookmark search: query =" << text;
0346     }
0347 }
0348 
0349 QString KrBookmarkHandler::_quickSearchText() const
0350 {
0351     return _quickSearchBar->text();
0352 }
0353 
0354 void KrBookmarkHandler::_highlightAction(QAction *action, bool isMatched)
0355 {
0356     auto font = action->font();
0357     font.setBold(isMatched);
0358     action->setFont(font);
0359 }
0360 
0361 void KrBookmarkHandler::populate(QMenu *menu)
0362 {
0363     // removing action from previous menu is necessary
0364     // otherwise it won't be displayed in the currently populating menu
0365     if (_mainBookmarkPopup) {
0366         _mainBookmarkPopup->removeAction(_quickSearchAction);
0367     }
0368     _mainBookmarkPopup = menu;
0369     menu->clear();
0370     _specialBookmarks.clear();
0371     buildMenu(_root, menu);
0372 }
0373 
0374 void KrBookmarkHandler::buildMenu(KrBookmark *parent, QMenu *menu, int depth)
0375 {
0376     // add search bar widget to the top of the menu
0377     if (depth == 0) {
0378         menu->addAction(_quickSearchAction);
0379     }
0380 
0381     // run the loop twice, in order to put the folders on top. stupid but easy :-)
0382     // note: this code drops the separators put there by the user
0383     QListIterator<KrBookmark *> it(parent->children());
0384     while (it.hasNext()) {
0385         KrBookmark *bm = it.next();
0386 
0387         if (!bm->isFolder())
0388             continue;
0389         auto *newMenu = new QMenu(menu);
0390         newMenu->setIcon(Icon(bm->iconName()));
0391         newMenu->setTitle(bm->text());
0392         QAction *menuAction = menu->addMenu(newMenu);
0393         QVariant v;
0394         v.setValue<KrBookmark *>(bm);
0395         menuAction->setData(v);
0396 
0397         buildMenu(bm, newMenu, depth + 1);
0398     }
0399 
0400     it.toFront();
0401     while (it.hasNext()) {
0402         KrBookmark *bm = it.next();
0403         if (bm->isFolder())
0404             continue;
0405         if (bm->isSeparator()) {
0406             menu->addSeparator();
0407             continue;
0408         }
0409 
0410         QUrl urlToSet = bm->url();
0411         if (!urlToSet.isEmpty() && urlToSet.isRelative()) {
0412             // Make it possible that the url can be used later by Krusader.
0413             // This avoids users seeing the effects described in the "Editing a local path in Bookmark
0414             // Manager breaks a bookmark" bug report (https://bugs.kde.org/show_bug.cgi?id=393320),
0415             // though it would be better to solve that upstream frameworks-kbookmarks bug
0416             bm->setURL(QUrl::fromUserInput(urlToSet.toString(), QString(), QUrl::AssumeLocalFile));
0417         }
0418 
0419         menu->addAction(bm);
0420         CONNECT_BM(bm);
0421     }
0422 
0423     if (depth == 0) {
0424         KConfigGroup group(krConfig, "Private");
0425         bool hasPopularURLs = group.readEntry("BM Popular URLs", true);
0426         bool hasTrash = group.readEntry("BM Trash", true);
0427         bool hasLan = group.readEntry("BM Lan", true);
0428         bool hasVirtualFS = group.readEntry("BM Virtual FS", true);
0429         bool hasJumpback = group.readEntry("BM Jumpback", true);
0430 
0431         if (hasPopularURLs) {
0432             menu->addSeparator();
0433 
0434             // add the popular links submenu
0435             auto *newMenu = new QMenu(menu);
0436             newMenu->setTitle(i18n("Popular URLs"));
0437             newMenu->setIcon(Icon("folder-bookmark"));
0438             QAction *bmfAct = menu->addMenu(newMenu);
0439             _specialBookmarks.append(bmfAct);
0440             // add the top 15 urls
0441 #define MAX 15
0442             QList<QUrl> list = _mainWindow->popularUrls()->getMostPopularUrls(MAX);
0443             QList<QUrl>::Iterator it;
0444             for (it = list.begin(); it != list.end(); ++it) {
0445                 QString name;
0446                 if ((*it).isLocalFile())
0447                     name = (*it).path();
0448                 else
0449                     name = (*it).toDisplayString();
0450                 // note: these bookmark are put into the private collection
0451                 // as to not spam the general collection
0452                 KrBookmark *bm = KrBookmark::getExistingBookmark(name, _privateCollection);
0453                 if (!bm)
0454                     bm = new KrBookmark(name, *it, _privateCollection);
0455                 newMenu->addAction(bm);
0456                 CONNECT_BM(bm);
0457             }
0458 
0459             newMenu->addSeparator();
0460             if (krPopularUrls != nullptr) {
0461                 newMenu->addAction(krPopularUrls);
0462             }
0463             newMenu->installEventFilter(this);
0464         }
0465 
0466         // do we need to add special bookmarks?
0467         if (SPECIAL_BOOKMARKS) {
0468             if (hasTrash || hasLan || hasVirtualFS)
0469                 menu->addSeparator();
0470 
0471             KrBookmark *bm;
0472 
0473             // note: special bookmarks are not kept inside the _bookmarks list and added ad-hoc
0474             if (hasTrash) {
0475                 bm = KrBookmark::trash(_collection);
0476                 menu->addAction(bm);
0477                 _specialBookmarks.append(bm);
0478                 CONNECT_BM(bm);
0479             }
0480 
0481             if (hasLan) {
0482                 bm = KrBookmark::lan(_collection);
0483                 menu->addAction(bm);
0484                 _specialBookmarks.append(bm);
0485                 CONNECT_BM(bm);
0486             }
0487 
0488             if (hasVirtualFS) {
0489                 bm = KrBookmark::virt(_collection);
0490                 menu->addAction(bm);
0491                 _specialBookmarks.append(bm);
0492                 CONNECT_BM(bm);
0493             }
0494 
0495             if (hasJumpback) {
0496                 menu->addSeparator();
0497 
0498                 ListPanelActions *actions = _mainWindow->listPanelActions();
0499 
0500                 auto slotTriggered = [=] {
0501                     if (_mainBookmarkPopup && !_mainBookmarkPopup->isHidden()) {
0502                         _mainBookmarkPopup->close();
0503                     }
0504                 };
0505                 auto addJumpBackAction = [=](bool isSetter) {
0506                     auto action = KrBookmark::jumpBackAction(_privateCollection, isSetter, actions);
0507                     if (action) {
0508                         menu->addAction(action);
0509                         _specialBookmarks.append(action);
0510 
0511                         // disconnecting from this as a receiver is important:
0512                         // we don't want to break connections established by KrBookmark::jumpBackAction
0513                         disconnect(action, &QAction::triggered, this, nullptr);
0514                         connect(action, &QAction::triggered, this, slotTriggered);
0515                     }
0516                 };
0517 
0518                 addJumpBackAction(true);
0519                 addJumpBackAction(false);
0520             }
0521         }
0522 
0523         menu->addSeparator();
0524         if (KrActions::actAddBookmark != nullptr) {
0525             menu->addAction(KrActions::actAddBookmark);
0526             _specialBookmarks.append(KrActions::actAddBookmark);
0527         }
0528         QAction *bmAct = menu->addAction(Icon("bookmarks"), i18n("Manage Bookmarks"), manager, SLOT(slotEditBookmarks()));
0529         _specialBookmarks.append(bmAct);
0530 
0531         // make sure the menu is connected to us
0532         disconnect(menu, SIGNAL(triggered(QAction *)), nullptr, nullptr);
0533     }
0534 
0535     menu->installEventFilter(this);
0536 }
0537 
0538 void KrBookmarkHandler::clearBookmarks(KrBookmark *root, bool removeBookmarks)
0539 {
0540     for (auto it = root->children().begin(); it != root->children().end(); it = root->children().erase(it)) {
0541         KrBookmark *bm = *it;
0542 
0543         if (bm->isFolder()) {
0544             clearBookmarks(bm, removeBookmarks);
0545             delete bm;
0546         } else if (bm->isSeparator()) {
0547             delete bm;
0548         } else if (removeBookmarks) {
0549             foreach (QWidget *w, bm->associatedWidgets()) {
0550                 w->removeAction(bm);
0551             }
0552             delete bm;
0553         }
0554     }
0555 }
0556 
0557 void KrBookmarkHandler::bookmarksChanged(const QString &, const QString &)
0558 {
0559     importFromFile();
0560 }
0561 
0562 bool KrBookmarkHandler::eventFilter(QObject *obj, QEvent *ev)
0563 {
0564     auto eventType = ev->type();
0565     auto *menu = qobject_cast<QMenu *>(obj);
0566 
0567     if (eventType == QEvent::Show && menu) {
0568         _setQuickSearchText("");
0569         _quickSearchMenu = menu;
0570         qDebug() << "Bookmark search: menu" << menu << "is shown";
0571 
0572         return QObject::eventFilter(obj, ev);
0573     }
0574 
0575     if (eventType == QEvent::Close && menu && _quickSearchMenu) {
0576         if (_quickSearchMenu == menu) {
0577             qDebug() << "Bookmark search: stopped on menu" << menu;
0578             _setQuickSearchText("");
0579             _quickSearchMenu = nullptr;
0580         } else {
0581             qDebug() << "Bookmark search: active action =" << _quickSearchMenu->activeAction();
0582 
0583             // fix automatic deactivation of current action due to spurious close event from submenu
0584             auto quickSearchMenu = _quickSearchMenu;
0585             auto activeAction = _quickSearchMenu->activeAction();
0586             QTimer::singleShot(0, this, [=]() {
0587                 qDebug() << "Bookmark search: active action =" << quickSearchMenu->activeAction();
0588                 if (!quickSearchMenu->activeAction() && activeAction) {
0589                     quickSearchMenu->setActiveAction(activeAction);
0590                     qDebug() << "Bookmark search: restored active action =" << quickSearchMenu->activeAction();
0591                 }
0592             });
0593         }
0594 
0595         return QObject::eventFilter(obj, ev);
0596     }
0597 
0598     // Having it occur on keypress is consistent with other shortcuts,
0599     // such as Ctrl+W and accelerator keys
0600     if (eventType == QEvent::KeyPress && menu) {
0601         auto *kev = dynamic_cast<QKeyEvent *>(ev);
0602         const QList<QAction *> acts = menu->actions();
0603         bool quickSearchStarted = false;
0604         bool searchInSpecialItems = KConfigGroup(krConfig, "Look&Feel").readEntry("Search in special items", false);
0605 
0606         if (kev->key() == Qt::Key_Left && kev->modifiers() == Qt::NoModifier) {
0607             menu->close();
0608             return true;
0609         }
0610 
0611         if ((kev->modifiers() != Qt::ShiftModifier && kev->modifiers() != Qt::NoModifier) || kev->text().isEmpty() || kev->key() == Qt::Key_Delete
0612             || kev->key() == Qt::Key_Return || kev->key() == Qt::Key_Escape) {
0613             return QObject::eventFilter(obj, ev);
0614         }
0615 
0616         // update quick search text
0617         if (kev->key() == Qt::Key_Backspace) {
0618             auto newSearchText = _quickSearchText();
0619             newSearchText.chop(1);
0620             _setQuickSearchText(newSearchText);
0621 
0622             if (_quickSearchText().length() == 0) {
0623                 return QObject::eventFilter(obj, ev);
0624             }
0625         } else {
0626             quickSearchStarted = _quickSearchText().length() == 0;
0627             _setQuickSearchText(_quickSearchText().append(kev->text()));
0628         }
0629 
0630         if (quickSearchStarted) {
0631             _quickSearchMenu = menu;
0632             qDebug() << "Bookmark search: started on menu" << menu;
0633         }
0634 
0635         // match actions
0636         QAction *matchedAction = nullptr;
0637         int nMatches = 0;
0638         const Qt::CaseSensitivity matchCase = _quickSearchText() == _quickSearchText().toLower() ? Qt::CaseInsensitive : Qt::CaseSensitive;
0639         for (auto act : acts) {
0640             if (act->isSeparator() || act->text().isEmpty()) {
0641                 continue;
0642             }
0643 
0644             if (!searchInSpecialItems && _specialBookmarks.contains(act)) {
0645                 continue;
0646             }
0647 
0648             if (quickSearchStarted) {
0649                 // if the first key press is an accelerator key, let the accelerator handler process this event
0650                 if (act->text().contains('&' + kev->text(), Qt::CaseInsensitive)) {
0651                     qDebug() << "Bookmark search: hit accelerator key of" << act;
0652                     _setQuickSearchText("");
0653                     return QObject::eventFilter(obj, ev);
0654                 }
0655 
0656                 // strip accelerator keys from actions so they don't interfere with the search key press events
0657                 auto text = act->text();
0658                 _quickSearchOriginalActionTitles.insert(act, text);
0659                 act->setText(KLocalizedString::removeAcceleratorMarker(text));
0660             }
0661 
0662             // match prefix of the action text to the query
0663             if (act->text().left(_quickSearchText().length()).compare(_quickSearchText(), matchCase) == 0) {
0664                 _highlightAction(act);
0665                 if (!matchedAction || matchedAction->menu()) {
0666                     // Can't highlight menus (see comment below), hopefully pick something we can
0667                     matchedAction = act;
0668                 }
0669                 nMatches++;
0670             } else {
0671                 _highlightAction(act, false);
0672             }
0673         }
0674 
0675         if (matchedAction) {
0676             qDebug() << "Bookmark search: primary match =" << matchedAction->text() << ", number of matches =" << nMatches;
0677         } else {
0678             qDebug() << "Bookmark search: no matches";
0679         }
0680 
0681         // trigger the matched menu item or set an active item accordingly
0682         if (nMatches == 1) {
0683             _setQuickSearchText("");
0684             if ((bool)matchedAction->menu()) {
0685                 menu->setActiveAction(matchedAction);
0686             } else {
0687                 matchedAction->activate(QAction::Trigger);
0688             }
0689         } else if (nMatches > 1) {
0690             // Because of a bug submenus cannot be highlighted
0691             // https://bugreports.qt.io/browse/QTBUG-939
0692             if (!matchedAction->menu()) {
0693                 menu->setActiveAction(matchedAction);
0694             } else {
0695                 menu->setActiveAction(nullptr);
0696             }
0697         } else {
0698             menu->setActiveAction(nullptr);
0699         }
0700         return true;
0701     }
0702 
0703     if (eventType == QEvent::MouseButtonRelease) {
0704         switch (dynamic_cast<QMouseEvent *>(ev)->button()) {
0705         case Qt::RightButton:
0706             _middleClick = false;
0707             if (obj->inherits("QMenu")) {
0708                 auto *menu = dynamic_cast<QMenu *>(obj);
0709                 QAction *act = menu->actionAt(dynamic_cast<QMouseEvent *>(ev)->pos());
0710 
0711                 if (obj == _mainBookmarkPopup && _specialBookmarks.contains(act)) {
0712                     rightClickOnSpecialBookmark();
0713                     return true;
0714                 }
0715 
0716                 auto *bm = qobject_cast<KrBookmark *>(act);
0717                 if (bm != nullptr) {
0718                     rightClicked(menu, bm);
0719                     return true;
0720                 } else if (act && act->data().canConvert<KrBookmark *>()) {
0721                     auto *bm = act->data().value<KrBookmark *>();
0722                     rightClicked(menu, bm);
0723                 }
0724             }
0725             break;
0726         case Qt::LeftButton:
0727             _middleClick = false;
0728             break;
0729         case Qt::MidButton:
0730             _middleClick = true;
0731             break;
0732         default:
0733             break;
0734         }
0735     }
0736     return QObject::eventFilter(obj, ev);
0737 }
0738 
0739 void KrBookmarkHandler::_resetActionTextAndHighlighting()
0740 {
0741     for (QHash<QAction *, QString>::const_iterator i = _quickSearchOriginalActionTitles.constBegin(); i != _quickSearchOriginalActionTitles.constEnd(); ++i) {
0742         QAction *action = i.key();
0743         action->setText(i.value());
0744         _highlightAction(action, false);
0745     }
0746 
0747     _quickSearchOriginalActionTitles.clear();
0748 }
0749 
0750 #define POPULAR_URLS_ID 100100
0751 #define TRASH_ID 100101
0752 #define LAN_ID 100103
0753 #define VIRTUAL_FS_ID 100102
0754 #define JUMP_BACK_ID 100104
0755 
0756 void KrBookmarkHandler::rightClickOnSpecialBookmark()
0757 {
0758     KConfigGroup group(krConfig, "Private");
0759     bool hasPopularURLs = group.readEntry("BM Popular URLs", true);
0760     bool hasTrash = group.readEntry("BM Trash", true);
0761     bool hasLan = group.readEntry("BM Lan", true);
0762     bool hasVirtualFS = group.readEntry("BM Virtual FS", true);
0763     bool hasJumpback = group.readEntry("BM Jumpback", true);
0764 
0765     QMenu menu(_mainBookmarkPopup);
0766     menu.setTitle(i18n("Enable special bookmarks"));
0767 
0768     QAction *act;
0769 
0770     act = menu.addAction(i18n("Popular URLs"));
0771     act->setData(QVariant(POPULAR_URLS_ID));
0772     act->setCheckable(true);
0773     act->setChecked(hasPopularURLs);
0774     act = menu.addAction(i18n("Trash bin"));
0775     act->setData(QVariant(TRASH_ID));
0776     act->setCheckable(true);
0777     act->setChecked(hasTrash);
0778     act = menu.addAction(i18n("Local Network"));
0779     act->setData(QVariant(LAN_ID));
0780     act->setCheckable(true);
0781     act->setChecked(hasLan);
0782     act = menu.addAction(i18n("Virtual Filesystem"));
0783     act->setData(QVariant(VIRTUAL_FS_ID));
0784     act->setCheckable(true);
0785     act->setChecked(hasVirtualFS);
0786     act = menu.addAction(i18n("Jump back"));
0787     act->setData(QVariant(JUMP_BACK_ID));
0788     act->setCheckable(true);
0789     act->setChecked(hasJumpback);
0790 
0791     connect(_mainBookmarkPopup, SIGNAL(highlighted(int)), &menu, SLOT(close()));
0792     connect(_mainBookmarkPopup, SIGNAL(activated(int)), &menu, SLOT(close()));
0793 
0794     int result = -1;
0795     QAction *res = menu.exec(QCursor::pos());
0796     if (res && res->data().canConvert<int>())
0797         result = res->data().toInt();
0798 
0799     bool doCloseMain = true;
0800 
0801     switch (result) {
0802     case POPULAR_URLS_ID:
0803         group.writeEntry("BM Popular URLs", !hasPopularURLs);
0804         break;
0805     case TRASH_ID:
0806         group.writeEntry("BM Trash", !hasTrash);
0807         break;
0808     case LAN_ID:
0809         group.writeEntry("BM Lan", !hasLan);
0810         break;
0811     case VIRTUAL_FS_ID:
0812         group.writeEntry("BM Virtual FS", !hasVirtualFS);
0813         break;
0814     case JUMP_BACK_ID:
0815         group.writeEntry("BM Jumpback", !hasJumpback);
0816         break;
0817     default:
0818         doCloseMain = false;
0819         break;
0820     }
0821 
0822     menu.close();
0823 
0824     if (doCloseMain && _mainBookmarkPopup)
0825         _mainBookmarkPopup->close();
0826 }
0827 
0828 #define OPEN_ID 100200
0829 #define OPEN_NEW_TAB_ID 100201
0830 #define DELETE_ID 100202
0831 
0832 void KrBookmarkHandler::rightClicked(QMenu *menu, KrBookmark *bm)
0833 {
0834     QMenu popup(_mainBookmarkPopup);
0835     QAction *act;
0836 
0837     if (!bm->isFolder()) {
0838         act = popup.addAction(Icon("document-open"), i18n("Open"));
0839         act->setData(QVariant(OPEN_ID));
0840         act = popup.addAction(Icon("tab-new"), i18n("Open in a new tab"));
0841         act->setData(QVariant(OPEN_NEW_TAB_ID));
0842         popup.addSeparator();
0843     }
0844     act = popup.addAction(Icon("edit-delete"), i18n("Delete"));
0845     act->setData(QVariant(DELETE_ID));
0846 
0847     connect(menu, SIGNAL(highlighted(int)), &popup, SLOT(close()));
0848     connect(menu, SIGNAL(activated(int)), &popup, SLOT(close()));
0849 
0850     int result = -1;
0851     QAction *res = popup.exec(QCursor::pos());
0852     if (res && res->data().canConvert<int>())
0853         result = res->data().toInt();
0854 
0855     popup.close();
0856     if (_mainBookmarkPopup && result >= OPEN_ID && result <= DELETE_ID) {
0857         _mainBookmarkPopup->close();
0858     }
0859 
0860     switch (result) {
0861     case OPEN_ID:
0862         SLOTS->refresh(bm->url());
0863         break;
0864     case OPEN_NEW_TAB_ID:
0865         _mainWindow->activeManager()->newTab(bm->url());
0866         break;
0867     case DELETE_ID:
0868         deleteBookmark(bm);
0869         break;
0870     }
0871 }
0872 
0873 // used to monitor middle clicks. if mid is found, then the
0874 // bookmark is opened in a new tab. ugly, but easier than overloading
0875 // KAction and KActionCollection.
0876 void KrBookmarkHandler::slotActivated(const QUrl &url)
0877 {
0878     if (_mainBookmarkPopup && !_mainBookmarkPopup->isHidden())
0879         _mainBookmarkPopup->close();
0880     if (_middleClick)
0881         _mainWindow->activeManager()->newTab(url);
0882     else
0883         SLOTS->refresh(url);
0884 }