diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0ceccdd9a..a441b475b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -173,6 +173,7 @@ set(keepassx_SOURCES gui/osutils/OSUtilsBase.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 diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index bb0a6ec41..a91d15b3b 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1391,9 +1391,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); } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 7a53f144d..659ac8457 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -118,6 +118,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); @@ -1227,7 +1238,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; } } @@ -1248,13 +1262,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); } @@ -1266,6 +1280,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()) { diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 8a99d4432..a80346dfe 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -143,6 +143,7 @@ private slots: private slots: void updateTrayIcon(); void updateProgressBar(int percentage, QString message); + void focusSearchWidget(); private: static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 62d6cdd93..679578f1b 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -394,7 +394,7 @@ - + Qt::PreventContextMenu @@ -1043,6 +1043,11 @@
gui/WelcomeWidget.h
1 + + KPToolBar + QToolBar +
gui/widgets/KPToolBar.h
+
diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index 6ade47e2e..4cf53a9ac 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -48,8 +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())); + connect(m_clearSearchTimer, SIGNAL(timeout()), SLOT(clearSearch())); + connect(this, SIGNAL(escapePressed()), SLOT(clearSearch())); new QShortcut(QKeySequence::Find, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut); new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut); @@ -109,7 +109,7 @@ 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) { @@ -117,6 +117,7 @@ bool SearchWidget::eventFilter(QObject* obj, QEvent* event) 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(); @@ -133,10 +134,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())); } @@ -149,7 +150,7 @@ void SearchWidget::databaseChanged(DatabaseWidget* dbWidget) emit caseSensitiveChanged(m_actionCaseSensitive->isChecked()); emit limitGroupChanged(m_actionLimitGroup->isChecked()); } else { - m_ui->searchEdit->clear(); + clearSearch(); } } @@ -201,12 +202,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()) { diff --git a/src/gui/SearchWidget.h b/src/gui/SearchWidget.h index eefdff6ee..b2192f54d 100644 --- a/src/gui/SearchWidget.h +++ b/src/gui/SearchWidget.h @@ -52,16 +52,19 @@ protected: 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(); diff --git a/src/gui/widgets/KPToolBar.cpp b/src/gui/widgets/KPToolBar.cpp new file mode 100644 index 000000000..88b75b9b1 --- /dev/null +++ b/src/gui/widgets/KPToolBar.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 KeePassXC Team + * + * 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 . + */ + +#include "KPToolBar.h" + +#include +#include +#include + +KPToolBar::KPToolBar(const QString& title, QWidget* parent) + : QToolBar(title, parent) +{ + init(); +} + +KPToolBar::KPToolBar(QWidget* parent) + : QToolBar(parent) +{ + init(); +} + +void KPToolBar::init() +{ + m_expandButton = findChild("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); + } +} diff --git a/src/gui/widgets/KPToolBar.h b/src/gui/widgets/KPToolBar.h new file mode 100644 index 000000000..8883982bd --- /dev/null +++ b/src/gui/widgets/KPToolBar.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 KeePassXC Team + * + * 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 . + */ + +#ifndef KEEPASSXC_KPTOOLBAR_H +#define KEEPASSXC_KPTOOLBAR_H + +#include +#include +#include + +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 m_expandButton; +}; + +#endif // KEEPASSXC_KPTOOLBAR_H