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 }