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 zcmV-{1AhF8P)ll^X4{Z>T-^z>G~pp+RTU*DH>_u zACN&16chTPK!1pS$uAKU{TieonFJR0O9X{qP?pLdjm$y~@e9tTuJ-NJx!t+Do?SZB z7rZ$h?mq9&`+1(vQL-%KAWcn8wAE^L7>&j%mSuB7q0k7=^Pikf=Nb+|4ECv~r>D{H z_iu`#C~s_R$fQtFBodLw#>T$3wY6FHexFRx-rlY*E-oIi+wEoHa2S(IK}>og;G=OI zH88MSIUF`<5s$|sE|<$$S6BBM1R6W!=H|XEDJdzN^Q@ue!xBbUInahI==5m_(CK*E z@59IMK9FRb!{KNn4J}E7uCA`C^78U){y-3q`u>2;$bqyO#0|Rvcdz84+I9#=eJTd$ z;~1Rb6VbWC!ov4S1Jb=w5Cpoz?T4Vt#=Kv|R8WPMcT;%M^9?7hnJ{xAyde=CE-zx+ z6!P=)&ye#lsX#E9OsBTCw&3=NSfjTQiwY28fIF(jJv$VJXV)>aA|Wo)7@Ozeu%&|I zxEdva#bU`Od&2_HAhac6JtkpOkU%4jTbGaHRo_nxEy)OOituj;?ema9)$a}avK*eJ>X4h59 zT0xdjR*(isk`j#v6oZwOm0^uWgWG4dV8jSWQ{-haXt!(3(4CCYD51X5t zrQ{@bc6L_O*4FluSjeh>ey#qW#2lTBOSUxJsWd|*#naQ%U#hFC&w;ROkTfYR zE$ucK3??$NP^na)X-C2g7Z-Vt$Ftnq+xwRMZ2bV1iB?Iiv@Zcg0H7vfccdYrhyK1{*`rB9wAjfeS6r z-haLReodR)NO|;|d~>+>96smVbH2Y*!8A?$A9COT34&k)P)c@_#Z=3Z$(t#MXkiIt z5U)5B)CHivySw|PmX;P@6{)18qzWWSlKy6mkB`p{4h~KMctDJ;eGmqx%}nyinwpx+ zv$L~z4Z|>ZcXv%$md$WDY_6@Xam&xi_dJHj0(gNeSXumFUFpf5o}RY8zP?VI&E|e8 zM=>Jw1Jm z_w&Z%5WfCx15IbDA?5%Hf|@CZBMNTc{}b=OR*W}Kd*E<5u3aP@~7uUZQkyRitj~T~gl5{1N$G@H!7^7A&sve6sXl?| z&K|z|c?P$ag?-(}kLG~2zP=t{mKD6oWlDL(iK9*+oo%w+CLkwEz_gr;sjwY+xehq# znPP>s>M9G9UmFUA76EL)3g+5(I-Pj$tP9UXgGuP2T;SOvz*{8Xn@ctL>f(!Ns0?W(G6I9ld09O86(uGvgZ_}*T6h(30nU7%LR~g$*H~piI zgs#C<6;MbJ-Bsbzws7@AB^ta195uPMwe^bM@Bfi+I&0j(F9<%x7fM%GS7(#AK&miTYt=@#&Tzj=0Y kmc;^DPgzT~hM58KFXXsKDcM2yZ~y=R07*qoM6N<$g81D##{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 zcmV-O2)Fl%P)MvdBPMi;O~2rGiiMI;q3v1v_PDPFi(2|^bZVYx3ZW`)A;0t*WZqJtMC zB_hky^Gx?+J~rLL^iR*sGv9vaJLi3$^WM(8-vBTsi1AHWIu z98VPy5%G3iUEK|*(>Wv*qQAc%eSLlC?d=@_&hgv8z`zjKb07Cp&X@_vPpwoc{Ypzq z^9Bb8osS+p!o!CTaqr$e+`fGqU0q#pI2`Ec=pgu;bNt3NTuU-e?x!42*6^f!LU3^K zvKBFfa`0q5Jw2cTyWI{kjppWN@M*Kz2>Be>a4q+6FXvk*k8&x0Gy>zf_|Ba>J;Icr zX}i0-L7`NTQ_+Y6=M31CT!NJ`I=rFQVMVkS!SRI%KUSImntDY0^x+8S9cO5P8Lpgu-qd&TU6j zh8Z!J1evTapt*Ddk?AHJyktgnb`w;8sl%I*S8=hZ4y~=NpbW~QOub&8M?I1&Mwp&a z%YdWbXgadbEHz`zi8}1R(usptZzDXb9p{TJ=>O_5V zH6rA+3Be!NV5#adEX{2o0m`K88)8u3FacIB77yk*(}(6_5N$nU!1}Xo*qq*tkiT|e z(>aHDZ-wg5wWw+A#Ld~ zu~<5(S0XTz7YfsaC6z1#4pqP}z72kf9q`q(K@r;mpBO8ijg|;}0dG|~TD$H;8VMI` z;I$VT%^yf0c(PLO;j24OvjUlg5%w+0;SwQLjYc8Qy9nz(w?x--p$hm)P!+ zz>EkZ;xcYR8b)&`Ccl@5r?+1d1au<_Y|O@lx6gu#DVsX(-n~0Wz3vu}2#jih=_&&Z z<}OHMO{@;z*-?zA-!GOEkXt}bKxP4149H!u{}KJhR^jf0Y*V_vFBT&*%8wuS+mV5TF&l zMqqs=RA)*?1fjuTaAkq44#?9mucir#C==$!+d%?KK|pc13Ccqa@I6!yKlM#4P*-Db zT0LHgt-`Zm<(Rpz3{!WPVDgS4Jo#S1zX^m~!MLBLA-BRjA`R2i(_M8yRtee3NQH>L z+I-3YWsC)Lj|f*BvEfIFRxC=kU~x(V0+VXd+0*B`2Y_FkG+<$DCHzGJQiSUk*)-#1c(479J|coG_6M?_Pz^yXVo;=AdG^`DJ&3+il1;O1g~5IJ-ia{h$#))R+-? zq6y1CvEp?>U|B*1?||W(aD9>vOX91rSW|@;)fMpBTY!n1Gmu%_K;I9aUNkyRl}hEh z4L$S&vg?>vsUXpeMWVj{;P6enoFa()t^rGuO?W9h7rRrncy)g^7Dkuh#h6Mw_hA`k z?bc%6&P-(L8yWc{u4A1}C+i11bVTgo^~QcT-ZEQ(Psey-7?S(u$Cmp3HB#j=iQl%9}1oV+EIOvvnrOs~bdgkmgE#|AzqvQ1Jo(tW zVmS$KZ|@g`&k7$rc;NgW+H&T*Ge1ASkaFb8N3(#Jz%&7c05`r76ciLCnn_w?Xe@tB znCC@BMJ=mWua4q=mmGyl?o*x!NGE^N^+(g!wQ;GyTLK~e{{A1S)#?JRR@))kx?T}r zkA!80_!JFBuQ+W-baZq9*Ks}falcEBQsBERKAs4;llhjwx5-#wvcL=%sRaV`=L&%! yfwcmw1YQ>i5bzh6E%1GTi8Q^tQg;IXSNsdZr>XzdpmVeU0000bringToFront(); + 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 6614ab46..f582d62e 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 b67f542c..03cd0e55 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 7b1a77f6..9a3ef756 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 d9f9a055..67e45339 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 caf2a0c5..38b1c930 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 00000000..daa3a851 --- /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 6441502b..cde89957 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 6f438700..43dd7643 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 19b274a8..93fbbdee 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 14ac4879..c694e2c5 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 00000000..45e19e81 --- /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 00000000..66dac2b3 --- /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 b2a40bd2..ade1b632 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 9764c52d..c811fe62 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 f48d0777..0adeabd9 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"));