diff --git a/doc/bookmarks.docbook b/doc/bookmarks.docbook
--- a/doc/bookmarks.docbook
+++ b/doc/bookmarks.docbook
@@ -110,6 +110,12 @@
bookmark is executed.
+
+
+ The search bar will always be visible in the bookmarks menu if you check the
+ corresponding item on the Panel Konfigurator's page.
+
+
Password handling
diff --git a/doc/konfigurator.docbook b/doc/konfigurator.docbook
--- a/doc/konfigurator.docbook
+++ b/doc/konfigurator.docbook
@@ -297,6 +297,16 @@
and Filter. You can change the mode later using the search bar itself.
+
+
+ Bookmark Search
+
+
+
+ Always show search bar: If checked,
+ make bookmark search bar always visible.
+
+
Status/Totalsbar
diff --git a/krusader/BookMan/krbookmark.h b/krusader/BookMan/krbookmark.h
--- a/krusader/BookMan/krbookmark.h
+++ b/krusader/BookMan/krbookmark.h
@@ -26,8 +26,9 @@
// QtWidgets
#include
-
class KActionCollection;
+class ListPanelActions;
+
class KrBookmark: public QAction
{
@@ -57,12 +58,14 @@
return _children;
}
- static KrBookmark* getExistingBookmark(QString actionName, KActionCollection *collection);
+ static KrBookmark * getExistingBookmark(QString actionName, KActionCollection *collection);
+
// ----- special bookmarks
- static KrBookmark* trash(KActionCollection *collection);
- static KrBookmark* virt(KActionCollection *collection);
- static KrBookmark* lan(KActionCollection *collection);
- static KrBookmark* separator();
+ static KrBookmark * trash(KActionCollection *collection);
+ static KrBookmark * virt(KActionCollection *collection);
+ static KrBookmark * lan(KActionCollection *collection);
+ static QAction * jumpBackAction(KActionCollection *collection, bool isSetter = false, ListPanelActions *sourceActions = 0);
+ static KrBookmark * separator();
signals:
void activated(const QUrl &url);
diff --git a/krusader/BookMan/krbookmark.cpp b/krusader/BookMan/krbookmark.cpp
--- a/krusader/BookMan/krbookmark.cpp
+++ b/krusader/BookMan/krbookmark.cpp
@@ -21,6 +21,7 @@
#include "../krglobal.h"
#include "../Archive/krarchandler.h"
#include "../FileSystem/krtrashhandler.h"
+#include "../Panel/listpanelactions.h"
#include
#include
@@ -71,12 +72,12 @@
}
}
-KrBookmark* KrBookmark::getExistingBookmark(QString actionName, KActionCollection *collection)
+KrBookmark * KrBookmark::getExistingBookmark(QString actionName, KActionCollection *collection)
{
return static_cast(collection->action(BM_NAME(actionName)));
}
-KrBookmark* KrBookmark::trash(KActionCollection *collection)
+KrBookmark * KrBookmark::trash(KActionCollection *collection)
{
KrBookmark *bm = getExistingBookmark(i18n(NAME_TRASH), collection);
if (!bm)
@@ -86,7 +87,7 @@
return bm;
}
-KrBookmark* KrBookmark::virt(KActionCollection *collection)
+KrBookmark * KrBookmark::virt(KActionCollection *collection)
{
KrBookmark *bm = getExistingBookmark(i18n(NAME_VIRTUAL), collection);
if (!bm) {
@@ -96,7 +97,7 @@
return bm;
}
-KrBookmark* KrBookmark::lan(KActionCollection *collection)
+KrBookmark * KrBookmark::lan(KActionCollection *collection)
{
KrBookmark *bm = getExistingBookmark(i18n(NAME_LAN), collection);
if (!bm) {
@@ -106,7 +107,32 @@
return bm;
}
-KrBookmark* KrBookmark::separator()
+QAction * KrBookmark::jumpBackAction(KActionCollection *collection, bool isSetter, ListPanelActions *sourceActions)
+{
+ auto actionName = isSetter ? QString("setJumpBack") : QString("jumpBack");
+ auto action = collection->action(actionName);
+ if (action) {
+ return action;
+ }
+
+ if (!sourceActions) {
+ return nullptr;
+ }
+
+ // copy essential part of source action
+ auto sourceAction = isSetter ? sourceActions->actSetJumpBack : sourceActions->actJumpBack;
+ action = new QAction(sourceAction->icon(), sourceAction->text(), sourceAction);
+ action->setShortcut(sourceAction->shortcut());
+ action->setShortcutContext(Qt::WidgetShortcut);
+ connect(action, &QAction::triggered, sourceAction, &QAction::trigger);
+ // ensure there are no accelerator keys coming from another menu
+ action->setText(KLocalizedString::removeAcceleratorMarker(action->text()));
+
+ collection->addAction(actionName, action);
+ return action;
+}
+
+KrBookmark * KrBookmark::separator()
{
KrBookmark *bm = new KrBookmark("");
bm->_separator = true;
diff --git a/krusader/BookMan/krbookmarkbutton.cpp b/krusader/BookMan/krbookmarkbutton.cpp
--- a/krusader/BookMan/krbookmarkbutton.cpp
+++ b/krusader/BookMan/krbookmarkbutton.cpp
@@ -41,14 +41,10 @@
acmBookmarks = new KActionMenu(QIcon::fromTheme("bookmarks"), i18n("Bookmarks"), this);
acmBookmarks->setDelayed(false);
- // TODO KF5 : explicit cast as QMenu doesn't have those methods
- //(acmBookmarks->menu())->setKeyboardShortcutsEnabled(true);
- //(acmBookmarks->menu())->setKeyboardShortcutsExecute(true);
setMenu(acmBookmarks->menu());
connect(acmBookmarks->menu(), SIGNAL(aboutToShow()), this, SLOT(populate()));
connect(acmBookmarks->menu(), SIGNAL(aboutToShow()), this, SIGNAL(aboutToShow()));
- populate();
}
void KrBookmarkButton::populate()
diff --git a/krusader/BookMan/krbookmarkhandler.h b/krusader/BookMan/krbookmarkhandler.h
--- a/krusader/BookMan/krbookmarkhandler.h
+++ b/krusader/BookMan/krbookmarkhandler.h
@@ -30,6 +30,8 @@
#include
// QtWidgets
#include
+#include
+#include
#include "krbookmark.h"
@@ -58,10 +60,9 @@
void exportToFileFolder(QDomDocument &doc, QDomElement &parent, KrBookmark *folder);
void exportToFileBookmark(QDomDocument &doc, QDomElement &where, KrBookmark *bm);
void clearBookmarks(KrBookmark *root);
- void buildMenu(KrBookmark *parent, QMenu *menu);
+ void buildMenu(KrBookmark *parent, QMenu *menu, int depth = 0);
bool eventFilter(QObject *obj, QEvent *ev);
-
void rightClicked(QMenu *menu, KrBookmark *bm);
void rightClickOnSpecialBookmark();
@@ -81,6 +82,15 @@
QPointer _mainBookmarkPopup; // main bookmark popup menu
QList _specialBookmarks; // the action list of the special bookmarks
+
+ QWidgetAction *_quickSearchAction;
+ QLineEdit *_quickSearchBar;
+ QHash _quickSearchOriginalActionTitles; ///< Saved original action text values to restore after search
+
+ void _setQuickSearchText(const QString &text);
+ QString _quickSearchText() const;
+ static void _highlightAction(QAction *action, bool isMatched = true);
+ void _resetActionTextAndHighlighting();
};
Q_DECLARE_METATYPE(KrBookmark *)
diff --git a/krusader/BookMan/krbookmarkhandler.cpp b/krusader/BookMan/krbookmarkhandler.cpp
--- a/krusader/BookMan/krbookmarkhandler.cpp
+++ b/krusader/BookMan/krbookmarkhandler.cpp
@@ -34,6 +34,7 @@
#include
#include
#include
+#include
// QtGui
#include
#include
@@ -52,7 +53,8 @@
#define CONNECT_BM(X) { disconnect(X, SIGNAL(activated(QUrl)), 0, 0); connect(X, SIGNAL(activated(QUrl)), this, SLOT(slotActivated(QUrl))); }
KrBookmarkHandler::KrBookmarkHandler(KrMainWindow *mainWindow) : QObject(mainWindow->widget()),
- _mainWindow(mainWindow), _middleClick(false), _mainBookmarkPopup(0), _specialBookmarks()
+ _mainWindow(mainWindow), _middleClick(false), _mainBookmarkPopup(0), _specialBookmarks(),
+ _quickSearchAction(nullptr), _quickSearchBar(nullptr)
{
// create our own action collection and make the shortcuts apply only to parent
_privateCollection = new KActionCollection(this);
@@ -65,10 +67,23 @@
// load bookmarks
importFromFile();
- // hack
+ // create bookmark manager
QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + BOOKMARKS_FILE;
manager = KBookmarkManager::managerForFile(filename, QStringLiteral("krusader"));
connect(manager, SIGNAL(changed(QString,QString)), this, SLOT(bookmarksChanged(QString,QString)));
+
+ // create the quick search bar and action
+ _quickSearchAction = new QWidgetAction(this);
+ _quickSearchBar = new QLineEdit();
+ _quickSearchBar->setPlaceholderText(i18n("Type to search..."));
+ _quickSearchAction->setDefaultWidget(_quickSearchBar); // ownership of the bar is transferred to the action
+ _quickSearchAction->setEnabled(false);
+ _setQuickSearchText("");
+
+ // fill a dummy menu to properly init actions (allows toolbar bookmark buttons to work properly)
+ auto menu = new QMenu(mainWindow->widget());
+ populate(menu);
+ menu->deleteLater();
}
KrBookmarkHandler::~KrBookmarkHandler()
@@ -307,17 +322,51 @@
file.close();
}
+void KrBookmarkHandler::_setQuickSearchText(const QString &text)
+{
+ bool isEmptyQuickSearchBarVisible = KConfigGroup(krConfig, "Look&Feel").readEntry("Always show search bar", true);
+
+ _quickSearchBar->setText(text);
+
+ auto length = text.length();
+ bool isVisible = isEmptyQuickSearchBarVisible || length > 0;
+ _quickSearchAction->setVisible(isVisible);
+ _quickSearchBar->setVisible(isVisible);
+
+ if (length == 0) {
+ qDebug() << "Bookmark search: reset";
+ _resetActionTextAndHighlighting();
+ } else {
+ qDebug() << "Bookmark search: query =" << text;
+ }
+}
+
+QString KrBookmarkHandler::_quickSearchText() const
+{
+ return _quickSearchBar->text();
+}
+
+void KrBookmarkHandler::_highlightAction(QAction *action, bool isMatched)
+{
+ auto font = action->font();
+ font.setBold(isMatched);
+ action->setFont(font);
+}
+
void KrBookmarkHandler::populate(QMenu *menu)
{
_mainBookmarkPopup = menu;
menu->clear();
_specialBookmarks.clear();
buildMenu(_root, menu);
}
-void KrBookmarkHandler::buildMenu(KrBookmark *parent, QMenu *menu)
+void KrBookmarkHandler::buildMenu(KrBookmark *parent, QMenu *menu, int depth)
{
- static int inSecondaryMenu = 0; // used to know if we're on the top menu
+ // add search bar widget to the top of the menu
+ if (depth == 0) {
+ menu->addAction(_quickSearchAction);
+ }
// run the loop twice, in order to put the folders on top. stupid but easy :-)
// note: this code drops the separators put there by the user
@@ -334,9 +383,7 @@
v.setValue(bm);
menuAction->setData(v);
- ++inSecondaryMenu;
- buildMenu(bm, newMenu);
- --inSecondaryMenu;
+ buildMenu(bm, newMenu, depth + 1);
}
it.toFront();
@@ -351,7 +398,7 @@
CONNECT_BM(bm);
}
- if (!inSecondaryMenu) {
+ if (depth == 0) {
KConfigGroup group(krConfig, "Private");
bool hasPopularURLs = group.readEntry("BM Popular URLs", true);
bool hasTrash = group.readEntry("BM Trash", true);
@@ -392,7 +439,7 @@
// do we need to add special bookmarks?
if (SPECIAL_BOOKMARKS) {
- if (hasTrash || hasLan || hasVirtualFS || hasJumpback)
+ if (hasTrash || hasLan || hasVirtualFS)
menu->addSeparator();
KrBookmark *bm;
@@ -420,19 +467,25 @@
}
if (hasJumpback) {
- // add the jump-back button
- ListPanelActions *actions = _mainWindow->listPanelActions();
- menu->addAction(actions->actJumpBack);
- _specialBookmarks.append(actions->actJumpBack);
menu->addSeparator();
- menu->addAction(actions->actSetJumpBack);
- _specialBookmarks.append(actions->actSetJumpBack);
+
+ ListPanelActions *actions = _mainWindow->listPanelActions();
+
+ auto action = KrBookmark::jumpBackAction(_privateCollection, true, actions);
+ if (action) {
+ menu->addAction(action);
+ _specialBookmarks.append(action);
+ }
+
+ action = KrBookmark::jumpBackAction(_privateCollection, false, actions);
+ if (action) {
+ menu->addAction(action);
+ _specialBookmarks.append(action);
+ }
}
}
- if (!hasJumpback)
- menu->addSeparator();
-
+ menu->addSeparator();
menu->addAction(KrActions::actAddBookmark);
_specialBookmarks.append(KrActions::actAddBookmark);
QAction *bmAct = menu->addAction(krLoader->loadIcon("bookmarks", KIconLoader::Small),
@@ -471,6 +524,120 @@
bool KrBookmarkHandler::eventFilter(QObject *obj, QEvent *ev)
{
+ if (obj->inherits("QMenu") && (ev->type() == QEvent::Show ||
+ ev->type() == QEvent::Close)) {
+ _setQuickSearchText("");
+ }
+
+ // Having it occur on keypress is consistent with other shortcuts,
+ // such as Ctrl+W and accelerator keys
+ if (ev->type() == QEvent::KeyPress && obj->inherits("QMenu")) {
+ QKeyEvent *kev = static_cast(ev);
+ QMenu *menu = static_cast(obj);
+ QList acts = menu->actions();
+ bool quickSearchStarted = false;
+ bool searchInSpecialItems = KConfigGroup(krConfig, "Look&Feel").readEntry("Search in special items", false);
+
+ if (kev->key() == Qt::Key_Left && kev->modifiers() == Qt::NoModifier) {
+ menu->close();
+ return true;
+ }
+
+ if (kev->modifiers() != Qt::NoModifier ||
+ kev->text().isEmpty() ||
+ kev->key() == Qt::Key_Delete ||
+ kev->key() == Qt::Key_Return ||
+ kev->key() == Qt::Key_Escape)
+
+ {
+ return QObject::eventFilter(obj, ev);
+ }
+
+ // update quick search text
+ if (kev->key() == Qt::Key_Backspace) {
+ auto newSearchText = _quickSearchText();
+ newSearchText.chop(1);
+ _setQuickSearchText(newSearchText);
+
+ if (_quickSearchText().length() == 0) {
+ return QObject::eventFilter(obj, ev);
+ }
+ } else {
+ quickSearchStarted = _quickSearchText().length() == 0;
+ _setQuickSearchText(_quickSearchText().append(kev->text()));
+ }
+
+ if (quickSearchStarted) {
+ qDebug() << "Bookmark search: started";
+ }
+
+ // match actions
+ QAction *matchedAction = nullptr;
+ int nMatches = 0;
+ for (auto act : acts) {
+ if (act->isSeparator() || act->text() == "") {
+ continue;
+ }
+
+ if (!searchInSpecialItems && _specialBookmarks.contains(act)) {
+ continue;
+ }
+
+ if (quickSearchStarted) {
+ // if the first key press is an accelerator key, let the accelerator handler process this event
+ if (act->text().contains('&' + kev->text(), Qt::CaseInsensitive)) {
+ qDebug() << "Bookmark search: hit accelerator key of" << act;
+ _setQuickSearchText("");
+ return QObject::eventFilter(obj, ev);
+ }
+
+ // strip accelerator keys from actions so they don't interfere with the search key press events
+ auto text = act->text();
+ _quickSearchOriginalActionTitles.insert(act, text);
+ act->setText(KLocalizedString::removeAcceleratorMarker(text));
+ }
+
+ // match prefix of the action text to the query
+ if (act->text().left(_quickSearchText().length()).compare(_quickSearchText(), Qt::CaseInsensitive) == 0) {
+ _highlightAction(act);
+ if (!matchedAction || matchedAction->menu()) {
+ // Can't highlight menus (see comment below), hopefully pick something we can
+ matchedAction = act;
+ }
+ nMatches++;
+ } else {
+ _highlightAction(act, false);
+ }
+ }
+
+ if (matchedAction) {
+ qDebug() << "Bookmark search: primary match =" << matchedAction->text() << ", number of matches =" << nMatches;
+ } else {
+ qDebug() << "Bookmark search: no matches";
+ }
+
+ // trigger the matched menu item or set an active item accordingly
+ if (nMatches == 1) {
+ _setQuickSearchText("");
+ if ((bool) matchedAction->menu()) {
+ menu->setActiveAction(matchedAction);
+ } else {
+ matchedAction->activate(QAction::Trigger);
+ }
+ } else if (nMatches > 1) {
+ // Because of a bug submenus cannot be highlighted
+ // https://bugreports.qt.io/browse/QTBUG-939
+ if (!matchedAction->menu()) {
+ menu->setActiveAction(matchedAction);
+ } else {
+ menu->setActiveAction(nullptr);
+ }
+ } else {
+ menu->setActiveAction(nullptr);
+ }
+ return true;
+ }
+
if (ev->type() == QEvent::MouseButtonRelease) {
switch (static_cast(ev)->button()) {
case Qt::RightButton:
@@ -507,6 +674,18 @@
return QObject::eventFilter(obj, ev);
}
+void KrBookmarkHandler::_resetActionTextAndHighlighting()
+{
+ for (QHash::const_iterator i = _quickSearchOriginalActionTitles.begin();
+ i != _quickSearchOriginalActionTitles.end(); ++i) {
+ QAction *action = i.key();
+ action->setText(i.value());
+ _highlightAction(action, false);
+ }
+
+ _quickSearchOriginalActionTitles.clear();
+}
+
#define POPULAR_URLS_ID 100100
#define TRASH_ID 100101
#define LAN_ID 100103
diff --git a/krusader/Konfigurator/kgpanel.cpp b/krusader/Konfigurator/kgpanel.cpp
--- a/krusader/Konfigurator/kgpanel.cpp
+++ b/krusader/Konfigurator/kgpanel.cpp
@@ -209,6 +209,22 @@
gridLayout->addLayout(hbox, 1, 1);
layout->addWidget(groupBox);
+// --------------------------------------------------------------------------------------------
+// ------------------------------- Bookmark search settings ----------------------------------
+// --------------------------------------------------------------------------------------------
+ groupBox = createFrame(i18n("Bookmark Search"), tab);
+ gridLayout = createGridLayout(groupBox);
+
+ KONFIGURATOR_CHECKBOX_PARAM bookmarkSearchSettings[] =
+ {
+ {"Look&Feel", "Always show search bar", true, i18n("Always show search bar"), false, i18n("Make bookmark search bar always visible") },
+ {"Look&Feel", "Search in special items", false, i18n("Search in special items"), false, i18n("Bookmark search is also applied to special items in bookmark menu like Trash, Popular URLs, Jump Back, etc.") },
+ };
+ KonfiguratorCheckBoxGroup *bookmarkSearchSettingsGroup = createCheckBoxGroup(2, 0, bookmarkSearchSettings,
+ 2 /*count*/, groupBox, PAGE_GENERAL);
+ gridLayout->addWidget(bookmarkSearchSettingsGroup, 1, 0, 1, 2);
+ layout->addWidget(groupBox);
+
// --------------------------------------------------------------------------------------------
// ------------------------------- Status/Totalsbar settings ----------------------------------
// --------------------------------------------------------------------------------------------
diff --git a/krusader/krusader.cpp b/krusader/krusader.cpp
--- a/krusader/krusader.cpp
+++ b/krusader/krusader.cpp
@@ -154,14 +154,15 @@
KrGlobal::mountMan = new KMountMan(this);
connect(KrGlobal::mountMan, SIGNAL(refreshPanel(QUrl)), SLOTS, SLOT(refresh(QUrl)));
+ // create popular URLs container
+ _popularUrls = new PopularUrls(this);
+
// create bookman
krBookMan = new KrBookmarkHandler(this);
// create job manager
krJobMan = new JobMan(this);
- _popularUrls = new PopularUrls(this);
-
// create the main view
MAIN_VIEW = new KrusaderView(this);