Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Main menu: implement search-on-write and improve ordering of search results #1698

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 48 additions & 13 deletions plugin-mainmenu/actionview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,59 @@
#include <QMouseEvent>
#include <QMimeData>
#include <QUrl>
#include <QSortFilterProxyModel>

#include <QtDebug>
#include <QString>

//==============================

void FilterProxyModel::setfilterString(const QString &str) {
filterStr_ = str;
invalidateFilter();

#ifdef HAVE_MENU_CACHE
#include <QSortFilterProxyModel>
#else
setFilterFixedString(str);
#endif
}

bool FilterProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
/* Prefer entries that start with the searched text
* then entries that contains it
* then sort the rest by length of their text
* */

QString leftText = sourceModel()->data(source_left).toString();
QString rightText = sourceModel()->data(source_right).toString();

if(leftText.startsWith(filterStr_, Qt::CaseInsensitive))
{
if(rightText.startsWith(filterStr_, Qt::CaseInsensitive))
return leftText.size() < rightText.size();
else return true;
}
else if(rightText.startsWith(filterStr_, Qt::CaseInsensitive))
return false;
else if(leftText.contains(filterStr_, Qt::CaseInsensitive))
{
if(rightText.contains(filterStr_, Qt::CaseInsensitive))
return leftText.size() < rightText.size();
else return true;
}
else if(rightText.contains(filterStr_, Qt::CaseInsensitive))
return false;
else return leftText.size() < rightText.size();
}


FilterProxyModel::FilterProxyModel(QObject* parent) :
QSortFilterProxyModel(parent) {
}

FilterProxyModel::~FilterProxyModel() = default;

#ifndef HAVE_MENU_CACHE
bool FilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const {
if (filterStr_.isEmpty())
return true;
Expand Down Expand Up @@ -142,11 +184,7 @@ namespace
ActionView::ActionView(QWidget * parent /*= nullptr*/)
: QListView(parent)
, mModel{new QStandardItemModel{this}}
#ifdef HAVE_MENU_CACHE
, mProxy{new QSortFilterProxyModel{this}}
#else
, mProxy{new FilterProxyModel{this}}
#endif
, mMaxItemsToShow(10)
{
setEditTriggers(QAbstractItemView::NoEditTriggers);
Expand Down Expand Up @@ -227,21 +265,18 @@ void ActionView::fillActions(QMenu * menu)

void ActionView::setFilter(QString const & filter)
{
#ifdef HAVE_MENU_CACHE
mProxy->setFilterFixedString(filter);
#else
mProxy->setfilerString(filter);
#endif
mProxy->setfilterString(filter);

const int count = mProxy->rowCount();
if (0 < count)
{
if (count > mMaxItemsToShow)
{
setCurrentIndex(mProxy->index(mMaxItemsToShow - 1, 0));
setCurrentIndex(mProxy->index(0, 0));
verticalScrollBar()->triggerAction(QScrollBar::SliderToMinimum);
} else
{
setCurrentIndex(mProxy->index(count - 1, 0));
setCurrentIndex(mProxy->index(0, 0));
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions plugin-mainmenu/actionview.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@
class QStandardItemModel;

//==============================
#ifdef HAVE_MENU_CACHE
class QSortFilterProxyModel;
#else

#include <QSortFilterProxyModel>
class FilterProxyModel : public QSortFilterProxyModel
{
Expand All @@ -45,18 +43,20 @@ class FilterProxyModel : public QSortFilterProxyModel
explicit FilterProxyModel(QObject* parent = nullptr);
virtual ~FilterProxyModel();

void setfilerString(const QString &str) {
filterStr_ = str;
invalidateFilter();
}
void setfilterString(const QString &str);

protected:
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;

#ifndef HAVE_MENU_CACHE
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
#endif


private:
QString filterStr_;
};
#endif

//==============================
class ActionView : public QListView
{
Expand Down
158 changes: 80 additions & 78 deletions plugin-mainmenu/lxqtmainmenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "lxqtmainmenuconfiguration.h"
#include "../panel/lxqtpanel.h"
#include "actionview.h"
#include "qnamespace.h"
#include <QAction>
#include <QTimer>
#include <QMessageBox>
Expand Down Expand Up @@ -102,13 +103,15 @@ LXQtMainMenu::LXQtMainMenu(const ILXQtPanelPluginStartupInfo &startupInfo):
connect(&mButton, &QToolButton::clicked, this, &LXQtMainMenu::showHideMenu);

mSearchView = new ActionView;
mSearchView->installEventFilter(this);
mSearchView->setVisible(false);
mSearchView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(mSearchView, &QAbstractItemView::activated, this, &LXQtMainMenu::showHideMenu);
connect(mSearchView, &ActionView::requestShowHideMenu, this, &LXQtMainMenu::showHideMenu);
connect(mSearchView, &QWidget::customContextMenuRequested, this, &LXQtMainMenu::onRequestingCustomMenu);
mSearchViewAction->setDefaultWidget(mSearchView);
mSearchEdit = new QLineEdit;
mSearchEdit->installEventFilter(this);
mSearchEdit->setClearButtonEnabled(true);
mSearchEdit->setPlaceholderText(LXQtMainMenu::tr("Search..."));
connect(mSearchEdit, &QLineEdit::textChanged, this, [this] (QString const &) {
Expand Down Expand Up @@ -273,6 +276,7 @@ void LXQtMainMenu::settingsChanged()
mFilterShow = settings()->value(QStringLiteral("filterShow"), true).toBool();
mFilterClear = settings()->value(QStringLiteral("filterClear"), false).toBool();
mFilterShowHideMenu = settings()->value(QStringLiteral("filterShowHideMenu"), true).toBool();
mWriteToSearch = settings()->value(QStringLiteral("writeToSearch"), false).toBool();
if (mMenu)
{
mSearchEdit->setVisible(mFilterMenu || mFilterShow);
Expand Down Expand Up @@ -383,7 +387,7 @@ void LXQtMainMenu::searchMenu()
************************************************/
void LXQtMainMenu::setSearchFocus(QAction *action)
{
if (mFilterMenu || mFilterShow)
if (!mWriteToSearch && (mFilterMenu || mFilterShow))
{
if(action == mSearchEditAction)
mSearchEdit->setFocus();
Expand All @@ -398,6 +402,7 @@ static void menuInstallEventFilter(QMenu * menu, QObject * watcher)
{
if (action->menu())
menuInstallEventFilter(action->menu(), watcher); // recursion
else action->installEventFilter(watcher);
}
menu->installEventFilter(watcher);
}
Expand Down Expand Up @@ -625,99 +630,96 @@ QDialog *LXQtMainMenu::configureDialog()
// functor used to match a QAction by prefix
struct MatchAction
{
MatchAction(QString key):key_(key) {}
MatchAction(QChar key): key_(key) {}
bool operator()(QAction* action) { return action->text().startsWith(key_, Qt::CaseInsensitive); }
QString key_;
QChar key_;
};

bool LXQtMainMenu::eventFilter(QObject *obj, QEvent *event)
{
if(obj == mButton.parentWidget())
QKeyEvent* keyEvent = nullptr;
if(event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
keyEvent = static_cast<QKeyEvent*>(event);

// Check if the close/open shortcut has been triggered
// close the menu if so
if(event->type() == QEvent::KeyRelease)
{
// the application is given a new QStyle
if(event->type() == QEvent::StyleChange)
static const auto key_meta = QMetaEnum::fromType<Qt::Key>();
// if our shortcut key is pressed while the menu is open, close the menu
QFlags<Qt::KeyboardModifier> mod = keyEvent->modifiers();
switch (keyEvent->key()) {
case Qt::Key_Alt:
mod &= ~Qt::AltModifier;
break;
case Qt::Key_Control:
mod &= ~Qt::ControlModifier;
break;
case Qt::Key_Shift:
mod &= ~Qt::ShiftModifier;
break;
case Qt::Key_Super_L:
case Qt::Key_Super_R:
mod &= ~Qt::MetaModifier;
break;
}
const QString press = QKeySequence{static_cast<int>(mod)}.toString() % QString::fromLatin1(key_meta.valueToKey(keyEvent->key())).remove(0, 4);
if (press == mShortcutSeq)
{
setMenuFontSize();
setButtonIcon();
mHideTimer.start();
mMenu->hide(); // close the app menu
return true;
}
}
else if(QMenu* menu = qobject_cast<QMenu*>(obj))

if(obj == mButton.parentWidget() && event->type() == QEvent::StyleChange)
{
setMenuFontSize();
setButtonIcon();
}

// Redirect arrow up and arrow down keys to the view that shows search results
// so one can browse among them. Only do this when mWriteToSearch = true
else if(mWriteToSearch && obj == mSearchEdit && event->type() == QEvent::KeyPress &&
(keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down))
{
qApp->sendEvent(mSearchView, keyEvent);
return true;
}

// Redirect text edition input to the search edit line, without consideration to focus
// Only do this when mWriteToSearch = true
else if(mWriteToSearch && obj != mSearchEdit && event->type() == QEvent::KeyPress &&
(keyEvent->key() == Qt::Key_Backspace ||
keyEvent->key() == Qt::Key_Space ||
(keyEvent->text().size() == 1 && keyEvent->text()[0].isLetterOrNumber())))
{
if(event->type() == QEvent::KeyRelease)
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
qApp->sendEvent(mSearchEdit, keyEvent);
return true;
}
else if (obj == mMenu)
{
if (event->type() == QEvent::Resize)
{
static const auto key_meta = QMetaEnum::fromType<Qt::Key>();
// if our shortcut key is pressed while the menu is open, close the menu
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
QFlags<Qt::KeyboardModifier> mod = keyEvent->modifiers();
switch (keyEvent->key()) {
case Qt::Key_Alt:
mod &= ~Qt::AltModifier;
break;
case Qt::Key_Control:
mod &= ~Qt::ControlModifier;
break;
case Qt::Key_Shift:
mod &= ~Qt::ShiftModifier;
break;
case Qt::Key_Super_L:
case Qt::Key_Super_R:
mod &= ~Qt::MetaModifier;
break;
}
const QString press = QKeySequence{static_cast<int>(mod)}.toString() % QString::fromLatin1(key_meta.valueToKey(keyEvent->key())).remove(0, 4);
if (press == mShortcutSeq)
{
mHideTimer.start();
mMenu->hide(); // close the app menu
return true;
}
else // go to the menu item which starts with the pressed key if there is an active action.
QResizeEvent * e = dynamic_cast<QResizeEvent *>(event);
if (e->oldSize().isValid() && e->oldSize() != e->size())
{
QString key = keyEvent->text();
if(key.isEmpty())
return false;
QAction* action = menu->activeAction();
if(action !=nullptr) {
QList<QAction*> actions = menu->actions();
QList<QAction*>::iterator it = std::find(actions.begin(), actions.end(), action);
it = std::find_if(it + 1, actions.end(), MatchAction(key));
if(it == actions.end())
it = std::find_if(actions.begin(), it, MatchAction(key));
if(it != actions.end())
menu->setActiveAction(*it);
}
mMenu->move(calculatePopupWindowPos(e->size()).topLeft());
}
}

if (obj == mMenu)
else if (event->type() == QEvent::KeyPress &&
keyEvent->key() == Qt::Key_Escape &&
!mSearchEdit->text().isEmpty())
{
if (event->type() == QEvent::Resize)
{
QResizeEvent * e = dynamic_cast<QResizeEvent *>(event);
if (e->oldSize().isValid() && e->oldSize() != e->size())
{
mMenu->move(calculatePopupWindowPos(e->size()).topLeft());
}
} else if (event->type() == QEvent::KeyPress)
{
QKeyEvent * e = dynamic_cast<QKeyEvent*>(event);
if (Qt::Key_Escape == e->key())
{
if (!mSearchEdit->text().isEmpty())
{
mSearchEdit->setText(QString{});
//filter out this to not close the menu
return true;
}
}
} else if (QEvent::ActionChanged == event->type()
|| QEvent::ActionAdded == event->type())
{
//filter this if we are performing heavy changes to reduce flicker
if (mHeavyMenuChanges)
return true;
}
mSearchEdit->clear();
//filter out this to not close the menu
return true;
}
else if (mHeavyMenuChanges &&
(QEvent::ActionChanged == event->type() ||
QEvent::ActionAdded == event->type()))
return true;
}
return false;
}
Expand Down
1 change: 1 addition & 0 deletions plugin-mainmenu/lxqtmainmenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class LXQtMainMenu : public QObject, public ILXQtPanelPlugin
bool mFilterClear; //!< search field should be cleared upon showing the menu
bool mFilterShowHideMenu; //!< while searching all (original) menu entries should be hidden
bool mHeavyMenuChanges; //!< flag for filtering some mMenu events while heavy changes are performed
bool mWriteToSearch; //!< write anywhere on the menu to search

#ifdef HAVE_MENU_CACHE
MenuCache* mMenuCache;
Expand Down
3 changes: 3 additions & 0 deletions plugin-mainmenu/lxqtmainmenuconfiguration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ LXQtMainMenuConfiguration::LXQtMainMenuConfiguration(PluginSettings *settings, G
if (!mLockSettingChanges)
this->settings().setValue(QStringLiteral("filterClear"), value);
});
connect(ui->writeToSearch, &QCheckBox::toggled, this, [this] (bool enabled) {
this->settings().setValue(QStringLiteral("writeToSearch"), enabled);
});
}

LXQtMainMenuConfiguration::~LXQtMainMenuConfiguration()
Expand Down
Loading