From 880c3aeb349d2990ae3a297f06d79d79f25b4402 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Thu, 1 Nov 2018 21:33:27 -0400 Subject: [PATCH] Add search help pop-up * Support ! modifier (same as '-') * Create reusable PopupHelpWidget as self-contained popup that can be positioned around a parent widget and will follow the movement and sizing of the window * Eliminated KEEPASSXC_MAIN_WINDOW macro and replaced with getMainWindow() function * Add tests to cover search help show/hide --- .../application/16x16/actions/system-help.png | Bin 0 -> 897 bytes .../application/22x22/actions/system-help.png | Bin 0 -> 1222 bytes .../application/32x32/actions/system-help.png | Bin 0 -> 2120 bytes src/CMakeLists.txt | 3 +- src/browser/BrowserService.cpp | 4 +- src/core/EntrySearcher.cpp | 3 +- src/gui/Application.cpp | 11 - src/gui/Application.h | 4 - src/gui/MainWindow.cpp | 5 + src/gui/MainWindow.h | 10 +- src/gui/SearchHelpWidget.ui | 458 ++++++++++++++++++ src/gui/SearchWidget.cpp | 29 +- src/gui/SearchWidget.h | 5 + src/gui/SearchWidget.ui | 5 + src/gui/masterkey/KeyFileEditWidget.cpp | 4 +- src/gui/widgets/PopupHelpWidget.cpp | 99 ++++ src/gui/widgets/PopupHelpWidget.h | 48 ++ src/keys/YkChallengeResponseKey.cpp | 6 +- src/main.cpp | 1 - tests/gui/TestGui.cpp | 12 + 20 files changed, 671 insertions(+), 36 deletions(-) create mode 100644 share/icons/application/16x16/actions/system-help.png create mode 100644 share/icons/application/22x22/actions/system-help.png create mode 100644 share/icons/application/32x32/actions/system-help.png create mode 100644 src/gui/SearchHelpWidget.ui create mode 100644 src/gui/widgets/PopupHelpWidget.cpp create mode 100644 src/gui/widgets/PopupHelpWidget.h diff --git a/share/icons/application/16x16/actions/system-help.png b/share/icons/application/16x16/actions/system-help.png new file mode 100644 index 0000000000000000000000000000000000000000..75ebaf7f5f73f57df9cc93159479a672b49176e4 GIT binary patch literal 897 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7Sc;uILpV4%IBGajIv5xj zI14-?iy0UgcY`pa)tkqU7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3 zkz!zA`seB57*cU7cJhC3;Y69^_sh4Jetu?_{;H^N*~?!_jknHph-yk@K9HZ_C?F#I zYoSAZN8L$%L6N^wOB80Zh<)>BX^jt$~M@R;fhqF|BE-#ZC(^aGd`XnCzb>Rst8Rye(c!3ZCkfSeyf}BbK%~-b$WVwavN{IO?_A(G1=SEr%LI; z;zAiygALJ^GG-Zq{l}H{rk{=s5C1O3B4z#L*|U8*nwpxkKd)K%^stayxaEa5ofls+ zIWD}cxb^<<@%MHUQsx|fn4!$8>)E_+-MUquK7Gpm=OEDk>tDm!6OCIWkMQnhEMNa* z(XBELw^}W>=Z_`kJQTjT^U$F~-#wWx?F|%Q*>(7B`-0QQ_QW3v322>G9$azhGq3vE znFhs5dlUs__4Jc&i+uY0`TT^>1}hy5X8J7Owr$&kZygbfw+nW22_!XGuR3OHEz*{L z-Egy#qTVHm+0P$tSlaY>hm>mL(WH}V)rXnRD{N6sv{g$Cp5UO|7d^fIap--29^I21 zd0ij!Ii56Bs$9t0DlWoYGsi z4KsGkXj;hrym0G;qZ@O?*35D{%PD3S%%#L~siMT(vHAS-XzddLha*+A)kIVe+ln8X z-6;QIUB0qoqu+teua`UK@jQO~n1A-{*-M|Ol$Di*UAcDc+vJEdK@t`Je(Kk){Wt%J z%-kODZ6tNYeWf=A70Q^3lQ1({JZe;%=t>h%&9 z7QWl)@F1h0dT^=n;RT-?N)Ip?mt{@N}kE?(JxeDj^#w|`Ij zlk=BhT4$K&+AaEwt_%y*lFFATF6v-eG~<(slIiUm59PNX>2Q(x!Ii?mz`)??>gTe~ HDWM4fB~YDe literal 0 HcmV?d00001 diff --git a/share/icons/application/22x22/actions/system-help.png b/share/icons/application/22x22/actions/system-help.png new file mode 100644 index 0000000000000000000000000000000000000000..86b64075fb5bada5d8814d598b6d9ccfb04e3748 GIT binary patch literal 1222 zcmeAS@N?(olHy`uVBq!ia0y~yU=Rag4mJh`h9g^YtQi;>Sc;uILpV4%IBGajIv5xj zI14-?iy0W0%Rrd1_G;4^1_lPn64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xh zq!<`joIPC}Ln>}AF+}fG4h+pM$%a0wp zY7$pIFmUUusC;&GmYB8RM?%x7*^|yp_fSDo#K7_gAbbc{*fT{XtZ?XATPwh}{H)KXle1>c zI=y-G=5pS{2Ib}DzLC?;7Fgu0Tf4UK^!bzDZQ71ARW#~EME*&LeR*=;ym{O9?c3)! z`|PtiEq6nceJu;tB>4Lt@KBkctr8HFaJ>Eb=|B}Zrrz5!5)wZa?cZPD6Y;M$`rv&3 z&HIgQZEJhKe?InDVBf!7X5aKxiq9B0n`UXYD|6-D|1bQ0e^`&XfAxa`izm;XJ<~2O zF23Hmop;jNqJXc-$;slCOU>u6N&3OL*CMue$Ew4EQy-kZfAq8}+nkN18eOyANblL) z`caZ|*ShY-ix*Fyd2o%%jL=i;Pe1=W*ZNeprq2IZv7ES*pzOhpM)Oyv0z$PGecQn< zzvbGa%IR%Odo5O-cBv^!7L>kSt)Z`9zjgce@1kju2lMRyR@n6EzxOJT(L8>uuP>CT zeOqZwNv-JgebWoRwo7h!)wgrRFG+&V6Z?#Ds6hw0+vRl$dfwS5l`lPTH_t4 zFfaem1x;3|?J0*_&aYj(aT`*oIA)v@USNw9(`STlbGA{6Vwz z`PZDUi)=}|HNo>`OXNYLaFO#-6X&nAYkYA|Zr|@pw(ZkfYwsTKf7Y;d-#)p7X`*{h zrg~K#>0IomFgJJ8T{Q)D?*m)rbg$eXTe*|Z;^oYw)-A8JxF^R;bG3>w<{aR9cB|ZK zZpHgG+~-}IJzp%0d2mzdK(xw(+1{oKt;xNQoZO3iHX65`+rBDc`)xx@ z%b)C<*E|r2*Wl*n{?51i(}mh|tid;(tL|zwMDss4wBFzJR97^HDKc_fik4mN_3oT% zrqicS3x`~r%nP%pmv*425>8p*U&H#wWH< z&*$go|DU#y;g4FT{0*k_Yyob0Z(h7uk)M&V7cm@S<$qk kOp*SP-do#dGJev3vcp~TrsMYl1_lNOPgg&ebxsLQ0QlWI#{d8T literal 0 HcmV?d00001 diff --git a/share/icons/application/32x32/actions/system-help.png b/share/icons/application/32x32/actions/system-help.png new file mode 100644 index 0000000000000000000000000000000000000000..8a9eb1a827fd0eaf1f5be508de78c32eed68a163 GIT binary patch literal 2120 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 literal 0 HcmV?d00001 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5cd974a98..f68afc8f6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -126,7 +126,6 @@ set(keepassx_SOURCES gui/UnlockDatabaseWidget.cpp gui/UnlockDatabaseDialog.cpp gui/WelcomeWidget.cpp - gui/widgets/ElidedLabel.cpp gui/csvImport/CsvImportWidget.cpp gui/csvImport/CsvImportWizard.cpp gui/csvImport/CsvParserModel.cpp @@ -154,6 +153,8 @@ set(keepassx_SOURCES gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp gui/settings/SettingsWidget.cpp + gui/widgets/ElidedLabel.cpp + gui/widgets/PopupHelpWidget.cpp gui/wizard/NewDatabaseWizard.cpp gui/wizard/NewDatabaseWizardPage.cpp gui/wizard/NewDatabaseWizardPageMetaData.cpp diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index a1315ad49..95b9008e6 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -88,7 +88,7 @@ bool BrowserService::openDatabase(bool triggerUnlock) } if (triggerUnlock) { - KEEPASSXC_MAIN_WINDOW->bringToFront(); + getMainWindow()->bringToFront(); m_bringToFrontRequested = true; } @@ -901,7 +901,7 @@ void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget) { if (dbWidget) { if (m_bringToFrontRequested) { - KEEPASSXC_MAIN_WINDOW->lower(); + getMainWindow()->lower(); m_bringToFrontRequested = false; } emit databaseUnlocked(); diff --git a/src/core/EntrySearcher.cpp b/src/core/EntrySearcher.cpp index 6614ab463..f582d62e7 100644 --- a/src/core/EntrySearcher.cpp +++ b/src/core/EntrySearcher.cpp @@ -96,6 +96,7 @@ bool EntrySearcher::searchEntryImpl(const QString& searchString, Entry* entry) found = !attachments.filter(term->regex).empty(); break; default: + // Terms without a specific field try to match title, username, url, and notes found = term->regex.match(entry->resolvePlaceholder(entry->title())).hasMatch() || term->regex.match(entry->resolvePlaceholder(entry->username())).hasMatch() || term->regex.match(entry->resolvePlaceholder(entry->url())).hasMatch() || @@ -139,7 +140,7 @@ QList > EntrySearcher::parseSearchTerm term->regex = Tools::convertToRegex(term->word, !mods.contains("*"), mods.contains("+"), m_caseSensitive); // Exclude modifier - term->exclude = mods.contains("-"); + term->exclude = mods.contains("-") || mods.contains("!"); // Determine the field to search QString field = result.captured(2); diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index b67f542c6..03cd0e551 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -49,7 +49,6 @@ namespace Application::Application(int& argc, char** argv) : QApplication(argc, argv) - , m_mainWindow(nullptr) #ifdef Q_OS_UNIX , m_unixSignalNotifier(nullptr) #endif @@ -143,16 +142,6 @@ Application::~Application() } } -QWidget* Application::mainWindow() const -{ - return m_mainWindow; -} - -void Application::setMainWindow(QWidget* mainWindow) -{ - m_mainWindow = mainWindow; -} - bool Application::event(QEvent* event) { // Handle Apple QFileOpenEvent from finder (double click on .kdbx file) diff --git a/src/gui/Application.h b/src/gui/Application.h index 7b1a77f60..9a3ef756b 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -37,9 +37,7 @@ class Application : public QApplication public: Application(int& argc, char** argv); - QWidget* mainWindow() const; ~Application() override; - void setMainWindow(QWidget* mainWindow); bool event(QEvent* event) override; bool isAlreadyRunning() const; @@ -60,8 +58,6 @@ private slots: void socketReadyRead(); private: - QWidget* m_mainWindow; - #if defined(Q_OS_UNIX) /** * Register Unix signals such as SIGINT and SIGTERM for clean shutdown. diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index d9f9a0557..67e453392 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -109,12 +109,17 @@ public: const QString MainWindow::BaseWindowTitle = "KeePassXC"; +MainWindow* g_MainWindow = nullptr; +MainWindow* getMainWindow() { return g_MainWindow; } + MainWindow::MainWindow() : m_ui(new Ui::MainWindow()) , m_trayIcon(nullptr) , m_appExitCalled(false) , m_appExiting(false) { + g_MainWindow = this; + m_ui->setupUi(this); #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && !defined(QT_NO_DBUS) diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index caf2a0c58..38b1c9308 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -148,8 +148,12 @@ private: bool m_appExiting; }; -#define KEEPASSXC_MAIN_WINDOW \ - (qobject_cast(qApp) ? qobject_cast(qobject_cast(qApp)->mainWindow()) \ - : nullptr) +/** + * Return instance of MainWindow created on app load + * non-gui instances will return nullptr + * + * @return MainWindow instance or nullptr + */ +MainWindow* getMainWindow(); #endif // KEEPASSX_MAINWINDOW_H diff --git a/src/gui/SearchHelpWidget.ui b/src/gui/SearchHelpWidget.ui new file mode 100644 index 000000000..daa3a851e --- /dev/null +++ b/src/gui/SearchHelpWidget.ui @@ -0,0 +1,458 @@ + + + SearchHelpWidget + + + + 0 + 0 + 334 + 249 + + + + Search Help + + + false + + + #SearchHelpWidget { background-color: #ffffff } + + + QFrame::Box + + + QFrame::Plain + + + + 6 + + + QLayout::SetDefaultConstraint + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + + 75 + true + + + + Search terms are as follows: [modifiers][field:]["]term["] + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Every search term must match (ie, logical AND) + + + + + + + + + + 50 + false + + + + Modifiers + + + + 8 + + + 8 + + + 9 + + + 10 + + + 9 + + + 9 + + + + + + 10 + 0 + + + + + 75 + true + + + + ! + + + false + + + Qt::AlignCenter + + + + + + + exclude term from results + + + + + + + match term exactly + + + + + + + + 10 + 0 + + + + + 75 + true + + + + * + + + Qt::AlignCenter + + + + + + + use regex in term + + + + + + + + 10 + 0 + + + + + 75 + true + + + + + + + + Qt::AlignCenter + + + + + + + + + + + 50 + false + + + + Fields + + + false + + + + 15 + + + 10 + + + 15 + + + 8 + + + 5 + + + + + username + + + + + + + password + + + + + + + title + + + + + + + url + + + + + + + notes + + + + + + + attribute + + + + + + + attachment + + + + + + + + + + + + + + Term Wildcards + + + + 8 + + + 8 + + + + + + 10 + 0 + + + + + 75 + true + + + + * + + + false + + + Qt::AlignCenter + + + + + + + + 10 + 0 + + + + + 75 + true + + + + ? + + + Qt::AlignCenter + + + + + + + + 10 + 0 + + + + + 75 + true + + + + | + + + Qt::AlignCenter + + + + + + + match anything + + + + + + + match one + + + + + + + logical OR + + + + + + + + + + Examples + + + + 8 + + + + + + 0 + 0 + + + + user:name1 url:google + + + + + + + + 0 + 0 + + + + user:"name1|name2" + + + + + + + + 0 + 0 + + + + +user:name1 *notes:"secret \d" + + + + + + + + + + + + + diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index 6441502b0..cde899576 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -18,6 +18,7 @@ #include "SearchWidget.h" #include "ui_SearchWidget.h" +#include "ui_SearchHelpWidget.h" #include #include @@ -26,6 +27,7 @@ #include "core/Config.h" #include "core/FilePath.h" +#include "gui/widgets/PopupHelpWidget.h" SearchWidget::SearchWidget(QWidget* parent) : QWidget(parent) @@ -35,11 +37,17 @@ SearchWidget::SearchWidget(QWidget* parent) { m_ui->setupUi(this); + m_helpWidget = new PopupHelpWidget(m_ui->searchEdit); + m_helpWidget->setOffset(QPoint(0,1)); + Ui::SearchHelpWidget helpUi; + helpUi.setupUi(m_helpWidget); + m_searchTimer->setSingleShot(true); m_clearSearchTimer->setSingleShot(true); connect(m_ui->searchEdit, SIGNAL(textChanged(QString)), SLOT(startSearchTimer())); connect(m_ui->clearIcon, SIGNAL(triggered(bool)), m_ui->searchEdit, SLOT(clear())); + connect(m_ui->helpIcon, SIGNAL(triggered()), SLOT(toggleHelp())); connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(startSearch())); connect(m_clearSearchTimer, SIGNAL(timeout()), m_ui->searchEdit, SLOT(clear())); connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear())); @@ -65,6 +73,9 @@ SearchWidget::SearchWidget(QWidget* parent) m_ui->searchIcon->setMenu(searchMenu); m_ui->searchEdit->addAction(m_ui->searchIcon, QLineEdit::LeadingPosition); + m_ui->helpIcon->setIcon(filePath()->icon("actions", "system-help")); + m_ui->searchEdit->addAction(m_ui->helpIcon, QLineEdit::TrailingPosition); + m_ui->clearIcon->setIcon(filePath()->icon("actions", "edit-clear-locationbar-rtl")); m_ui->clearIcon->setVisible(false); m_ui->searchEdit->addAction(m_ui->clearIcon, QLineEdit::TrailingPosition); @@ -86,13 +97,6 @@ bool SearchWidget::eventFilter(QObject* obj, QEvent* event) if (keyEvent->key() == Qt::Key_Escape) { emit escapePressed(); return true; - } else if (keyEvent->matches(QKeySequence::Copy)) { - // If Control+C is pressed in the search edit when no text - // is selected, copy the password of the current entry - if (!m_ui->searchEdit->hasSelectedText()) { - emit copyPressed(); - return true; - } } else if (keyEvent->matches(QKeySequence::MoveToNextLine)) { if (m_ui->searchEdit->cursorPosition() == m_ui->searchEdit->text().length()) { // If down is pressed at EOL, move the focus to the entry view @@ -111,7 +115,7 @@ bool SearchWidget::eventFilter(QObject* obj, QEvent* event) m_clearSearchTimer->stop(); } - return QObject::eventFilter(obj, event); + return QWidget::eventFilter(obj, event); } void SearchWidget::connectSignals(SignalMultiplexer& mx) @@ -188,3 +192,12 @@ void SearchWidget::searchFocus() m_ui->searchEdit->setFocus(); m_ui->searchEdit->selectAll(); } + +void SearchWidget::toggleHelp() +{ + if (m_helpWidget->isVisible()) { + m_helpWidget->hide(); + } else { + m_helpWidget->show(); + } +} diff --git a/src/gui/SearchWidget.h b/src/gui/SearchWidget.h index 6f4387004..43dd76430 100644 --- a/src/gui/SearchWidget.h +++ b/src/gui/SearchWidget.h @@ -30,6 +30,8 @@ namespace Ui class SearchWidget; } +class PopupHelpWidget; + class SearchWidget : public QWidget { Q_OBJECT @@ -45,6 +47,7 @@ public: void setLimitGroup(bool state); protected: + // Filter key presses in the search field bool eventFilter(QObject* obj, QEvent* event) override; signals: @@ -65,9 +68,11 @@ private slots: void updateCaseSensitive(); void updateLimitGroup(); void searchFocus(); + void toggleHelp(); private: const QScopedPointer m_ui; + PopupHelpWidget* m_helpWidget; QTimer* m_searchTimer; QTimer* m_clearSearchTimer; QAction* m_actionCaseSensitive; diff --git a/src/gui/SearchWidget.ui b/src/gui/SearchWidget.ui index 19b274a88..93fbbdee5 100644 --- a/src/gui/SearchWidget.ui +++ b/src/gui/SearchWidget.ui @@ -81,6 +81,11 @@ Clear + + + Search Help + + searchEdit diff --git a/src/gui/masterkey/KeyFileEditWidget.cpp b/src/gui/masterkey/KeyFileEditWidget.cpp index 14ac4879c..c694e2c5a 100644 --- a/src/gui/masterkey/KeyFileEditWidget.cpp +++ b/src/gui/masterkey/KeyFileEditWidget.cpp @@ -45,7 +45,7 @@ bool KeyFileEditWidget::addToCompositeKey(QSharedPointer key) } if (fileKey->type() != FileKey::Hashed) { - QMessageBox::warning(KEEPASSXC_MAIN_WINDOW, + QMessageBox::warning(getMainWindow(), tr("Legacy key file format"), tr("You are using a legacy key file format which may become\n" "unsupported in the future.\n\n" @@ -100,7 +100,7 @@ void KeyFileEditWidget::createKeyFile() QString errorMsg; bool created = FileKey::create(fileName, &errorMsg); if (!created) { - MessageBox::critical(KEEPASSXC_MAIN_WINDOW, tr("Error creating key file"), + MessageBox::critical(getMainWindow(), tr("Error creating key file"), tr("Unable to create key file: %1").arg(errorMsg), QMessageBox::Button::Ok); } else { m_compUi->keyFileCombo->setEditText(fileName); diff --git a/src/gui/widgets/PopupHelpWidget.cpp b/src/gui/widgets/PopupHelpWidget.cpp new file mode 100644 index 000000000..45e19e81e --- /dev/null +++ b/src/gui/widgets/PopupHelpWidget.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 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 "PopupHelpWidget.h" + +#include + +#include "gui/MainWindow.h" + +PopupHelpWidget::PopupHelpWidget(QWidget* parent) + : QFrame(parent) + , m_parentWindow(parent->window()) + , m_appWindow(getMainWindow()) + , m_offset({0, 0}) + , m_corner(Qt::BottomLeftCorner) +{ + Q_ASSERT(parent); + + setWindowFlags(Qt::FramelessWindowHint | Qt::Tool); + hide(); + + m_appWindow->installEventFilter(this); + parent->installEventFilter(this); +} + +PopupHelpWidget::~PopupHelpWidget() +{ + m_parentWindow->removeEventFilter(this); + parentWidget()->removeEventFilter(this); +} + +void PopupHelpWidget::setOffset(const QPoint& offset) +{ + m_offset = offset; + if (isVisible()) { + alignWithParent(); + } +} + +void PopupHelpWidget::setPosition(Qt::Corner corner) +{ + m_corner = corner; + if (isVisible()) { + alignWithParent(); + } +} + +bool PopupHelpWidget::eventFilter(QObject* obj, QEvent* event) +{ + if (obj == parentWidget() && event->type() == QEvent::FocusOut) { + hide(); + } else if (obj == m_appWindow && (event->type() == QEvent::Move || event->type() == QEvent::Resize)) { + if (isVisible()) { + alignWithParent(); + } + } + return QFrame::eventFilter(obj, event); +} + +void PopupHelpWidget::showEvent(QShowEvent* event) +{ + alignWithParent(); + QFrame::showEvent(event); +} + +void PopupHelpWidget::alignWithParent() +{ + QPoint pos; + switch (m_corner) { + case Qt::TopLeftCorner: + pos = parentWidget()->geometry().topLeft() + m_offset - QPoint(0, height()); + break; + case Qt::TopRightCorner: + pos = parentWidget()->geometry().topRight() + m_offset - QPoint(width(), height()); + break; + case Qt::BottomRightCorner: + pos = parentWidget()->geometry().bottomRight() + m_offset - QPoint(width(), 0); + break; + default: + pos = parentWidget()->geometry().bottomLeft() + m_offset; + break; + } + + move(m_parentWindow->mapToGlobal(pos)); +} \ No newline at end of file diff --git a/src/gui/widgets/PopupHelpWidget.h b/src/gui/widgets/PopupHelpWidget.h new file mode 100644 index 000000000..66dac2b30 --- /dev/null +++ b/src/gui/widgets/PopupHelpWidget.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 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_POPUPHELPWIDGET_H +#define KEEPASSXC_POPUPHELPWIDGET_H + +#include +#include + +class PopupHelpWidget : public QFrame +{ + Q_OBJECT +public: + explicit PopupHelpWidget(QWidget* parent); + ~PopupHelpWidget() override; + + void setOffset(const QPoint& offset); + void setPosition(Qt::Corner corner); + +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + void showEvent(QShowEvent* event) override; + +private: + void alignWithParent(); + QPointer m_parentWindow; + QPointer m_appWindow; + + QPoint m_offset; + Qt::Corner m_corner; +}; + + +#endif //KEEPASSXC_POPUPHELPWIDGET_H diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp index b2a40bd23..ade1b6324 100644 --- a/src/keys/YkChallengeResponseKey.cpp +++ b/src/keys/YkChallengeResponseKey.cpp @@ -37,9 +37,9 @@ YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) , m_slot(slot) , m_blocking(blocking) { - if (KEEPASSXC_MAIN_WINDOW) { - connect(this, SIGNAL(userInteractionRequired()), KEEPASSXC_MAIN_WINDOW, SLOT(showYubiKeyPopup())); - connect(this, SIGNAL(userConfirmed()), KEEPASSXC_MAIN_WINDOW, SLOT(hideYubiKeyPopup())); + if (getMainWindow()) { + connect(this, SIGNAL(userInteractionRequired()), getMainWindow(), SLOT(showYubiKeyPopup())); + connect(this, SIGNAL(userConfirmed()), getMainWindow(), SLOT(hideYubiKeyPopup())); } } diff --git a/src/main.cpp b/src/main.cpp index 9764c52d1..c811fe62c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -111,7 +111,6 @@ int main(int argc, char** argv) } MainWindow mainWindow; - app.setMainWindow(&mainWindow); QObject::connect(&app, SIGNAL(anotherInstanceStarted()), &mainWindow, SLOT(bringToFront())); QObject::connect(&app, SIGNAL(applicationActivated()), &mainWindow, SLOT(bringToFront())); QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString))); diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index f48d0777c..0adeabd95 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -802,10 +802,22 @@ void TestGui::testSearch() auto* clearButton = searchWidget->findChild("clearIcon"); QVERIFY(!clearButton->isVisible()); + auto* helpButton = searchWidget->findChild("helpIcon"); + auto* helpPanel = searchWidget->findChild("SearchHelpWidget"); + QVERIFY(helpButton->isVisible()); + QVERIFY(!helpPanel->isVisible()); + // Enter search QTest::mouseClick(searchTextEdit, Qt::LeftButton); QTRY_VERIFY(searchTextEdit->hasFocus()); QTRY_VERIFY(!clearButton->isVisible()); + // Show/Hide search help + helpButton->trigger(); + QTRY_VERIFY(helpPanel->isVisible()); + QTest::mouseClick(searchTextEdit, Qt::LeftButton); + QTRY_VERIFY(helpPanel->isVisible()); + helpButton->trigger(); + QTRY_VERIFY(!helpPanel->isVisible()); // Search for "ZZZ" QTest::keyClicks(searchTextEdit, "ZZZ"); QTRY_COMPARE(searchTextEdit->text(), QString("ZZZ"));