Skip to content

Commit

Permalink
Show search bar when toolbar is hidden or overflow
Browse files Browse the repository at this point in the history
* Fix keepassxreboot#505 - always show the search bar when the search keyboard shortcut is pressed. If the toolbar is in overflow, the toolbar will be expanded automatically and search focused. If the toolbar is hidden it will be shown and expanded if necessary. When searching is canceled or the down arrow is pressed (to select the first entry) the toolbar will be set back to it's previous configuration.
  • Loading branch information
droidmonkey committed May 15, 2021
1 parent e1c8304 commit 8c61a73
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 17 deletions.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ set(keepassx_SOURCES
gui/osutils/ScreenLockListenerPrivate.cpp
gui/settings/SettingsWidget.cpp
gui/widgets/ElidedLabel.cpp
gui/widgets/KPToolBar.cpp
gui/widgets/PopupHelpWidget.cpp
gui/wizard/NewDatabaseWizard.cpp
gui/wizard/NewDatabaseWizardPage.cpp
Expand Down
3 changes: 1 addition & 2 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1401,9 +1401,8 @@ void DatabaseWidget::onGroupChanged()
// Intercept group changes if in search mode
if (isSearchActive() && m_searchLimitGroup) {
search(m_lastSearchText);
} else if (isSearchActive()) {
endSearch();
} else {
endSearch();
m_entryView->displayGroup(group);
}

Expand Down
29 changes: 26 additions & 3 deletions src/gui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,17 @@ MainWindow::MainWindow()
m_searchWidgetAction = m_ui->toolBar->addWidget(m_searchWidget);
m_searchWidgetAction->setEnabled(false);

new QShortcut(QKeySequence::Find, this, SLOT(focusSearchWidget()));

connect(m_searchWidget, &SearchWidget::searchCanceled, this, [this] {
m_ui->toolBar->setExpanded(false);
m_ui->toolBar->setVisible(!config()->get(Config::GUI_HideToolbar).toBool());
});
connect(m_searchWidget, &SearchWidget::lostFocus, this, [this] {
m_ui->toolBar->setExpanded(false);
m_ui->toolBar->setVisible(!config()->get(Config::GUI_HideToolbar).toBool());
});

m_countDefaultAttributes = m_ui->menuEntryCopyAttribute->actions().size();

m_entryContextMenu = new QMenu(this);
Expand Down Expand Up @@ -1273,7 +1284,10 @@ void MainWindow::keyPressEvent(QKeyEvent* event)
dbWidget->focusOnEntries(true);
return;
} else if (event->key() == Qt::Key_F3) {
m_searchWidget->searchFocus();
focusSearchWidget();
return;
} else if (event->key() == Qt::Key_Escape && dbWidget->isSearchActive()) {
m_searchWidget->clearSearch();
return;
}
}
Expand All @@ -1294,13 +1308,13 @@ bool MainWindow::focusNextPrevChild(bool next)
} else if (m_ui->tabWidget->hasFocus()) {
dbWidget->setFocus(Qt::TabFocusReason);
} else {
m_searchWidget->setFocus(Qt::TabFocusReason);
focusSearchWidget();
}
} else {
if (m_searchWidget->hasFocus()) {
dbWidget->setFocus(Qt::BacktabFocusReason);
} else if (m_ui->tabWidget->hasFocus()) {
m_searchWidget->setFocus(Qt::BacktabFocusReason);
focusSearchWidget();
} else {
m_ui->tabWidget->setFocus(Qt::BacktabFocusReason);
}
Expand All @@ -1312,6 +1326,15 @@ bool MainWindow::focusNextPrevChild(bool next)
return QMainWindow::focusNextPrevChild(next);
}

void MainWindow::focusSearchWidget()
{
if (m_searchWidgetAction->isEnabled()) {
m_ui->toolBar->setVisible(true);
m_ui->toolBar->setExpanded(true);
m_searchWidget->focusSearch();
}
}

void MainWindow::saveWindowInformation()
{
if (isVisible()) {
Expand Down
1 change: 1 addition & 0 deletions src/gui/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ private slots:
void agentEnabled(bool enabled);
void updateTrayIcon();
void updateProgressBar(int percentage, QString message);
void focusSearchWidget();

private:
static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0);
Expand Down
7 changes: 6 additions & 1 deletion src/gui/MainWindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@
<addaction name="menuView"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QToolBar" name="toolBar">
<widget class="KPToolBar" name="toolBar">
<property name="contextMenuPolicy">
<enum>Qt::PreventContextMenu</enum>
</property>
Expand Down Expand Up @@ -1077,6 +1077,11 @@
<header>gui/WelcomeWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KPToolBar</class>
<extends>QToolBar</extends>
<header>gui/widgets/KPToolBar.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
Expand Down
24 changes: 14 additions & 10 deletions src/gui/SearchWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,8 @@ SearchWidget::SearchWidget(QWidget* parent)
connect(m_ui->helpIcon, SIGNAL(triggered()), SLOT(toggleHelp()));
connect(m_ui->searchIcon, SIGNAL(triggered()), SLOT(showSearchMenu()));
connect(m_searchTimer, SIGNAL(timeout()), SLOT(startSearch()));
connect(m_clearSearchTimer, SIGNAL(timeout()), m_ui->searchEdit, SLOT(clear()));
connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear()));

new QShortcut(QKeySequence::Find, this, SLOT(searchFocus()));
new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()));
connect(m_clearSearchTimer, SIGNAL(timeout()), SLOT(clearSearch()));
connect(this, SIGNAL(escapePressed()), SLOT(clearSearch()));

m_ui->searchEdit->setPlaceholderText(tr("Search (%1)…", "Search placeholder text, %1 is the keyboard shortcut")
.arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText)));
Expand Down Expand Up @@ -109,14 +106,15 @@ bool SearchWidget::eventFilter(QObject* obj, QEvent* event)
return true;
}
}
} else if (event->type() == QEvent::FocusOut && !m_ui->searchEdit->text().isEmpty()) {
} else if (event->type() == QEvent::FocusOut) {
if (config()->get(Config::Security_ClearSearch).toBool()) {
int timeout = config()->get(Config::Security_ClearSearchTimeout).toInt();
if (timeout > 0) {
// Auto-clear search after set timeout (5 minutes by default)
m_clearSearchTimer->start(timeout * 60000); // 60 sec * 1000 ms
}
}
emit lostFocus();
} else if (event->type() == QEvent::FocusIn) {
// Never clear the search if we are using it
m_clearSearchTimer->stop();
Expand All @@ -133,10 +131,10 @@ void SearchWidget::connectSignals(SignalMultiplexer& mx)
mx.connect(this, SIGNAL(limitGroupChanged(bool)), SLOT(setSearchLimitGroup(bool)));
mx.connect(this, SIGNAL(copyPressed()), SLOT(copyPassword()));
mx.connect(this, SIGNAL(downPressed()), SLOT(focusOnEntries()));
mx.connect(SIGNAL(clearSearch()), m_ui->searchEdit, SLOT(clear()));
mx.connect(SIGNAL(clearSearch()), this, SLOT(clearSearch()));
mx.connect(SIGNAL(entrySelectionChanged()), this, SLOT(resetSearchClearTimer()));
mx.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(resetSearchClearTimer()));
mx.connect(SIGNAL(databaseUnlocked()), this, SLOT(searchFocus()));
mx.connect(SIGNAL(databaseUnlocked()), this, SLOT(focusSearch()));
mx.connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(switchToEntryEdit()));
}

Expand All @@ -149,7 +147,7 @@ void SearchWidget::databaseChanged(DatabaseWidget* dbWidget)
emit caseSensitiveChanged(m_actionCaseSensitive->isChecked());
emit limitGroupChanged(m_actionLimitGroup->isChecked());
} else {
m_ui->searchEdit->clear();
clearSearch();
}
}

Expand Down Expand Up @@ -201,12 +199,18 @@ void SearchWidget::setLimitGroup(bool state)
updateLimitGroup();
}

void SearchWidget::searchFocus()
void SearchWidget::focusSearch()
{
m_ui->searchEdit->setFocus();
m_ui->searchEdit->selectAll();
}

void SearchWidget::clearSearch()
{
m_ui->searchEdit->clear();
emit searchCanceled();
}

void SearchWidget::toggleHelp()
{
if (m_helpWidget->isVisible()) {
Expand Down
5 changes: 4 additions & 1 deletion src/gui/SearchWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,19 @@ class SearchWidget : public QWidget

signals:
void search(const QString& text);
void searchCanceled();
void caseSensitiveChanged(bool state);
void limitGroupChanged(bool state);
void escapePressed();
void copyPressed();
void downPressed();
void enterPressed();
void lostFocus();

public slots:
void databaseChanged(DatabaseWidget* dbWidget = nullptr);
void searchFocus();
void focusSearch();
void clearSearch();

private slots:
void startSearchTimer();
Expand Down
75 changes: 75 additions & 0 deletions src/gui/widgets/KPToolBar.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2021 KeePassXC Team <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "KPToolBar.h"

#include <QAbstractButton>
#include <QEvent>
#include <QLayout>

KPToolBar::KPToolBar(const QString& title, QWidget* parent)
: QToolBar(title, parent)
{
init();
}

KPToolBar::KPToolBar(QWidget* parent)
: QToolBar(parent)
{
init();
}

void KPToolBar::init()
{
m_expandButton = findChild<QAbstractButton*>("qt_toolbar_ext_button");
m_expandTimer.setSingleShot(true);
connect(&m_expandTimer, &QTimer::timeout, this, [this] { setExpanded(false); });
}

bool KPToolBar::isExpanded()
{
return !canExpand() || (canExpand() && m_expandButton->isChecked());
}

bool KPToolBar::canExpand()
{
return m_expandButton && m_expandButton->isVisible();
}

void KPToolBar::setExpanded(bool state)
{
if (canExpand() && !QMetaObject::invokeMethod(layout(), "setExpanded", Q_ARG(bool, state))) {
qWarning("Toolbar: Cannot invoke setExpanded!");
}
}

bool KPToolBar::event(QEvent* event)
{
// Override events handled by the base class for better UX when using an expandable toolbar.
switch (event->type()) {
case QEvent::Leave:
// Hide the toolbar after 2 seconds of mouse exit
m_expandTimer.start(2000);
return true;
case QEvent::Enter:
// Mouse came back in, stop hiding timer
m_expandTimer.stop();
return true;
default:
return QToolBar::event(event);
}
}
52 changes: 52 additions & 0 deletions src/gui/widgets/KPToolBar.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2021 KeePassXC Team <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef KEEPASSXC_KPTOOLBAR_H
#define KEEPASSXC_KPTOOLBAR_H

#include <QPointer>
#include <QTimer>
#include <QToolBar>

class QAbstractButton;

class KPToolBar : public QToolBar
{
Q_OBJECT

public:
explicit KPToolBar(const QString& title, QWidget* parent = nullptr);
explicit KPToolBar(QWidget* parent = nullptr);
~KPToolBar() override = default;

bool isExpanded();
bool canExpand();

public slots:
void setExpanded(bool state);

protected:
bool event(QEvent* event) override;

private:
void init();

QTimer m_expandTimer;
QPointer<QAbstractButton> m_expandButton;
};

#endif // KEEPASSXC_KPTOOLBAR_H

0 comments on commit 8c61a73

Please sign in to comment.