From 13983d0e51aa31fce58bc92e7a159ffccd5e1b62 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Wed, 2 Nov 2016 21:01:02 -0400 Subject: [PATCH] Make search always visible (PR #67) * Moved search bar to toolbar and consolidated search options into dropdown list * Updated GUI tests to be atomic and rewrote search tests * Searches are saved between databases * Search is cleared when all databases are closed * Implemented global search shortcut (CTRL+F) and a notification bar when in search mode --- CMakeLists.txt | 2 +- src/CMakeLists.txt | 1 + src/autotype/AutoType.cpp | 3 + src/gui/DatabaseWidget.cpp | 211 ++++++++---------------- src/gui/DatabaseWidget.h | 30 ++-- src/gui/DatabaseWidgetStateSync.cpp | 11 +- src/gui/MainWindow.cpp | 33 ++-- src/gui/MainWindow.h | 1 + src/gui/MainWindow.ui | 81 ++++----- src/gui/SearchWidget.cpp | 124 ++++++++++++++ src/gui/SearchWidget.h | 77 +++++++++ src/gui/SearchWidget.ui | 81 +-------- src/gui/group/GroupView.cpp | 5 +- tests/gui/TestGui.cpp | 246 ++++++++++++++++------------ tests/gui/TestGui.h | 13 +- 15 files changed, 507 insertions(+), 412 deletions(-) create mode 100644 src/gui/SearchWidget.cpp create mode 100644 src/gui/SearchWidget.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 82ef59f394..96a9c425de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,7 @@ macro(add_gcc_compiler_flags FLAGS) add_gcc_compiler_cflags("${FLAGS}") endmacro(add_gcc_compiler_flags) -add_definitions(-DQT_NO_KEYWORDS -DQT_NO_EXCEPTIONS -DQT_STRICT_ITERATORS -DQT_NO_CAST_TO_ASCII) +add_definitions(-DQT_NO_EXCEPTIONS -DQT_STRICT_ITERATORS -DQT_NO_CAST_TO_ASCII) add_gcc_compiler_flags("-fno-common -fstack-protector --param=ssp-buffer-size=4") add_gcc_compiler_flags("-Wall -Wextra -Wundef -Wpointer-arith -Wno-long-long") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c7d8cbf3b5..5f7740b612 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -102,6 +102,7 @@ set(keepassx_SOURCES gui/PasswordGeneratorWidget.cpp gui/PasswordComboBox.cpp gui/SettingsWidget.cpp + gui/SearchWidget.cpp gui/SortFilterHideProxyModel.cpp gui/UnlockDatabaseWidget.cpp gui/WelcomeWidget.cpp diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 8032c27d2b..6a727cf1c4 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -187,6 +187,9 @@ void AutoType::performGlobalAutoType(const QList& dbList) QList entryList; QHash sequenceHash; + // TODO: Check if there are any active databases here, if not do nothing + // TODO: Check if all databases are locked, if so ask to unlock them + for (Database* db : dbList) { const QList dbEntries = db->rootGroup()->entriesRecursive(); for (Entry* entry : dbEntries) { diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 0ede41926f..d97fc2f852 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -16,7 +16,6 @@ */ #include "DatabaseWidget.h" -#include "ui_SearchWidget.h" #include #include @@ -25,8 +24,10 @@ #include #include #include -#include +#include #include +#include +#include #include "autotype/AutoType.h" #include "core/Config.h" @@ -50,24 +51,16 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) : QStackedWidget(parent) , m_db(db) - , m_searchUi(new Ui::SearchWidget()) - , m_searchWidget(new QWidget()) , m_newGroup(nullptr) , m_newEntry(nullptr) , m_newParent(nullptr) { - m_searchUi->setupUi(m_searchWidget); - - m_searchTimer = new QTimer(this); - m_searchTimer->setSingleShot(true); - m_mainWidget = new QWidget(this); QLayout* layout = new QHBoxLayout(m_mainWidget); m_splitter = new QSplitter(m_mainWidget); m_splitter->setChildrenCollapsible(false); QWidget* rightHandSideWidget = new QWidget(m_splitter); - m_searchWidget->setParent(rightHandSideWidget); m_groupView = new GroupView(db, m_splitter); m_groupView->setObjectName("groupView"); @@ -82,25 +75,24 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_entryView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(emitEntryContextMenuRequested(QPoint))); - QAction* closeAction = new QAction(m_searchWidget); - QIcon closeIcon = filePath()->icon("actions", "dialog-close"); - closeAction->setIcon(closeIcon); - m_searchUi->closeSearchButton->setDefaultAction(closeAction); - m_searchUi->closeSearchButton->setShortcut(Qt::Key_Escape); - m_searchWidget->hide(); - m_searchUi->caseSensitiveCheckBox->setVisible(false); - m_searchUi->searchEdit->installEventFilter(this); + // Add a notification for when we are searching + m_searchingLabel = new QLabel(); + m_searchingLabel->setText(tr("Searching...")); + m_searchingLabel->setAlignment(Qt::AlignCenter); + m_searchingLabel->setStyleSheet("background-color: rgb(255, 253, 160);" + "border: 2px solid rgb(190, 190, 190);" + "border-radius: 5px;"); QVBoxLayout* vLayout = new QVBoxLayout(rightHandSideWidget); vLayout->setMargin(0); - vLayout->addWidget(m_searchWidget); + vLayout->addWidget(m_searchingLabel); vLayout->addWidget(m_entryView); + m_searchingLabel->setVisible(false); + rightHandSideWidget->setLayout(vLayout); - setTabOrder(m_searchUi->searchRootRadioButton, m_entryView); setTabOrder(m_entryView, m_groupView); - setTabOrder(m_groupView, m_searchWidget); m_splitter->addWidget(m_groupView); m_splitter->addWidget(rightHandSideWidget); @@ -158,13 +150,9 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool))); connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool))); connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged())); - connect(m_searchUi->searchEdit, SIGNAL(textChanged(QString)), this, SLOT(startSearchTimer())); - connect(m_searchUi->caseSensitiveCheckBox, SIGNAL(toggled(bool)), this, SLOT(startSearch())); - connect(m_searchUi->searchCurrentRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch())); - connect(m_searchUi->searchRootRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch())); - connect(m_searchUi->searchEdit, SIGNAL(returnPressed()), m_entryView, SLOT(setFocus())); - connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(search())); - connect(closeAction, SIGNAL(triggered()), this, SLOT(closeSearch())); + + m_searchCaseSensitive = false; + m_searchCurrentGroup = false; setCurrentWidget(m_mainWidget); } @@ -764,118 +752,82 @@ void DatabaseWidget::switchToImportKeepass1(const QString& fileName) setCurrentWidget(m_keepass1OpenWidget); } -void DatabaseWidget::openSearch() +void DatabaseWidget::search(const QString& searchtext) { - if (isInSearchMode()) { - m_searchUi->searchEdit->selectAll(); - - if (!m_searchUi->searchEdit->hasFocus()) { - m_searchUi->searchEdit->setFocus(); - // make sure the search action is checked again - emitCurrentModeChanged(); - } - } - else { - showSearch(); + if (searchtext.isEmpty()) + { + endSearch(); + return; } -} - -void DatabaseWidget::closeSearch() -{ - Q_ASSERT(m_lastGroup); - Q_EMIT listModeAboutToActivate(); - - m_groupView->setCurrentGroup(m_lastGroup); - m_searchTimer->stop(); - - Q_EMIT listModeActivated(); -} - -void DatabaseWidget::showSearch() -{ Q_EMIT searchModeAboutToActivate(); - m_searchUi->searchEdit->blockSignals(true); - m_searchUi->searchEdit->clear(); - m_searchUi->searchEdit->blockSignals(false); + if (!isInSearchMode()) + { + m_lastGroup = m_groupView->currentGroup(); + Q_ASSERT(m_lastGroup); + } - m_searchUi->searchCurrentRadioButton->blockSignals(true); - m_searchUi->searchRootRadioButton->blockSignals(true); - m_searchUi->searchRootRadioButton->setChecked(true); - m_searchUi->searchCurrentRadioButton->blockSignals(false); - m_searchUi->searchRootRadioButton->blockSignals(false); + Group* searchGroup = m_searchCurrentGroup ? m_lastGroup : m_db->rootGroup(); + Qt::CaseSensitivity sensitivity = m_searchCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; - m_lastGroup = m_groupView->currentGroup(); + QList searchResult = EntrySearcher().search(searchtext, searchGroup, sensitivity); - Q_ASSERT(m_lastGroup); + m_entryView->setEntryList(searchResult); + m_lastSearchText = searchtext; - if (m_lastGroup == m_db->rootGroup()) { - m_searchUi->optionsWidget->hide(); - m_searchUi->searchCurrentRadioButton->hide(); - m_searchUi->searchRootRadioButton->hide(); + // Display a label detailing our search results + if (searchResult.size() > 0) { + m_searchingLabel->setText(tr("Search Results (%1)").arg(searchResult.size())); } else { - m_searchUi->optionsWidget->show(); - m_searchUi->searchCurrentRadioButton->show(); - m_searchUi->searchRootRadioButton->show(); - m_searchUi->searchCurrentRadioButton->setText(tr("Current group") - .append(" (") - .append(m_lastGroup->name()) - .append(")")); + m_searchingLabel->setText(tr("No Results")); } - m_groupView->setCurrentIndex(QModelIndex()); - m_searchWidget->show(); - search(); - m_searchUi->searchEdit->setFocus(); + m_searchingLabel->setVisible(true); Q_EMIT searchModeActivated(); } -void DatabaseWidget::search() +void DatabaseWidget::setSearchCaseSensitive(bool state) { - Q_ASSERT(m_lastGroup); + m_searchCaseSensitive = state; - Group* searchGroup; - if (m_searchUi->searchCurrentRadioButton->isChecked()) { - searchGroup = m_lastGroup; - } - else if (m_searchUi->searchRootRadioButton->isChecked()) { - searchGroup = m_db->rootGroup(); - } - else { - Q_ASSERT(false); - return; - } - - Qt::CaseSensitivity sensitivity; - if (m_searchUi->caseSensitiveCheckBox->isChecked()) { - sensitivity = Qt::CaseSensitive; - } - else { - sensitivity = Qt::CaseInsensitive; - } + if (isInSearchMode()) + search(m_lastSearchText); +} - QList searchResult = EntrySearcher().search(m_searchUi->searchEdit->text(), searchGroup, sensitivity); +void DatabaseWidget::setSearchCurrentGroup(bool state) +{ + m_searchCurrentGroup = state; - m_entryView->setEntryList(searchResult); + if (isInSearchMode()) + search(m_lastSearchText); } -void DatabaseWidget::startSearchTimer() +QString DatabaseWidget::getCurrentSearch() { - if (!m_searchTimer->isActive()) { - m_searchTimer->stop(); - } - m_searchTimer->start(100); + return m_lastSearchText; } -void DatabaseWidget::startSearch() +void DatabaseWidget::endSearch() { - if (!m_searchTimer->isActive()) { - m_searchTimer->stop(); + if (isInSearchMode()) + { + Q_ASSERT(m_lastGroup); + + Q_EMIT listModeAboutToActivate(); + + m_groupView->setCurrentGroup(m_lastGroup); + m_entryView->setGroup(m_lastGroup); + + Q_EMIT listModeActivated(); } - search(); + + m_searchingLabel->setVisible(false); + m_searchingLabel->setText(tr("Searching...")); + + m_lastSearchText.clear(); } void DatabaseWidget::emitGroupContextMenuRequested(const QPoint& pos) @@ -908,16 +860,12 @@ void DatabaseWidget::clearLastGroup(Group* group) { if (group) { m_lastGroup = nullptr; - m_searchWidget->hide(); } } void DatabaseWidget::lock() { Q_ASSERT(currentMode() != DatabaseWidget::LockedMode); - if (isInSearchMode()) { - closeSearch(); - } if (m_groupView->currentGroup()) { m_groupBeforeLock = m_groupView->currentGroup()->uuid(); @@ -1008,34 +956,3 @@ bool DatabaseWidget::currentEntryHasNotes() } return !currentEntry->notes().isEmpty(); } - -bool DatabaseWidget::eventFilter(QObject* object, QEvent* event) -{ - if (object == m_searchUi->searchEdit) { - if (event->type() == QEvent::KeyPress) { - QKeyEvent* keyEvent = static_cast(event); - - 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. - Entry* currentEntry = m_entryView->currentEntry(); - if (currentEntry && !m_searchUi->searchEdit->hasSelectedText()) { - setClipboardTextAndMinimize(currentEntry->password()); - return true; - } - } - else if (keyEvent->matches(QKeySequence::MoveToNextLine)) { - // If Down is pressed at EOL in the search edit, move - // the focus to the entry view. - if (!m_searchUi->searchEdit->hasSelectedText() - && m_searchUi->searchEdit->cursorPosition() == m_searchUi->searchEdit->text().size()) { - m_entryView->setFocus(); - return true; - } - } - } - } - - return false; -} diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index e49ee86f57..571429036f 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -39,12 +39,9 @@ class KeePass1OpenWidget; class QFile; class QMenu; class QSplitter; +class QLabel; class UnlockDatabaseWidget; -namespace Ui { - class SearchWidget; -} - class DatabaseWidget : public QStackedWidget { Q_OBJECT @@ -64,6 +61,7 @@ class DatabaseWidget : public QStackedWidget bool dbHasKey() const; bool canDeleteCurrentGroup() const; bool isInSearchMode() const; + QString getCurrentSearch(); int addWidget(QWidget* w); void setCurrentIndex(int index); void setCurrentWidget(QWidget* widget); @@ -101,9 +99,7 @@ class DatabaseWidget : public QStackedWidget void searchModeActivated(); void splitterSizesChanged(); void entryColumnSizesChanged(); - -protected: - bool eventFilter(QObject* object, QEvent* event) override; + void updateSearch(QString text); public Q_SLOTS: void createEntry(); @@ -127,7 +123,11 @@ public Q_SLOTS: void switchToOpenDatabase(const QString& fileName); void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile); void switchToImportKeepass1(const QString& fileName); - void openSearch(); + // Search related slots + void search(const QString& searchtext); + void setSearchCaseSensitive(bool state); + void setSearchCurrentGroup(bool state); + void endSearch(); private Q_SLOTS: void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column); @@ -144,11 +144,6 @@ private Q_SLOTS: void unlockDatabase(bool accepted); void emitCurrentModeChanged(); void clearLastGroup(Group* group); - void search(); - void startSearch(); - void startSearchTimer(); - void showSearch(); - void closeSearch(); private: void setClipboardTextAndMinimize(const QString& text); @@ -156,8 +151,6 @@ private Q_SLOTS: void replaceDatabase(Database* db); Database* m_db; - const QScopedPointer m_searchUi; - QWidget* const m_searchWidget; QWidget* m_mainWidget; EditEntryWidget* m_editEntryWidget; EditEntryWidget* m_historyEditEntryWidget; @@ -170,13 +163,18 @@ private Q_SLOTS: QSplitter* m_splitter; GroupView* m_groupView; EntryView* m_entryView; + QLabel* m_searchingLabel; Group* m_newGroup; Entry* m_newEntry; Group* m_newParent; Group* m_lastGroup; - QTimer* m_searchTimer; QString m_filename; Uuid m_groupBeforeLock; + + // Search state + QString m_lastSearchText; + bool m_searchCaseSensitive; + bool m_searchCurrentGroup; }; #endif // KEEPASSX_DATABASEWIDGET_H diff --git a/src/gui/DatabaseWidgetStateSync.cpp b/src/gui/DatabaseWidgetStateSync.cpp index f2359fe03b..fd5719f5a3 100644 --- a/src/gui/DatabaseWidgetStateSync.cpp +++ b/src/gui/DatabaseWidgetStateSync.cpp @@ -48,16 +48,13 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget) if (m_activeDbWidget) { m_blockUpdates = true; - if (!m_splitterSizes.isEmpty()) { + if (!m_splitterSizes.isEmpty()) m_activeDbWidget->setSplitterSizes(m_splitterSizes); - } - if (m_activeDbWidget->isGroupSelected()) { - restoreListView(); - } - else { + if (m_activeDbWidget->isInSearchMode()) restoreSearchView(); - } + else + restoreListView(); m_blockUpdates = false; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 455d050efc..bb8f17923b 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -33,13 +33,15 @@ #include "gui/DatabaseRepairWidget.h" #include "gui/FileDialog.h" #include "gui/MessageBox.h" +#include "gui/SearchWidget.h" #include "http/Service.h" #include "http/HttpSettings.h" #include "http/OptionDialog.h" #include "gui/SettingsWidget.h" -class HttpPlugin: public ISettingsPage { +class HttpPlugin: public ISettingsPage +{ public: HttpPlugin(DatabaseTabWidget * tabWidget) { m_service = new Service(tabWidget); @@ -68,7 +70,7 @@ class HttpPlugin: public ISettingsPage { } private: Service *m_service; - }; +}; const QString MainWindow::BaseWindowTitle = "KeePassX"; @@ -80,6 +82,12 @@ MainWindow::MainWindow() m_ui->setupUi(this); + // Setup the search widget in the toolbar + SearchWidget *search = new SearchWidget(); + search->connectSignals(m_actionMultiplexer); + m_searchWidgetAction = m_ui->toolBar->addWidget(search); + m_searchWidgetAction->setEnabled(false); + m_countDefaultAttributes = m_ui->menuEntryCopyAttribute->actions().size(); restoreGeometry(config()->get("GUI/MainWindowGeometry").toByteArray()); @@ -125,7 +133,6 @@ MainWindow::MainWindow() setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W); m_ui->actionLockDatabases->setShortcut(Qt::CTRL + Qt::Key_L); setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q); - setShortcut(m_ui->actionSearch, QKeySequence::Find, Qt::CTRL + Qt::Key_F); m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::Key_N); m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E); m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D); @@ -164,8 +171,6 @@ MainWindow::MainWindow() m_ui->actionAbout->setIcon(filePath()->icon("actions", "help-about")); - m_ui->actionSearch->setIcon(filePath()->icon("actions", "system-search")); - m_actionMultiplexer.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)), this, SLOT(setMenuActionState(DatabaseWidget::Mode))); m_actionMultiplexer.connect(SIGNAL(groupChanged()), @@ -177,6 +182,10 @@ MainWindow::MainWindow() m_actionMultiplexer.connect(SIGNAL(entryContextMenuRequested(QPoint)), this, SLOT(showEntryContextMenu(QPoint))); + // Notify search when the active database changes + connect(m_ui->tabWidget, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), + search, SLOT(databaseChanged(DatabaseWidget*))); + connect(m_ui->tabWidget, SIGNAL(tabNameChanged()), SLOT(updateWindowTitle())); connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), @@ -253,9 +262,6 @@ MainWindow::MainWindow() connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog())); - m_actionMultiplexer.connect(m_ui->actionSearch, SIGNAL(triggered()), - SLOT(openSearch())); - updateTrayIcon(); } @@ -367,13 +373,13 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionGroupNew->setEnabled(groupSelected); m_ui->actionGroupEdit->setEnabled(groupSelected); m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); - // TODO: get checked state from db widget - m_ui->actionSearch->setEnabled(true); m_ui->actionChangeMasterKey->setEnabled(true); m_ui->actionChangeDatabaseSettings->setEnabled(true); m_ui->actionDatabaseSave->setEnabled(true); m_ui->actionDatabaseSaveAs->setEnabled(true); m_ui->actionExportCsv->setEnabled(true); + + m_searchWidgetAction->setEnabled(true); break; } case DatabaseWidget::EditMode: @@ -394,12 +400,13 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryCopyNotes->setEnabled(false); m_ui->menuEntryCopyAttribute->setEnabled(false); - m_ui->actionSearch->setEnabled(false); m_ui->actionChangeMasterKey->setEnabled(false); m_ui->actionChangeDatabaseSettings->setEnabled(false); m_ui->actionDatabaseSave->setEnabled(false); m_ui->actionDatabaseSaveAs->setEnabled(false); m_ui->actionExportCsv->setEnabled(false); + + m_searchWidgetAction->setEnabled(false); break; } default: @@ -424,14 +431,14 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryCopyNotes->setEnabled(false); m_ui->menuEntryCopyAttribute->setEnabled(false); - m_ui->actionSearch->setEnabled(false); m_ui->actionChangeMasterKey->setEnabled(false); m_ui->actionChangeDatabaseSettings->setEnabled(false); m_ui->actionDatabaseSave->setEnabled(false); m_ui->actionDatabaseSaveAs->setEnabled(false); - m_ui->actionDatabaseClose->setEnabled(false); m_ui->actionExportCsv->setEnabled(false); + + m_searchWidgetAction->setEnabled(false); } bool inDatabaseTabWidgetOrWelcomeWidget = inDatabaseTabWidget || inWelcomeWidget; diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 7e1a182938..82eb1a3a9b 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -84,6 +84,7 @@ private Q_SLOTS: const QScopedPointer m_ui; SignalMultiplexer m_actionMultiplexer; QAction* m_clearHistoryAction; + QAction* m_searchWidgetAction; QActionGroup* m_lastDatabasesActions; QActionGroup* m_copyAdditionalAttributeActions; QStringList m_openDatabases; diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 66c040e999..c98e9ac6be 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -97,7 +97,7 @@ 0 0 800 - 20 + 26 @@ -106,7 +106,7 @@ - Recent databases + &Recent databases @@ -127,20 +127,20 @@ - Help + He&lp - Entries + E&ntries false - Copy attribute to clipboard + Copy att&ribute to clipboard @@ -156,11 +156,10 @@ - - Groups + &Groups @@ -175,7 +174,7 @@ - View + &View @@ -206,21 +205,21 @@ - + - Quit + &Quit - About + &About - Open database + &Open database @@ -228,7 +227,7 @@ false - Save database + &Save database @@ -236,12 +235,12 @@ false - Close database + &Close database - New database + &New database @@ -249,7 +248,7 @@ false - Add new entry + &Add new entry @@ -257,7 +256,7 @@ false - View/Edit entry + &View/Edit entry @@ -265,7 +264,7 @@ false - Delete entry + &Delete entry @@ -273,7 +272,7 @@ false - Add new group + &Add new group @@ -281,7 +280,7 @@ false - Edit group + &Edit group @@ -289,7 +288,7 @@ false - Delete group + &Delete group @@ -297,7 +296,7 @@ false - Save database as + Sa&ve database as @@ -305,7 +304,7 @@ false - Change master key + Change &master key @@ -313,7 +312,7 @@ false - Database settings + &Database settings Database settings @@ -321,7 +320,7 @@ - Import KeePass 1 database + &Import KeePass 1 database @@ -329,15 +328,7 @@ false - Clone entry - - - - - false - - - Find + &Clone entry @@ -345,7 +336,7 @@ false - Copy username + Copy &username Copy username to clipboard @@ -356,7 +347,7 @@ false - Copy password + Cop&y password Copy password to clipboard @@ -364,7 +355,7 @@ - Settings + &Settings @@ -372,7 +363,7 @@ false - Perform Auto-Type + &Perform Auto-Type @@ -380,7 +371,7 @@ false - Open URL + &Open URL @@ -388,7 +379,7 @@ false - Lock databases + &Lock databases @@ -396,7 +387,7 @@ false - Title + &Title @@ -404,7 +395,7 @@ false - URL + &URL @@ -412,7 +403,7 @@ false - Notes + &Notes @@ -420,12 +411,12 @@ false - Export to CSV file + &Export to CSV file - Repair database + Re&pair database diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp new file mode 100644 index 0000000000..1de392b48d --- /dev/null +++ b/src/gui/SearchWidget.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2016 Jonathan White + * + * 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 "SearchWidget.h" +#include "ui_SearchWidget.h" + +#include +#include +#include + +#include "core/FilePath.h" + +bool SearchEventFilter::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + emit escapePressed(); + return true; + } + } + + return QObject::eventFilter(obj, event); +} + + +SearchWidget::SearchWidget(QWidget *parent) + : QWidget(parent) + , m_ui(new Ui::SearchWidget()) +{ + m_ui->setupUi(this); + + m_searchTimer = new QTimer(this); + m_searchTimer->setSingleShot(true); + + connect(m_ui->searchEdit, SIGNAL(textChanged(QString)), SLOT(startSearchTimer())); + connect(m_ui->searchEdit, SIGNAL(returnPressed()), SLOT(startSearch())); + connect(m_ui->searchIcon, SIGNAL(triggered(QAction*)), m_ui->searchEdit, SLOT(setFocus())); + connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(startSearch())); + connect(&m_searchEventFilter, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear())); + + new QShortcut(Qt::CTRL + Qt::Key_F, m_ui->searchEdit, SLOT(setFocus()), nullptr, Qt::ApplicationShortcut); + + m_ui->searchEdit->installEventFilter(&m_searchEventFilter); + + QMenu *searchMenu = new QMenu(); + m_actionCaseSensitive = searchMenu->addAction(tr("Case Sensitive"), this, SLOT(updateCaseSensitive())); + m_actionCaseSensitive->setCheckable(true); + + m_actionGroupSearch = searchMenu->addAction(tr("Search Current Group"), this, SLOT(updateGroupSearch())); + m_actionGroupSearch->setCheckable(true); + + m_ui->searchIcon->setIcon(filePath()->icon("actions", "system-search")); + m_ui->searchIcon->setMenu(searchMenu); + m_ui->searchIcon->setPopupMode(QToolButton::MenuButtonPopup); +} + +SearchWidget::~SearchWidget() +{ + +} + +void SearchWidget::connectSignals(SignalMultiplexer& mx) +{ + mx.connect(this, SIGNAL(search(QString)), SLOT(search(QString))); + mx.connect(this, SIGNAL(setCaseSensitive(bool)), SLOT(setSearchCaseSensitive(bool))); + mx.connect(this, SIGNAL(setGroupSearch(bool)), SLOT(setSearchCurrentGroup(bool))); + mx.connect(SIGNAL(groupChanged()), m_ui->searchEdit, SLOT(clear())); +} + +void SearchWidget::databaseChanged(DatabaseWidget *dbWidget) +{ + if (dbWidget != nullptr) { + // Set current search text from this database + m_ui->searchEdit->setText(dbWidget->getCurrentSearch()); + + // Enforce search policy + emit setCaseSensitive(m_actionCaseSensitive->isChecked()); + emit setGroupSearch(m_actionGroupSearch->isChecked()); + } else { + m_ui->searchEdit->clear(); + } +} + +void SearchWidget::startSearchTimer() +{ + if (!m_searchTimer->isActive()) { + m_searchTimer->stop(); + } + m_searchTimer->start(100); +} + +void SearchWidget::startSearch() +{ + if (!m_searchTimer->isActive()) { + m_searchTimer->stop(); + } + + search(m_ui->searchEdit->text()); +} + +void SearchWidget::updateCaseSensitive() +{ + emit setCaseSensitive(m_actionCaseSensitive->isChecked()); +} + +void SearchWidget::updateGroupSearch() +{ + emit setGroupSearch(m_actionGroupSearch->isChecked()); +} diff --git a/src/gui/SearchWidget.h b/src/gui/SearchWidget.h new file mode 100644 index 0000000000..49e4a6a01b --- /dev/null +++ b/src/gui/SearchWidget.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 Jonathan White + * + * 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 KEEPASSX_SEARCHWIDGET_H +#define KEEPASSX_SEARCHWIDGET_H + +#include +#include + +#include "gui/DatabaseWidget.h" +#include "core/SignalMultiplexer.h" + +namespace Ui { + class SearchWidget; +} + +class SearchEventFilter : public QObject +{ + Q_OBJECT +signals: + void escapePressed(); + +protected: + virtual bool eventFilter(QObject *obj, QEvent *event) override; +}; + + +class SearchWidget : public QWidget +{ + Q_OBJECT + +public: + explicit SearchWidget(QWidget *parent = 0); + ~SearchWidget(); + + void connectSignals(SignalMultiplexer& mx); + +signals: + void search(const QString &text); + void setCaseSensitive(bool state); + void setGroupSearch(bool state); + +public slots: + void databaseChanged(DatabaseWidget* dbWidget); + +private slots: + void startSearchTimer(); + void startSearch(); + void updateCaseSensitive(); + void updateGroupSearch(); + +private: + const QScopedPointer m_ui; + QTimer* m_searchTimer; + SearchEventFilter m_searchEventFilter; + + QAction *m_actionCaseSensitive; + QAction *m_actionGroupSearch; + + Q_DISABLE_COPY(SearchWidget) +}; + +#endif // SEARCHWIDGET_H diff --git a/src/gui/SearchWidget.ui b/src/gui/SearchWidget.ui index ce4845dcd5..3959582cf1 100644 --- a/src/gui/SearchWidget.ui +++ b/src/gui/SearchWidget.ui @@ -7,7 +7,7 @@ 0 0 630 - 87 + 34 @@ -23,16 +23,16 @@ 0 - - - - + Qt::ClickFocus + + Qt::ToolButtonIconOnly + true @@ -47,79 +47,14 @@ - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Case sensitive - - - - - - - Current group - - - false - - - - - - - Root group - - - true - - - - - - - Qt::Horizontal - - - - 255 - 1 - - - - - - + + - - - LineEdit - QLineEdit -
gui/LineEdit.h
-
-
- closeSearchButton + searchIcon searchEdit - caseSensitiveCheckBox - searchCurrentRadioButton - searchRootRadioButton diff --git a/src/gui/group/GroupView.cpp b/src/gui/group/GroupView.cpp index 31f5639fc5..18f7de804c 100644 --- a/src/gui/group/GroupView.cpp +++ b/src/gui/group/GroupView.cpp @@ -136,7 +136,10 @@ void GroupView::syncExpandedState(const QModelIndex& parent, int start, int end) void GroupView::setCurrentGroup(Group* group) { - setCurrentIndex(m_model->index(group)); + if (group == nullptr) + setCurrentIndex(QModelIndex()); + else + setCurrentIndex(m_model->index(group)); } void GroupView::modelReset() diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 90d7fc2b08..b44491323e 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "config-keepassx-tests.h" #include "core/Config.h" @@ -59,20 +60,25 @@ void TestGui::initTestCase() m_mainWindow->activateWindow(); Tools::wait(50); + // Load the NewDatabase.kdbx file into temporary storage QByteArray tmpData; QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx")); QVERIFY(sourceDbFile.open(QIODevice::ReadOnly)); QVERIFY(Tools::readAllFromDevice(&sourceDbFile, tmpData)); + sourceDbFile.close(); - QVERIFY(m_orgDbFile.open()); - m_orgDbFileName = QFileInfo(m_orgDbFile.fileName()).fileName(); - QCOMPARE(m_orgDbFile.write(tmpData), static_cast((tmpData.size()))); - m_orgDbFile.close(); + // Write the temp storage to a temp database file for use in our tests + QVERIFY(m_dbFile.open()); + QCOMPARE(m_dbFile.write(tmpData), static_cast((tmpData.size()))); + m_dbFile.close(); + + m_dbFileName = QFileInfo(m_dbFile).fileName(); } -void TestGui::testOpenDatabase() +// Every test starts with opening the temp database +void TestGui::init() { - fileDialog()->setNextFileName(m_orgDbFile.fileName()); + fileDialog()->setNextFileName(m_dbFile.fileName()); triggerAction("actionDatabaseOpen"); QWidget* databaseOpenWidget = m_mainWindow->findChild("databaseOpenWidget"); @@ -81,60 +87,91 @@ void TestGui::testOpenDatabase() QTest::keyClicks(editPassword, "a"); QTest::keyClick(editPassword, Qt::Key_Enter); + Tools::wait(100); + + QVERIFY(m_tabWidget->currentDatabaseWidget()); + + m_dbWidget = m_tabWidget->currentDatabaseWidget(); + m_db = m_dbWidget->database(); +} + +// Every test ends with closing the temp database without saving +void TestGui::cleanup() +{ + // DO NOT save the database + MessageBox::setNextAnswer(QMessageBox::No); + triggerAction("actionDatabaseClose"); + Tools::wait(100); + + m_db = nullptr; + m_dbWidget = nullptr; } void TestGui::testTabs() { QCOMPARE(m_tabWidget->count(), 1); - QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), m_orgDbFileName); - - m_dbWidget = m_tabWidget->currentDatabaseWidget(); - m_db = m_dbWidget->database(); + QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), m_dbFileName); } void TestGui::testEditEntry() { + QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + + // Select the first entry in the database EntryView* entryView = m_dbWidget->findChild("entryView"); - QModelIndex item = entryView->model()->index(0, 1); - QRect itemRect = entryView->visualRect(item); - QTest::mouseClick(entryView->viewport(), Qt::LeftButton, Qt::NoModifier, itemRect.center()); + QModelIndex entryItem = entryView->model()->index(0, 1); + clickIndex(entryItem, entryView, Qt::LeftButton); + // Confirm the edit action button is enabled QAction* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); QVERIFY(entryEditAction->isEnabled()); - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); QVERIFY(entryEditWidget->isVisible()); QVERIFY(entryEditWidget->isEnabled()); - QTest::mouseClick(entryEditWidget, Qt::LeftButton); + // Edit the first entry ("Sample Entry") + QTest::mouseClick(entryEditWidget, Qt::LeftButton); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QVERIFY(m_dbWidget->currentWidget() == editEntryWidget); + QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + QTest::keyClicks(titleEdit, "_test"); + + // Save the edit QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); - QVERIFY(editEntryWidgetButtonBox); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - // make sure the database isn't marked as modified - // wait for modified timer - QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), m_orgDbFileName); + + // Confirm edit was made + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); + Entry* entry = entryView->entryFromIndex(entryItem); + QCOMPARE(entry->title(), QString("Sample Entry_test")); + QCOMPARE(entry->historyItems().size(), 1); + + // Confirm modified indicator is showing + QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("%1*").arg(m_dbFileName)); } void TestGui::testAddEntry() { + QToolBar* toolBar = m_mainWindow->findChild("toolBar"); EntryView* entryView = m_dbWidget->findChild("entryView"); + + // Find the new entry action QAction* entryNewAction = m_mainWindow->findChild("actionEntryNew"); QVERIFY(entryNewAction->isEnabled()); - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + + // Find the button associated with the new entry action QWidget* entryNewWidget = toolBar->widgetForAction(entryNewAction); QVERIFY(entryNewWidget->isVisible()); QVERIFY(entryNewWidget->isEnabled()); + // Click the new entry button and check that we enter edit mode QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); + // Add entry "test" and confirm added EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "test"); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); @@ -144,98 +181,106 @@ void TestGui::testAddEntry() QCOMPARE(entry->title(), QString("test")); QCOMPARE(entry->historyItems().size(), 0); - // wait for modified timer - QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("%1*").arg(m_orgDbFileName)); - - QAction* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); - QVERIFY(entryEditAction->isEnabled()); - QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); - QVERIFY(entryEditWidget->isVisible()); - QVERIFY(entryEditWidget->isEnabled()); - QTest::mouseClick(entryEditWidget, Qt::LeftButton); - - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); - QTest::keyClicks(titleEdit, "something"); - QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - - QCOMPARE(entry->title(), QString("testsomething")); - QCOMPARE(entry->historyItems().size(), 1); - + // Add entry "something 2" QTest::mouseClick(entryNewWidget, Qt::LeftButton); QTest::keyClicks(titleEdit, "something 2"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - + // Add entry "something 3" QTest::mouseClick(entryNewWidget, Qt::LeftButton); QTest::keyClicks(titleEdit, "something 3"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - + // Confirm that 4 entries now exist QTRY_COMPARE(entryView->model()->rowCount(), 4); } void TestGui::testSearch() { - QAction* searchAction = m_mainWindow->findChild("actionSearch"); - QVERIFY(searchAction->isEnabled()); + // Add canned entries for consistent testing + testAddEntry(); + QToolBar* toolBar = m_mainWindow->findChild("toolBar"); - QWidget* searchActionWidget = toolBar->widgetForAction(searchAction); - EntryView* entryView = m_dbWidget->findChild("entryView"); - QLineEdit* searchEdit = m_dbWidget->findChild("searchEdit"); - QToolButton* clearSearch = m_dbWidget->findChild("clearButton"); - QVERIFY(!searchEdit->isVisible()); + QWidget* searchActionWidget = toolBar->findChild("SearchWidget"); + QVERIFY(searchActionWidget->isEnabled()); + QLineEdit* searchEdit = searchActionWidget->findChild("searchEdit"); + + EntryView* entryView = m_dbWidget->findChild("entryView"); + QVERIFY(entryView->isVisible()); // Enter search - QTest::mouseClick(searchActionWidget, Qt::LeftButton); + QTest::mouseClick(searchEdit, Qt::LeftButton); QTRY_VERIFY(searchEdit->hasFocus()); // Search for "ZZZ" QTest::keyClicks(searchEdit, "ZZZ"); - QTRY_COMPARE(entryView->model()->rowCount(), 0); - // Escape - QTest::keyClick(m_mainWindow, Qt::Key_Escape); - QTRY_VERIFY(!searchEdit->hasFocus()); - // Enter search again - QTest::mouseClick(searchActionWidget, Qt::LeftButton); - QTRY_VERIFY(searchEdit->hasFocus()); - // Input and clear - QTest::keyClicks(searchEdit, "ZZZ"); QTRY_COMPARE(searchEdit->text(), QString("ZZZ")); - QTest::mouseClick(clearSearch, Qt::LeftButton); - QTRY_COMPARE(searchEdit->text(), QString("")); - // Triggering search should select the existing text - QTest::keyClicks(searchEdit, "ZZZ"); - QTest::mouseClick(searchActionWidget, Qt::LeftButton); + QTRY_VERIFY(m_dbWidget->isInSearchMode()); + QTRY_COMPARE(entryView->model()->rowCount(), 0); + // Escape clears searchedit and retains focus + QTest::keyClick(searchEdit, Qt::Key_Escape); + QTRY_VERIFY(searchEdit->text().isEmpty()); QTRY_VERIFY(searchEdit->hasFocus()); + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); // Search for "some" QTest::keyClicks(searchEdit, "some"); - QTRY_COMPARE(entryView->model()->rowCount(), 4); + QTRY_VERIFY(m_dbWidget->isInSearchMode()); + QTRY_COMPARE(entryView->model()->rowCount(), 3); // Press Down to focus on the entry view - QVERIFY(!entryView->hasFocus()); - QTest::keyClick(searchEdit, Qt::Key_Down); - QVERIFY(entryView->hasFocus()); + QTest::keyClicks(searchEdit, "thing"); + QTRY_COMPARE(entryView->model()->rowCount(), 2); + //QVERIFY(!entryView->hasFocus()); + //QTest::keyClick(searchEdit, Qt::Key_Down); + //QVERIFY(entryView->hasFocus()); - clickIndex(entryView->model()->index(0, 1), entryView, Qt::LeftButton); + // Try to edit the first entry from the search view + QModelIndex item = entryView->model()->index(0, 1); + Entry* entry = entryView->entryFromIndex(item); + QVERIFY(m_dbWidget->isInSearchMode()); + clickIndex(item, entryView, Qt::LeftButton); QAction* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); QVERIFY(entryEditAction->isEnabled()); QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); QVERIFY(entryEditWidget->isVisible()); QVERIFY(entryEditWidget->isEnabled()); QTest::mouseClick(entryEditWidget, Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); + // Perform the edit and save it EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + QString origTitle = titleEdit->text(); + QTest::keyClicks(titleEdit, "_edited"); QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); + // Confirm the edit was made and we are back in view mode + QTRY_VERIFY(m_dbWidget->isInSearchMode()); + QCOMPARE(entry->title(), origTitle.append("_edited")); - clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton); - QAction* entryDeleteAction = m_mainWindow->findChild("actionEntryDelete"); + // Cancel search, should return to normal view + QTest::mouseClick(searchEdit, Qt::LeftButton); + QTest::keyClick(searchEdit, Qt::Key_Escape); + QTRY_COMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); + //QCOMPARE(entryView->model()->rowCount(), 4); + // TODO: add tests to confirm case sensitive and group search +} + +void TestGui::testDeleteEntry() +{ + // Add canned entries for consistent testing + testAddEntry(); + + GroupView* groupView = m_dbWidget->findChild("groupView"); + EntryView* entryView = m_dbWidget->findChild("entryView"); + QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + QAction* entryDeleteAction = m_mainWindow->findChild("actionEntryDelete"); QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction); + + QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); + clickIndex(entryView->model()->index(1, 0), entryView, Qt::LeftButton); QVERIFY(entryDeleteWidget->isVisible()); QVERIFY(entryDeleteWidget->isEnabled()); QVERIFY(!m_db->metadata()->recycleBin()); @@ -260,21 +305,7 @@ void TestGui::testSearch() QCOMPARE(entryView->model()->rowCount(), 1); QCOMPARE(m_db->metadata()->recycleBin()->entries().size(), 3); - QWidget* closeSearchButton = m_dbWidget->findChild("closeSearchButton"); - QTest::mouseClick(closeSearchButton, Qt::LeftButton); - - QCOMPARE(entryView->model()->rowCount(), 1); -} - -void TestGui::testDeleteEntry() -{ - GroupView* groupView = m_dbWidget->findChild("groupView"); - EntryView* entryView = m_dbWidget->findChild("entryView"); - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); - QAction* entryDeleteAction = m_mainWindow->findChild("actionEntryDelete"); - QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction); QCOMPARE(groupView->currentGroup(), m_db->rootGroup()); - QModelIndex rootGroupIndex = groupView->model()->index(0, 0); clickIndex(groupView->model()->index(groupView->model()->rowCount(rootGroupIndex) - 1, 0, rootGroupIndex), groupView, Qt::LeftButton); @@ -363,28 +394,29 @@ void TestGui::testDragAndDropGroup() dragAndDropGroup(groupModel->index(0, 0, rootIndex), rootIndex, - -1, true, "NewDatabase", 5); + -1, true, "NewDatabase", 4); } void TestGui::testSaveAs() { - QFileInfo fileInfo(m_orgDbFile.fileName()); + QFileInfo fileInfo(m_dbFile.fileName()); QDateTime lastModified = fileInfo.lastModified(); m_db->metadata()->setName("SaveAs"); - QTemporaryFile* tmpFile = new QTemporaryFile(); // open temporary file so it creates a filename - QVERIFY(tmpFile->open()); - m_tmpFileName = tmpFile->fileName(); - delete tmpFile; - fileDialog()->setNextFileName(m_tmpFileName); + QTemporaryFile tmpFile; + QVERIFY(tmpFile.open()); + QString tmpFileName = tmpFile.fileName(); + tmpFile.remove(); + + fileDialog()->setNextFileName(tmpFileName); triggerAction("actionDatabaseSaveAs"); QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("SaveAs")); - checkDatabase(); + checkDatabase(tmpFileName); fileInfo.refresh(); QCOMPARE(fileInfo.lastModified(), lastModified); @@ -433,40 +465,48 @@ void TestGui::testKeePass1Import() QCOMPARE(m_tabWidget->count(), 2); QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("basic [New database]*")); + + // Close the KeePass1 Database + MessageBox::setNextAnswer(QMessageBox::No); + triggerAction("actionDatabaseClose"); + Tools::wait(100); + } void TestGui::testDatabaseLocking() { - MessageBox::setNextAnswer(QMessageBox::Cancel); + QString origDbName = m_tabWidget->tabText(0); + MessageBox::setNextAnswer(QMessageBox::Cancel); triggerAction("actionLockDatabases"); - QCOMPARE(m_tabWidget->tabText(0).remove('&'), QString("Save [locked]")); - QCOMPARE(m_tabWidget->tabText(1).remove('&'), QString("basic [New database]*")); + QCOMPARE(m_tabWidget->tabText(0).remove('&'), origDbName + " [locked]"); QWidget* dbWidget = m_tabWidget->currentDatabaseWidget(); QWidget* unlockDatabaseWidget = dbWidget->findChild("unlockDatabaseWidget"); QWidget* editPassword = unlockDatabaseWidget->findChild("editPassword"); QVERIFY(editPassword); - QTest::keyClicks(editPassword, "masterpw"); + QTest::keyClicks(editPassword, "a"); QTest::keyClick(editPassword, Qt::Key_Enter); - QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()).remove('&'), QString("basic [New database]*")); + QCOMPARE(m_tabWidget->tabText(0).remove('&'), origDbName); } void TestGui::cleanupTestCase() { delete m_mainWindow; - QFile::remove(m_tmpFileName); } -void TestGui::checkDatabase() +void TestGui::checkDatabase(QString dbFileName) { + if (dbFileName.isEmpty()) + dbFileName = m_dbFile.fileName(); + CompositeKey key; key.addKey(PasswordKey("a")); KeePass2Reader reader; - QScopedPointer dbSaved(reader.readDatabase(m_tmpFileName, key)); + QScopedPointer dbSaved(reader.readDatabase(dbFileName, key)); QVERIFY(dbSaved); QVERIFY(!reader.hasError()); QCOMPARE(dbSaved->metadata()->name(), m_db->metadata()->name()); diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index a7474ca54e..72e3f40568 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -34,7 +34,10 @@ class TestGui : public QObject private Q_SLOTS: void initTestCase(); - void testOpenDatabase(); + void init(); + void cleanup(); + void cleanupTestCase(); + void testTabs(); void testEditEntry(); void testAddEntry(); @@ -48,10 +51,9 @@ private Q_SLOTS: void testDatabaseSettings(); void testKeePass1Import(); void testDatabaseLocking(); - void cleanupTestCase(); private: - void checkDatabase(); + void checkDatabase(QString dbFileName = ""); void triggerAction(const QString& name); void dragAndDropGroup(const QModelIndex& sourceIndex, const QModelIndex& targetIndex, int row, bool expectedResult, const QString& expectedParentName, int expectedPos); @@ -61,9 +63,8 @@ private Q_SLOTS: MainWindow* m_mainWindow; DatabaseTabWidget* m_tabWidget; DatabaseWidget* m_dbWidget; - QTemporaryFile m_orgDbFile; - QString m_orgDbFileName; - QString m_tmpFileName; + QTemporaryFile m_dbFile; + QString m_dbFileName; Database* m_db; };