diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a21b0057ee..5a4aa0a55c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,7 +33,6 @@ endif(NOT ZXCVBN_LIBRARIES) set(keepassx_SOURCES core/Alloc.cpp core/AutoTypeAssociations.cpp - core/AutoTypeMatch.cpp core/Base32.cpp core/Bootstrap.cpp core/Clock.cpp @@ -139,8 +138,6 @@ set(keepassx_SOURCES gui/csvImport/CsvImportWizard.cpp gui/csvImport/CsvParserModel.cpp gui/entry/AutoTypeAssociationsModel.cpp - gui/entry/AutoTypeMatchModel.cpp - gui/entry/AutoTypeMatchView.cpp gui/entry/EditEntryWidget.cpp gui/entry/EntryAttachmentsModel.cpp gui/entry/EntryAttachmentsWidget.cpp @@ -273,11 +270,10 @@ set(autotype_SOURCES core/Tools.cpp autotype/AutoType.cpp autotype/AutoTypeAction.cpp - autotype/AutoTypeFilterLineEdit.cpp + autotype/AutoTypeMatchModel.cpp + autotype/AutoTypeMatchView.cpp autotype/AutoTypeSelectDialog.cpp - autotype/AutoTypeSelectView.cpp autotype/ShortcutWidget.cpp - autotype/WildcardMatcher.cpp autotype/WindowSelectComboBox.cpp) if(MINGW) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 94dd432c99..7a52491b37 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -21,13 +21,12 @@ #include #include #include +#include #include "config-keepassx.h" #include "autotype/AutoTypePlatformPlugin.h" #include "autotype/AutoTypeSelectDialog.h" -#include "autotype/WildcardMatcher.h" -#include "core/AutoTypeMatch.h" #include "core/Config.h" #include "core/Database.h" #include "core/Entry.h" @@ -250,12 +249,10 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) return; } - QList sequences = autoTypeSequences(entry); - if (sequences.isEmpty()) { - return; + auto sequences = entry->autoTypeSequences(); + if (!sequences.isEmpty()) { + executeAutoTypeActions(entry, hideWindow, sequences.first()); } - - executeAutoTypeActions(entry, hideWindow, sequences.first()); } /** @@ -273,6 +270,11 @@ void AutoType::performAutoTypeWithSequence(const Entry* entry, const QString& se void AutoType::startGlobalAutoType() { + // Never Auto-Type into KeePassXC itself + if (qApp->focusWindow()) { + return; + } + m_windowForGlobal = m_plugin->activeWindow(); m_windowTitleForGlobal = m_plugin->activeWindowTitle(); #ifdef Q_OS_MACOS @@ -331,58 +333,62 @@ void AutoType::performGlobalAutoType(const QList>& dbLi for (const auto& db : dbList) { const QList dbEntries = db->rootGroup()->entriesRecursive(); - for (Entry* entry : dbEntries) { + for (auto entry : dbEntries) { + auto group = entry->group(); + if (!group || !group->resolveAutoTypeEnabled() || !entry->autoTypeEnabled()) { + continue; + } + if (hideExpired && entry->isExpired()) { continue; } - const QSet sequences = autoTypeSequences(entry, m_windowTitleForGlobal).toSet(); - for (const QString& sequence : sequences) { - if (!sequence.isEmpty()) { - matchList << AutoTypeMatch(entry, sequence); - } + auto sequences = entry->autoTypeSequences(m_windowTitleForGlobal).toSet(); + for (const auto& sequence : sequences) { + matchList << AutoTypeMatch(entry, sequence); } } } - if (matchList.isEmpty()) { - if (qobject_cast(QCoreApplication::instance())) { - auto* msgBox = new QMessageBox(); - msgBox->setAttribute(Qt::WA_DeleteOnClose); - msgBox->setWindowTitle(tr("Auto-Type - KeePassXC")); - msgBox->setText(tr("Couldn't find an entry that matches the window title:") - .append("\n\n") - .append(m_windowTitleForGlobal)); - msgBox->setIcon(QMessageBox::Information); - msgBox->setStandardButtons(QMessageBox::Ok); -#ifdef Q_OS_MACOS - m_plugin->raiseOwnWindow(); - Tools::wait(200); -#endif - msgBox->exec(); - restoreWindowState(); + // Show the selection dialog if we always ask, have multiple matches, or no matches + if (config()->get(Config::Security_AutoTypeAsk).toBool() || matchList.size() > 1 || matchList.isEmpty()) { + // Close any open modal windows that would interfere with the process + if (qApp->modalWindow()) { + qApp->modalWindow()->close(); } - m_inGlobalAutoTypeDialog.unlock(); - emit autotypeRejected(); - } else if ((matchList.size() == 1) && !config()->get(Config::Security_AutoTypeAsk).toBool()) { - executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence, m_windowForGlobal); - m_inGlobalAutoTypeDialog.unlock(); - } else { auto* selectDialog = new AutoTypeSelectDialog(); + selectDialog->setMatches(matchList, dbList); - // connect slots, both of which must unlock the m_inGlobalAutoTypeDialog mutex - connect(selectDialog, SIGNAL(matchActivated(AutoTypeMatch)), SLOT(performAutoTypeFromGlobal(AutoTypeMatch))); - connect(selectDialog, SIGNAL(rejected()), SLOT(autoTypeRejectedFromGlobal())); + connect(getMainWindow(), &MainWindow::databaseLocked, selectDialog, &AutoTypeSelectDialog::reject); + connect(selectDialog, &AutoTypeSelectDialog::matchActivated, this, [this](AutoTypeMatch match) { + restoreWindowState(); + QApplication::processEvents(); + m_plugin->raiseWindow(m_windowForGlobal); + executeAutoTypeActions(match.first, nullptr, match.second, m_windowForGlobal); + resetAutoTypeState(); + }); + connect(selectDialog, &QDialog::rejected, this, [this] { + restoreWindowState(); + resetAutoTypeState(); + emit autotypeRejected(); + }); - selectDialog->setMatchList(matchList); #ifdef Q_OS_MACOS m_plugin->raiseOwnWindow(); Tools::wait(200); #endif selectDialog->show(); selectDialog->raise(); - // necessary when the main window is minimized selectDialog->activateWindow(); + } else if (!matchList.isEmpty()) { + // Only one match and not asking, do it! + executeAutoTypeActions(matchList.first().first, nullptr, matchList.first().second, m_windowForGlobal); + resetAutoTypeState(); + } else { + // We should never get here + Q_ASSERT(false); + resetAutoTypeState(); + emit autotypeRejected(); } } @@ -399,29 +405,12 @@ void AutoType::restoreWindowState() #endif } -void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) -{ - restoreWindowState(); - - m_plugin->raiseWindow(m_windowForGlobal); - executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowForGlobal); - - // make sure the mutex is definitely locked before we unlock it - Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock()); - m_inGlobalAutoTypeDialog.unlock(); -} - -void AutoType::autoTypeRejectedFromGlobal() +void AutoType::resetAutoTypeState() { - // this slot can be called twice when the selection dialog is deleted, - // so make sure the mutex is locked before we try unlocking it - Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock()); - m_inGlobalAutoTypeDialog.unlock(); m_windowForGlobal = 0; m_windowTitleForGlobal.clear(); - - restoreWindowState(); - emit autotypeRejected(); + Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock()); + m_inGlobalAutoTypeDialog.unlock(); } /** @@ -622,101 +611,6 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c return list; } -/** - * Retrive the autotype sequences matches for a given windowTitle - * This returns a list with priority ordering. If you don't want duplicates call .toSet() on it. - */ -QList AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle) -{ - QList sequenceList; - const Group* group = entry->group(); - - if (!group || !entry->autoTypeEnabled()) { - return sequenceList; - } - - do { - if (group->autoTypeEnabled() == Group::Disable) { - return sequenceList; - } else if (group->autoTypeEnabled() == Group::Enable) { - break; - } - group = group->parentGroup(); - - } while (group); - - if (!windowTitle.isEmpty()) { - const QList assocList = entry->autoTypeAssociations()->getAll(); - for (const AutoTypeAssociations::Association& assoc : assocList) { - const QString window = entry->resolveMultiplePlaceholders(assoc.window); - if (windowMatches(windowTitle, window)) { - if (!assoc.sequence.isEmpty()) { - sequenceList.append(assoc.sequence); - } else { - sequenceList.append(entry->effectiveAutoTypeSequence()); - } - } - } - - if (config()->get(Config::AutoTypeEntryTitleMatch).toBool() - && windowMatchesTitle(windowTitle, entry->resolvePlaceholder(entry->title()))) { - sequenceList.append(entry->effectiveAutoTypeSequence()); - } - - if (config()->get(Config::AutoTypeEntryURLMatch).toBool() - && windowMatchesUrl(windowTitle, entry->resolvePlaceholder(entry->url()))) { - sequenceList.append(entry->effectiveAutoTypeSequence()); - } - - if (sequenceList.isEmpty()) { - return sequenceList; - } - } else { - sequenceList.append(entry->effectiveAutoTypeSequence()); - } - - return sequenceList; -} - -/** - * Checks if a window title matches a pattern - */ -bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPattern) -{ - if (windowPattern.startsWith("//") && windowPattern.endsWith("//") && windowPattern.size() >= 4) { - QRegExp regExp(windowPattern.mid(2, windowPattern.size() - 4), Qt::CaseInsensitive, QRegExp::RegExp2); - return (regExp.indexIn(windowTitle) != -1); - } - return WildcardMatcher(windowTitle).match(windowPattern); -} - -/** - * Checks if a window title matches an entry Title - * The entry title should be Spr-compiled by the caller - */ -bool AutoType::windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle) -{ - return !resolvedTitle.isEmpty() && windowTitle.contains(resolvedTitle, Qt::CaseInsensitive); -} - -/** - * Checks if a window title matches an entry URL - * The entry URL should be Spr-compiled by the caller - */ -bool AutoType::windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl) -{ - if (!resolvedUrl.isEmpty() && windowTitle.contains(resolvedUrl, Qt::CaseInsensitive)) { - return true; - } - - QUrl url(resolvedUrl); - if (url.isValid() && !url.host().isEmpty()) { - return windowTitle.contains(url.host(), Qt::CaseInsensitive); - } - - return false; -} - /** * Checks if the overall syntax of an autotype sequence is fine */ diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index 8d0cf3d14f..0af78c4c58 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -24,7 +24,7 @@ #include #include -#include "core/AutoTypeMatch.h" +#include "autotype/AutoTypeMatch.h" class AutoTypeAction; class AutoTypeExecutor; @@ -68,8 +68,6 @@ public slots: private slots: void startGlobalAutoType(); - void performAutoTypeFromGlobal(AutoTypeMatch match); - void autoTypeRejectedFromGlobal(); void unloadPlugin(); private: @@ -89,11 +87,8 @@ private slots: WId window = 0); bool parseActions(const QString& sequence, const Entry* entry, QList& actions); QList createActionFromTemplate(const QString& tmpl, const Entry* entry); - QList autoTypeSequences(const Entry* entry, const QString& windowTitle = QString()); - bool windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle); - bool windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl); - bool windowMatches(const QString& windowTitle, const QString& windowPattern); void restoreWindowState(); + void resetAutoTypeState(); QMutex m_inAutoType; QMutex m_inGlobalAutoTypeDialog; diff --git a/src/autotype/AutoTypeFilterLineEdit.cpp b/src/autotype/AutoTypeFilterLineEdit.cpp deleted file mode 100644 index d94292d26f..0000000000 --- a/src/autotype/AutoTypeFilterLineEdit.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2019 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 "AutoTypeFilterLineEdit.h" -#include - -void AutoTypeFilterLineEdit::keyPressEvent(QKeyEvent* event) -{ - if (event->key() == Qt::Key_Up) { - emit keyUpPressed(); - } else if (event->key() == Qt::Key_Down) { - emit keyDownPressed(); - } else { - QLineEdit::keyPressEvent(event); - } -} - -void AutoTypeFilterLineEdit::keyReleaseEvent(QKeyEvent* event) -{ - if (event->key() == Qt::Key_Escape) { - emit escapeReleased(); - } else { - QLineEdit::keyReleaseEvent(event); - } -} diff --git a/src/autotype/AutoTypeFilterLineEdit.h b/src/autotype/AutoTypeFilterLineEdit.h deleted file mode 100644 index 6f8ca570d3..0000000000 --- a/src/autotype/AutoTypeFilterLineEdit.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019 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 KEEPASSX_AUTOTYPEFILTERLINEEDIT_H -#define KEEPASSX_AUTOTYPEFILTERLINEEDIT_H - -#include - -class AutoTypeFilterLineEdit : public QLineEdit -{ - Q_OBJECT - -public: - AutoTypeFilterLineEdit(QWidget* widget) - : QLineEdit(widget) - { - } - -protected: - virtual void keyPressEvent(QKeyEvent* event); - virtual void keyReleaseEvent(QKeyEvent* event); -signals: - void keyUpPressed(); - void keyDownPressed(); - void escapeReleased(); -}; - -#endif // KEEPASSX_AUTOTYPEFILTERLINEEDIT_H diff --git a/src/core/AutoTypeMatch.h b/src/autotype/AutoTypeMatch.h similarity index 56% rename from src/core/AutoTypeMatch.h rename to src/autotype/AutoTypeMatch.h index 768cf1682f..aeb36547ae 100644 --- a/src/core/AutoTypeMatch.h +++ b/src/autotype/AutoTypeMatch.h @@ -1,6 +1,5 @@ /* - * Copyright (C) 2015 David Wu - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2020 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 @@ -16,26 +15,14 @@ * along with this program. If not, see . */ -#ifndef KEEPASSX_AUTOTYPEMATCH_H -#define KEEPASSX_AUTOTYPEMATCH_H +#ifndef KPXC_AUTOTYPEMATCH_H +#define KPXC_AUTOTYPEMATCH_H -#include +#include +#include #include class Entry; +typedef QPair, QString> AutoTypeMatch; -struct AutoTypeMatch -{ - Entry* entry; - QString sequence; - - AutoTypeMatch(); - AutoTypeMatch(Entry* entry, QString sequence); - - bool operator==(const AutoTypeMatch& other) const; - bool operator!=(const AutoTypeMatch& other) const; -}; - -Q_DECLARE_TYPEINFO(AutoTypeMatch, Q_MOVABLE_TYPE); - -#endif // KEEPASSX_AUTOTYPEMATCH_H +#endif // KPXC_AUTOTYPEMATCH_H diff --git a/src/gui/entry/AutoTypeMatchModel.cpp b/src/autotype/AutoTypeMatchModel.cpp similarity index 88% rename from src/gui/entry/AutoTypeMatchModel.cpp rename to src/autotype/AutoTypeMatchModel.cpp index ac2d0f874b..f15df233b5 100644 --- a/src/gui/entry/AutoTypeMatchModel.cpp +++ b/src/autotype/AutoTypeMatchModel.cpp @@ -56,7 +56,7 @@ void AutoTypeMatchModel::setMatchList(const QList& matches) QSet databases; for (AutoTypeMatch& match : m_matches) { - databases.insert(match.entry->group()->database()); + databases.insert(match.first->group()->database()); } for (Database* db : asConst(databases)) { @@ -88,7 +88,6 @@ int AutoTypeMatchModel::rowCount(const QModelIndex& parent) const int AutoTypeMatchModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); - return 4; } @@ -103,30 +102,30 @@ QVariant AutoTypeMatchModel::data(const QModelIndex& index, int role) const if (role == Qt::DisplayRole) { switch (index.column()) { case ParentGroup: - if (match.entry->group()) { - return match.entry->group()->name(); + if (match.first->group()) { + return match.first->group()->name(); } break; case Title: - return match.entry->resolveMultiplePlaceholders(match.entry->title()); + return match.first->resolveMultiplePlaceholders(match.first->title()); case Username: - return match.entry->resolveMultiplePlaceholders(match.entry->username()); + return match.first->resolveMultiplePlaceholders(match.first->username()); case Sequence: - return match.sequence; + return match.second; } } else if (role == Qt::DecorationRole) { switch (index.column()) { case ParentGroup: - if (match.entry->group()) { - return match.entry->group()->iconPixmap(); + if (match.first->group()) { + return match.first->group()->iconPixmap(); } break; case Title: - return match.entry->iconPixmap(); + return match.first->iconPixmap(); } } else if (role == Qt::FontRole) { QFont font; - if (match.entry->isExpired()) { + if (match.first->isExpired()) { font.setStrikeOut(true); } return font; @@ -157,7 +156,7 @@ void AutoTypeMatchModel::entryDataChanged(Entry* entry) { for (int row = 0; row < m_matches.size(); ++row) { AutoTypeMatch match = m_matches[row]; - if (match.entry == entry) { + if (match.first == entry) { emit dataChanged(index(row, 0), index(row, columnCount() - 1)); } } @@ -167,7 +166,7 @@ void AutoTypeMatchModel::entryAboutToRemove(Entry* entry) { for (int row = 0; row < m_matches.size(); ++row) { AutoTypeMatch match = m_matches[row]; - if (match.entry == entry) { + if (match.first == entry) { beginRemoveRows(QModelIndex(), row, row); m_matches.removeAt(row); endRemoveRows(); diff --git a/src/gui/entry/AutoTypeMatchModel.h b/src/autotype/AutoTypeMatchModel.h similarity index 98% rename from src/gui/entry/AutoTypeMatchModel.h rename to src/autotype/AutoTypeMatchModel.h index 58b89465b4..e5c86e84d7 100644 --- a/src/gui/entry/AutoTypeMatchModel.h +++ b/src/autotype/AutoTypeMatchModel.h @@ -21,7 +21,7 @@ #include -#include "core/AutoTypeMatch.h" +#include "autotype/AutoTypeMatch.h" class Entry; class Group; diff --git a/src/autotype/AutoTypeMatchView.cpp b/src/autotype/AutoTypeMatchView.cpp new file mode 100644 index 0000000000..0753e4f82b --- /dev/null +++ b/src/autotype/AutoTypeMatchView.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 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 "AutoTypeMatchView.h" + +#include "core/Entry.h" +#include "gui/Clipboard.h" +#include "gui/Icons.h" + +#include +#include +#include +#include + +class CustomSortFilterProxyModel : public QSortFilterProxyModel +{ +public: + explicit CustomSortFilterProxyModel(QObject* parent = nullptr) + : QSortFilterProxyModel(parent){}; + ~CustomSortFilterProxyModel() override = default; + + // Only search the first three columns (ie, ignore sequence column) + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override + { + auto index0 = sourceModel()->index(sourceRow, 0, sourceParent); + auto index1 = sourceModel()->index(sourceRow, 1, sourceParent); + auto index2 = sourceModel()->index(sourceRow, 2, sourceParent); + + return sourceModel()->data(index0).toString().contains(filterRegExp()) + || sourceModel()->data(index1).toString().contains(filterRegExp()) + || sourceModel()->data(index2).toString().contains(filterRegExp()); + } +}; + +AutoTypeMatchView::AutoTypeMatchView(QWidget* parent) + : QTableView(parent) + , m_model(new AutoTypeMatchModel(this)) + , m_sortModel(new CustomSortFilterProxyModel(this)) +{ + m_sortModel->setSourceModel(m_model); + m_sortModel->setDynamicSortFilter(true); + m_sortModel->setSortLocaleAware(true); + m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive); + m_sortModel->setFilterKeyColumn(-1); + m_sortModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + setModel(m_sortModel); + + setAlternatingRowColors(true); + setDragEnabled(false); + setSortingEnabled(true); + setCursor(Qt::PointingHandCursor); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + setTabKeyNavigation(false); + horizontalHeader()->setStretchLastSection(true); + verticalHeader()->hide(); + + setContextMenuPolicy(Qt::CustomContextMenu); + + connect(this, &QTableView::doubleClicked, this, [this](const QModelIndex& index) { + emit matchActivated(matchFromIndex(index)); + }); +} + +void AutoTypeMatchView::keyPressEvent(QKeyEvent* event) +{ + if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && currentIndex().isValid()) { + emit matchActivated(matchFromIndex(currentIndex())); + } + + QTableView::keyPressEvent(event); +} + +void AutoTypeMatchView::setMatchList(const QList& matches) +{ + m_model->setMatchList(matches); + m_sortModel->setFilterWildcard({}); + + horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); + selectionModel()->setCurrentIndex(m_sortModel->index(0, 0), + QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + emit currentMatchChanged(currentMatch()); +} + +void AutoTypeMatchView::filterList(const QString& filter) +{ + m_sortModel->setFilterWildcard(filter); + setCurrentIndex(m_sortModel->index(0, 0)); +} + +AutoTypeMatch AutoTypeMatchView::currentMatch() +{ + QModelIndexList list = selectionModel()->selectedRows(); + if (list.size() == 1) { + return m_model->matchFromIndex(m_sortModel->mapToSource(list.first())); + } + return {}; +} + +AutoTypeMatch AutoTypeMatchView::matchFromIndex(const QModelIndex& index) +{ + if (index.isValid()) { + return m_model->matchFromIndex(m_sortModel->mapToSource(index)); + } + return {}; +} + +void AutoTypeMatchView::currentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + auto match = matchFromIndex(current); + emit currentMatchChanged(match); + QTableView::currentChanged(current, previous); +} diff --git a/src/gui/entry/AutoTypeMatchView.h b/src/autotype/AutoTypeMatchView.h similarity index 72% rename from src/gui/entry/AutoTypeMatchView.h rename to src/autotype/AutoTypeMatchView.h index 69d0795d9d..90442baa6e 100644 --- a/src/gui/entry/AutoTypeMatchView.h +++ b/src/autotype/AutoTypeMatchView.h @@ -19,42 +19,37 @@ #ifndef KEEPASSX_AUTOTYPEMATCHVIEW_H #define KEEPASSX_AUTOTYPEMATCHVIEW_H -#include +#include -#include "core/AutoTypeMatch.h" +#include "autotype/AutoTypeMatch.h" +#include "autotype/AutoTypeMatchModel.h" -#include "gui/entry/AutoTypeMatchModel.h" +class QSortFilterProxyModel; -class SortFilterHideProxyModel; - -class AutoTypeMatchView : public QTreeView +class AutoTypeMatchView : public QTableView { Q_OBJECT public: explicit AutoTypeMatchView(QWidget* parent = nullptr); AutoTypeMatch currentMatch(); - void setCurrentMatch(const AutoTypeMatch& match); AutoTypeMatch matchFromIndex(const QModelIndex& index); void setMatchList(const QList& matches); - void setFirstMatchActive(); + void filterList(const QString& filter); signals: + void currentMatchChanged(AutoTypeMatch match); void matchActivated(AutoTypeMatch match); - void matchSelectionChanged(); - void matchTextCopied(); protected: void keyPressEvent(QKeyEvent* event) override; -private slots: - void emitMatchActivated(const QModelIndex& index); - void userNameCopied(); - void passwordCopied(); +protected slots: + void currentChanged(const QModelIndex& current, const QModelIndex& previous) override; private: AutoTypeMatchModel* const m_model; - SortFilterHideProxyModel* const m_sortModel; + QSortFilterProxyModel* const m_sortModel; }; #endif // KEEPASSX_AUTOTYPEMATCHVIEW_H diff --git a/src/autotype/AutoTypeSelectDialog.cpp b/src/autotype/AutoTypeSelectDialog.cpp index 514d3cf557..7b650ecde3 100644 --- a/src/autotype/AutoTypeSelectDialog.cpp +++ b/src/autotype/AutoTypeSelectDialog.cpp @@ -1,6 +1,6 @@ /* - * Copyright (C) 2012 Felix Geyer * Copyright (C) 2020 KeePassXC Team + * Copyright (C) 2012 Felix Geyer * * 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 @@ -17,168 +17,319 @@ */ #include "AutoTypeSelectDialog.h" +#include "ui_AutoTypeSelectDialog.h" #include +#include +#include +#include +#include #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #include #else #include #endif -#include -#include -#include -#include -#include -#include -#include "autotype/AutoTypeSelectView.h" -#include "core/AutoTypeMatch.h" #include "core/Config.h" +#include "core/Database.h" +#include "core/Entry.h" +#include "core/EntrySearcher.h" +#include "gui/Clipboard.h" #include "gui/Icons.h" -#include "gui/entry/AutoTypeMatchModel.h" AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) : QDialog(parent) - , m_view(new AutoTypeSelectView(this)) - , m_filterLineEdit(new AutoTypeFilterLineEdit(this)) - , m_matchActivatedEmitted(false) - , m_rejected(false) + , m_ui(new Ui::AutoTypeSelectDialog()) { setAttribute(Qt::WA_DeleteOnClose); // Places the window on the active (virtual) desktop instead of where the main window is. setAttribute(Qt::WA_X11BypassTransientForHint); - setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); - setWindowTitle(tr("Auto-Type - KeePassXC")); + setWindowFlags((windowFlags() | Qt::WindowStaysOnTopHint) & ~Qt::WindowContextHelpButtonHint); setWindowIcon(icons()->applicationIcon()); -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QRect screenGeometry = QApplication::screenAt(QCursor::pos())->availableGeometry(); -#else - QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos()); -#endif - QSize size = config()->get(Config::GUI_AutoTypeSelectDialogSize).toSize(); - size.setWidth(qMin(size.width(), screenGeometry.width())); - size.setHeight(qMin(size.height(), screenGeometry.height())); - resize(size); - - // move dialog to the center of the screen - QPoint screenCenter = screenGeometry.center(); - move(screenCenter.x() - (size.width() / 2), screenCenter.y() - (size.height() / 2)); - - QVBoxLayout* layout = new QVBoxLayout(this); - - QLabel* descriptionLabel = new QLabel(tr("Select entry to Auto-Type:"), this); - layout->addWidget(descriptionLabel); - - // clang-format off - connect(m_view, SIGNAL(activated(QModelIndex)), SLOT(emitMatchActivated(QModelIndex))); - connect(m_view, SIGNAL(clicked(QModelIndex)), SLOT(emitMatchActivated(QModelIndex))); - connect(m_view->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(matchRemoved())); - connect(m_view, SIGNAL(rejected()), SLOT(reject())); - connect(m_view, SIGNAL(matchTextCopied()), SLOT(reject())); - // clang-format on - - QSortFilterProxyModel* proxy = qobject_cast(m_view->model()); - if (proxy) { - proxy->setFilterKeyColumn(-1); - proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); - } + buildActionMenu(); - layout->addWidget(m_view); + m_ui->setupUi(this); - connect(m_filterLineEdit, SIGNAL(textChanged(QString)), SLOT(filterList(QString))); - connect(m_filterLineEdit, SIGNAL(returnPressed()), SLOT(activateCurrentIndex())); - connect(m_filterLineEdit, SIGNAL(keyUpPressed()), SLOT(moveSelectionUp())); - connect(m_filterLineEdit, SIGNAL(keyDownPressed()), SLOT(moveSelectionDown())); - connect(m_filterLineEdit, SIGNAL(escapeReleased()), SLOT(reject())); + connect(m_ui->view, &AutoTypeMatchView::matchActivated, this, &AutoTypeSelectDialog::submitAutoTypeMatch); + connect(m_ui->view, &AutoTypeMatchView::currentMatchChanged, this, &AutoTypeSelectDialog::updateActionMenu); + connect(m_ui->view, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) { + if (m_ui->view->currentMatch().first) { + m_actionMenu->popup(m_ui->view->viewport()->mapToGlobal(pos)); + } + }); - m_filterLineEdit->setPlaceholderText(tr("Search...")); - layout->addWidget(m_filterLineEdit); - QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, Qt::Horizontal, this); - connect(buttonBox, SIGNAL(rejected()), SLOT(reject())); - layout->addWidget(buttonBox); + m_ui->search->setFocus(); + m_ui->search->installEventFilter(this); - m_filterLineEdit->setFocus(); -} + m_searchTimer.setInterval(300); + m_searchTimer.setSingleShot(true); -void AutoTypeSelectDialog::setMatchList(const QList& matchList) -{ - m_view->setMatchList(matchList); + connect(m_ui->search, SIGNAL(textChanged(QString)), &m_searchTimer, SLOT(start())); + connect(m_ui->search, SIGNAL(returnPressed()), SLOT(activateCurrentMatch())); + connect(&m_searchTimer, SIGNAL(timeout()), SLOT(performSearch())); - m_view->header()->resizeSections(QHeaderView::ResizeToContents); -} + connect(m_ui->filterRadio, &QRadioButton::toggled, this, [this](bool checked) { + if (checked) { + // Reset to original match list + m_ui->view->setMatchList(m_matches); + performSearch(); + m_ui->search->setFocus(); + } + }); + connect(m_ui->searchRadio, &QRadioButton::toggled, this, [this](bool checked) { + if (checked) { + performSearch(); + m_ui->search->setFocus(); + } + }); -void AutoTypeSelectDialog::done(int r) -{ - config()->set(Config::GUI_AutoTypeSelectDialogSize, size()); + m_actionMenu->installEventFilter(this); + m_ui->action->setMenu(m_actionMenu); + m_ui->action->installEventFilter(this); + connect(m_ui->action, &QToolButton::clicked, this, &AutoTypeSelectDialog::activateCurrentMatch); - QDialog::done(r); + connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(reject())); } -void AutoTypeSelectDialog::reject() +void AutoTypeSelectDialog::setMatches(const QList& matches, const QList>& dbs) { - m_rejected = true; + m_matches = matches; + m_dbs = dbs; - QDialog::reject(); + m_ui->view->setMatchList(m_matches); + if (m_matches.isEmpty()) { + m_ui->searchRadio->setChecked(true); + } else { + m_ui->filterRadio->setChecked(true); + } } -void AutoTypeSelectDialog::emitMatchActivated(const QModelIndex& index) +void AutoTypeSelectDialog::submitAutoTypeMatch(AutoTypeMatch match) { - // make sure we don't emit the signal twice when both activated() and clicked() are triggered - if (m_matchActivatedEmitted) { - return; - } - m_matchActivatedEmitted = true; - - AutoTypeMatch match = m_view->matchFromIndex(index); accept(); + m_accepted = true; emit matchActivated(match); } -void AutoTypeSelectDialog::matchRemoved() +void AutoTypeSelectDialog::performSearch() { - if (m_rejected) { + if (m_ui->filterRadio->isChecked()) { + m_ui->view->filterList(m_ui->search->text()); return; } - - if (m_view->model()->rowCount() == 0 && m_filterLineEdit->text().isEmpty()) { - reject(); + if (m_ui->search->text().isEmpty()) { + m_ui->view->setMatchList({}); + return; } -} -void AutoTypeSelectDialog::filterList(QString filterString) -{ - QSortFilterProxyModel* proxy = qobject_cast(m_view->model()); - if (proxy) { - proxy->setFilterWildcard(filterString); - if (!m_view->currentIndex().isValid()) { - m_view->setCurrentIndex(m_view->model()->index(0, 0)); + EntrySearcher searcher; + QList matches; + for (const auto& db : m_dbs) { + auto found = searcher.search(m_ui->search->text(), db->rootGroup()); + for (auto entry : found) { + QSet sequences; + auto defSequence = entry->effectiveAutoTypeSequence(); + if (!defSequence.isEmpty()) { + matches.append({entry, defSequence}); + sequences << defSequence; + } + for (auto assoc : entry->autoTypeAssociations()->getAll()) { + if (!sequences.contains(assoc.sequence) && !assoc.sequence.isEmpty()) { + matches.append({entry, assoc.sequence}); + sequences << assoc.sequence; + } + } } } + + m_ui->view->setMatchList(matches); } void AutoTypeSelectDialog::moveSelectionUp() { - auto current = m_view->currentIndex(); + auto current = m_ui->view->currentIndex(); auto previous = current.sibling(current.row() - 1, 0); if (previous.isValid()) { - m_view->setCurrentIndex(previous); + m_ui->view->setCurrentIndex(previous); } } void AutoTypeSelectDialog::moveSelectionDown() { - auto current = m_view->currentIndex(); + auto current = m_ui->view->currentIndex(); auto next = current.sibling(current.row() + 1, 0); if (next.isValid()) { - m_view->setCurrentIndex(next); + m_ui->view->setCurrentIndex(next); + } +} + +void AutoTypeSelectDialog::activateCurrentMatch() +{ + submitAutoTypeMatch(m_ui->view->currentMatch()); +} + +bool AutoTypeSelectDialog::eventFilter(QObject* obj, QEvent* event) +{ + if (obj == m_ui->action) { + if (event->type() == QEvent::FocusIn) { + m_ui->action->showMenu(); + return true; + } else if (event->type() == QEvent::KeyPress && static_cast(event)->key() == Qt::Key_Return) { + // handle case where the menu is closed but the button has focus + activateCurrentMatch(); + return true; + } + } else if (obj == m_actionMenu) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + switch (keyEvent->key()) { + case Qt::Key_Tab: + m_actionMenu->close(); + focusNextPrevChild(true); + return true; + case Qt::Key_Backtab: + m_actionMenu->close(); + focusNextPrevChild(false); + return true; + case Qt::Key_Return: + // accept the dialog with default sequence if no action selected + if (!m_actionMenu->activeAction()) { + activateCurrentMatch(); + return true; + } + default: + break; + } + } + } else if (obj == m_ui->search) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + switch (keyEvent->key()) { + case Qt::Key_Up: + moveSelectionUp(); + return true; + case Qt::Key_Down: + moveSelectionDown(); + return true; + case Qt::Key_Escape: + if (m_ui->search->text().isEmpty()) { + reject(); + } else { + m_ui->search->clear(); + } + return true; + default: + break; + } + } + } + + return QDialog::eventFilter(obj, event); +} + +void AutoTypeSelectDialog::updateActionMenu(AutoTypeMatch match) +{ + if (!match.first) { + m_ui->action->setEnabled(false); + return; } + + m_ui->action->setEnabled(true); + + bool hasUsername = !match.first->username().isEmpty(); + bool hasPassword = !match.first->password().isEmpty(); + bool hasTotp = match.first->hasTotp(); + + auto actions = m_actionMenu->actions(); + Q_ASSERT(actions.size() >= 6); + actions[0]->setEnabled(hasUsername); + actions[1]->setEnabled(hasPassword); + actions[2]->setEnabled(hasTotp); + actions[3]->setEnabled(hasUsername); + actions[4]->setEnabled(hasPassword); + actions[5]->setEnabled(hasTotp); } -void AutoTypeSelectDialog::activateCurrentIndex() +void AutoTypeSelectDialog::buildActionMenu() { - emitMatchActivated(m_view->currentIndex()); + m_actionMenu = new QMenu(this); + auto typeUsernameAction = new QAction(icons()->icon("auto-type"), tr("Type {USERNAME}"), this); + auto typePasswordAction = new QAction(icons()->icon("auto-type"), tr("Type {PASSWORD}"), this); + auto typeTotpAction = new QAction(icons()->icon("auto-type"), tr("Type {TOTP}"), this); + auto copyUsernameAction = new QAction(icons()->icon("username-copy"), tr("Copy Username"), this); + auto copyPasswordAction = new QAction(icons()->icon("password-copy"), tr("Copy Password"), this); + auto copyTotpAction = new QAction(icons()->icon("chronometer"), tr("Copy TOTP"), this); + m_actionMenu->addAction(typeUsernameAction); + m_actionMenu->addAction(typePasswordAction); + m_actionMenu->addAction(typeTotpAction); + m_actionMenu->addAction(copyUsernameAction); + m_actionMenu->addAction(copyPasswordAction); + m_actionMenu->addAction(copyTotpAction); + + connect(typeUsernameAction, &QAction::triggered, this, [&] { + auto match = m_ui->view->currentMatch(); + match.second = QStringLiteral("{USERNAME}"); + submitAutoTypeMatch(match); + }); + connect(typePasswordAction, &QAction::triggered, this, [&] { + auto match = m_ui->view->currentMatch(); + match.second = QStringLiteral("{PASSWORD}"); + submitAutoTypeMatch(match); + }); + connect(typeTotpAction, &QAction::triggered, this, [&] { + auto match = m_ui->view->currentMatch(); + match.second = QStringLiteral("{TOTP}"); + submitAutoTypeMatch(match); + }); + + connect(copyUsernameAction, &QAction::triggered, this, [&] { + clipboard()->setText(m_ui->view->currentMatch().first->username()); + reject(); + }); + connect(copyPasswordAction, &QAction::triggered, this, [&] { + clipboard()->setText(m_ui->view->currentMatch().first->password()); + reject(); + }); + connect(copyTotpAction, &QAction::triggered, this, [&] { + clipboard()->setText(m_ui->view->currentMatch().first->totp()); + reject(); + }); +} + +void AutoTypeSelectDialog::showEvent(QShowEvent* event) +{ + QDialog::showEvent(event); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + auto screen = QApplication::screenAt(QCursor::pos()); + if (!screen) { + // screenAt can return a nullptr, default to the primary screen + screen = QApplication::primaryScreen(); + } + QRect screenGeometry = screen->availableGeometry(); +#else + QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos()); +#endif + + // Resize to last used size + QSize size = config()->get(Config::GUI_AutoTypeSelectDialogSize).toSize(); + size.setWidth(qMin(size.width(), screenGeometry.width())); + size.setHeight(qMin(size.height(), screenGeometry.height())); + resize(size); + + // move dialog to the center of the screen + move(screenGeometry.center().x() - (size.width() / 2), screenGeometry.center().y() - (size.height() / 2)); +} + +void AutoTypeSelectDialog::closeEvent(QCloseEvent* event) +{ + config()->set(Config::GUI_AutoTypeSelectDialogSize, size()); + if (!m_accepted) { + emit rejected(); + } + QDialog::closeEvent(event); } diff --git a/src/autotype/AutoTypeSelectDialog.h b/src/autotype/AutoTypeSelectDialog.h index a22218f1f0..71595b5462 100644 --- a/src/autotype/AutoTypeSelectDialog.h +++ b/src/autotype/AutoTypeSelectDialog.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2020 Team KeePassXC * Copyright (C) 2012 Felix Geyer * * This program is free software: you can redistribute it and/or modify @@ -18,14 +19,17 @@ #ifndef KEEPASSX_AUTOTYPESELECTDIALOG_H #define KEEPASSX_AUTOTYPESELECTDIALOG_H -#include +#include "autotype/AutoTypeMatch.h" #include -#include +#include -#include "autotype/AutoTypeFilterLineEdit.h" -#include "core/AutoTypeMatch.h" +class Database; +class QMenu; -class AutoTypeSelectView; +namespace Ui +{ + class AutoTypeSelectDialog; +} class AutoTypeSelectDialog : public QDialog { @@ -33,28 +37,36 @@ class AutoTypeSelectDialog : public QDialog public: explicit AutoTypeSelectDialog(QWidget* parent = nullptr); - void setMatchList(const QList& matchList); + void setMatches(const QList& matchList, const QList>& dbs); signals: void matchActivated(AutoTypeMatch match); -public slots: - void done(int r) override; - void reject() override; +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + void showEvent(QShowEvent* event) override; + void closeEvent(QCloseEvent* event) override; private slots: - void emitMatchActivated(const QModelIndex& index); - void matchRemoved(); - void filterList(QString filterString); + void submitAutoTypeMatch(AutoTypeMatch match); + void performSearch(); void moveSelectionUp(); void moveSelectionDown(); - void activateCurrentIndex(); + void activateCurrentMatch(); + void updateActionMenu(AutoTypeMatch match); private: - AutoTypeSelectView* const m_view; - AutoTypeFilterLineEdit* const m_filterLineEdit; - bool m_matchActivatedEmitted; - bool m_rejected; + void buildActionMenu(); + + // For some reason QScopedPointer causes compiler errors here... + QSharedPointer m_ui; + + QList> m_dbs; + QList m_matches; + QTimer m_searchTimer; + QPointer m_actionMenu; + + bool m_accepted = false; }; #endif // KEEPASSX_AUTOTYPESELECTDIALOG_H diff --git a/src/autotype/AutoTypeSelectDialog.ui b/src/autotype/AutoTypeSelectDialog.ui new file mode 100644 index 0000000000..4e9449a4be --- /dev/null +++ b/src/autotype/AutoTypeSelectDialog.ui @@ -0,0 +1,167 @@ + + + AutoTypeSelectDialog + + + + 0 + 0 + 418 + 295 + + + + Auto-Type - KeePassXC + + + + + + Double click a row to perform Auto-Type or find an entry using the search: + + + + + + + + 0 + 0 + + + + + 0 + 175 + + + + + + + + + 10 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + &Filter Matches + + + true + + + + + + + &Search Database + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + 0 + + + + + 400 + 0 + + + + Filter or Search... + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Type Sequence + + + QToolButton::MenuButtonPopup + + + + + + + Cancel + + + false + + + + + + + + + + AutoTypeMatchView + QTableView +
autotype/AutoTypeMatchView.h
+
+
+ + view + filterRadio + searchRadio + search + + + +
diff --git a/src/autotype/AutoTypeSelectView.cpp b/src/autotype/AutoTypeSelectView.cpp deleted file mode 100644 index cfa1136713..0000000000 --- a/src/autotype/AutoTypeSelectView.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2012 Felix Geyer - * - * 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 "AutoTypeSelectView.h" - -#include -#include - -AutoTypeSelectView::AutoTypeSelectView(QWidget* parent) - : AutoTypeMatchView(parent) -{ - setMouseTracking(true); - setAllColumnsShowFocus(true); - - connect(model(), SIGNAL(modelReset()), SLOT(selectFirstMatch())); -} - -void AutoTypeSelectView::mouseMoveEvent(QMouseEvent* event) -{ - QModelIndex index = indexAt(event->pos()); - - if (index.isValid()) { - setCurrentIndex(index); - setCursor(Qt::PointingHandCursor); - } else { - unsetCursor(); - } - - AutoTypeMatchView::mouseMoveEvent(event); -} - -void AutoTypeSelectView::selectFirstMatch() -{ - QModelIndex index = model()->index(0, 0); - - if (index.isValid()) { - setCurrentIndex(index); - } -} - -void AutoTypeSelectView::keyReleaseEvent(QKeyEvent* e) -{ - if (e->key() == Qt::Key_Escape) { - emit rejected(); - } else { - e->ignore(); - } -} \ No newline at end of file diff --git a/src/autotype/AutoTypeSelectView.h b/src/autotype/AutoTypeSelectView.h deleted file mode 100644 index e6a2ec6529..0000000000 --- a/src/autotype/AutoTypeSelectView.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2012 Felix Geyer - * - * 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_AUTOTYPESELECTVIEW_H -#define KEEPASSX_AUTOTYPESELECTVIEW_H - -#include "gui/entry/AutoTypeMatchView.h" - -class AutoTypeSelectView : public AutoTypeMatchView -{ - Q_OBJECT - -public: - explicit AutoTypeSelectView(QWidget* parent = nullptr); - -protected: - void mouseMoveEvent(QMouseEvent* event) override; - void keyReleaseEvent(QKeyEvent* e) override; - -private slots: - void selectFirstMatch(); - -signals: - void rejected(); -}; - -#endif // KEEPASSX_AUTOTYPESELECTVIEW_H diff --git a/src/autotype/WildcardMatcher.cpp b/src/autotype/WildcardMatcher.cpp deleted file mode 100644 index b69425ee14..0000000000 --- a/src/autotype/WildcardMatcher.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2012 Felix Geyer - * - * 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 "WildcardMatcher.h" - -#include -#include - -const QChar WildcardMatcher::Wildcard = '*'; -const Qt::CaseSensitivity WildcardMatcher::Sensitivity = Qt::CaseInsensitive; - -WildcardMatcher::WildcardMatcher(QString text) - : m_text(std::move(text)) -{ -} - -bool WildcardMatcher::match(const QString& pattern) -{ - m_pattern = pattern; - - if (patternContainsWildcard()) { - return matchWithWildcards(); - } else { - return patternEqualsText(); - } -} - -bool WildcardMatcher::patternContainsWildcard() -{ - return m_pattern.contains(Wildcard); -} - -bool WildcardMatcher::patternEqualsText() -{ - return m_text.compare(m_pattern, Sensitivity) == 0; -} - -bool WildcardMatcher::matchWithWildcards() -{ - QStringList parts = m_pattern.split(Wildcard, QString::KeepEmptyParts); - Q_ASSERT(parts.size() >= 2); - - if (startOrEndDoesNotMatch(parts)) { - return false; - } - - return partsMatch(parts); -} - -bool WildcardMatcher::startOrEndDoesNotMatch(const QStringList& parts) -{ - return !m_text.startsWith(parts.first(), Sensitivity) || !m_text.endsWith(parts.last(), Sensitivity); -} - -bool WildcardMatcher::partsMatch(const QStringList& parts) -{ - int index = 0; - for (const QString& part : parts) { - int matchIndex = getMatchIndex(part, index); - if (noMatchFound(matchIndex)) { - return false; - } - index = calculateNewIndex(matchIndex, part.length()); - } - - return true; -} - -int WildcardMatcher::getMatchIndex(const QString& part, int startIndex) -{ - return m_text.indexOf(part, startIndex, Sensitivity); -} - -bool WildcardMatcher::noMatchFound(int index) -{ - return index == -1; -} - -int WildcardMatcher::calculateNewIndex(int matchIndex, int partLength) -{ - return matchIndex + partLength; -} diff --git a/src/autotype/WildcardMatcher.h b/src/autotype/WildcardMatcher.h deleted file mode 100644 index d8ee1dc79b..0000000000 --- a/src/autotype/WildcardMatcher.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2012 Felix Geyer - * - * 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_WILDCARDMATCHER_H -#define KEEPASSX_WILDCARDMATCHER_H - -#include - -class WildcardMatcher -{ -public: - explicit WildcardMatcher(QString text); - bool match(const QString& pattern); - - static const QChar Wildcard; - -private: - bool patternEqualsText(); - bool patternContainsWildcard(); - bool matchWithWildcards(); - bool startOrEndDoesNotMatch(const QStringList& parts); - bool partsMatch(const QStringList& parts); - int getMatchIndex(const QString& part, int startIndex); - bool noMatchFound(int index); - int calculateNewIndex(int matchIndex, int partLength); - - static const Qt::CaseSensitivity Sensitivity; - const QString m_text; - QString m_pattern; -}; - -#endif // KEEPASSX_WILDCARDMATCHER_H diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index 2d458cb5dd..b22049acba 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -70,6 +70,10 @@ BrowserService::BrowserService() , m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224")) { connect(m_browserHost, &BrowserHost::clientMessageReceived, this, &BrowserService::processClientMessage); + connect(getMainWindow(), &MainWindow::databaseUnlocked, this, &BrowserService::databaseUnlocked); + connect(getMainWindow(), &MainWindow::databaseLocked, this, &BrowserService::databaseLocked); + connect(getMainWindow(), &MainWindow::activeDatabaseChanged, this, &BrowserService::activeDatabaseChanged); + setEnabled(browserSettings()->isEnabled()); } diff --git a/src/core/AutoTypeMatch.cpp b/src/core/AutoTypeMatch.cpp deleted file mode 100644 index 13b037bb61..0000000000 --- a/src/core/AutoTypeMatch.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2015 David Wu - * Copyright (C) 2017 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 "AutoTypeMatch.h" - -#include - -AutoTypeMatch::AutoTypeMatch() - : entry(nullptr) - , sequence() -{ -} - -AutoTypeMatch::AutoTypeMatch(Entry* entry, QString sequence) - : entry(entry) - , sequence(std::move(sequence)) -{ -} - -bool AutoTypeMatch::operator==(const AutoTypeMatch& other) const -{ - return entry == other.entry && sequence == other.sequence; -} - -bool AutoTypeMatch::operator!=(const AutoTypeMatch& other) const -{ - return entry != other.entry || sequence != other.sequence; -} diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index bc31e9caf8..6e9e211a08 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -20,6 +20,7 @@ #include "config-keepassx.h" #include "core/Clock.h" +#include "core/Config.h" #include "core/Database.h" #include "core/DatabaseIcons.h" #include "core/Group.h" @@ -282,6 +283,70 @@ QString Entry::effectiveAutoTypeSequence() const return sequence; } +/** + * Retrive the autotype sequences matches for a given windowTitle + * This returns a list with priority ordering. If you don't want duplicates call .toSet() on it. + */ +QList Entry::autoTypeSequences(const QString& windowTitle) const +{ + auto windowMatches = [&](const QString& pattern) { + // Regex searching + if (pattern.startsWith("//") && pattern.endsWith("//") && pattern.size() >= 4) { + QRegExp regExp(pattern.mid(2, pattern.size() - 4), Qt::CaseInsensitive, QRegExp::RegExp2); + return (regExp.indexIn(windowTitle) != -1); + } + + // Wildcard searching + auto regex = Tools::convertToRegex(pattern, true, false, false); + return windowTitle.contains(regex); + }; + + auto windowMatchesTitle = [&](const QString& entryTitle) { + return !entryTitle.isEmpty() && windowTitle.contains(entryTitle, Qt::CaseInsensitive); + }; + + auto windowMatchesUrl = [&](const QString& entryUrl) { + if (!entryUrl.isEmpty() && windowTitle.contains(entryUrl, Qt::CaseInsensitive)) { + return true; + } + + QUrl url(entryUrl); + if (url.isValid() && !url.host().isEmpty()) { + return windowTitle.contains(url.host(), Qt::CaseInsensitive); + } + + return false; + }; + + QList sequenceList; + if (!windowTitle.isEmpty()) { + const auto assocList = autoTypeAssociations()->getAll(); + for (const auto& assoc : assocList) { + auto window = resolveMultiplePlaceholders(assoc.window); + if (windowMatches(window)) { + if (!assoc.sequence.isEmpty()) { + sequenceList.append(assoc.sequence); + } else { + sequenceList.append(effectiveAutoTypeSequence()); + } + } + } + + if (config()->get(Config::AutoTypeEntryTitleMatch).toBool() + && windowMatchesTitle(resolvePlaceholder(title()))) { + sequenceList.append(effectiveAutoTypeSequence()); + } + + if (config()->get(Config::AutoTypeEntryURLMatch).toBool() && windowMatchesUrl(resolvePlaceholder(url()))) { + sequenceList.append(effectiveAutoTypeSequence()); + } + } else { + sequenceList.append(effectiveAutoTypeSequence()); + } + + return sequenceList; +} + AutoTypeAssociations* Entry::autoTypeAssociations() { return m_autoTypeAssociations; diff --git a/src/core/Entry.h b/src/core/Entry.h index e76ee9acbf..5a4631127c 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -95,6 +95,7 @@ class Entry : public QObject QString defaultAutoTypeSequence() const; QString effectiveAutoTypeSequence() const; QString effectiveNewAutoTypeSequence() const; + QList autoTypeSequences(const QString& pattern = {}) const; AutoTypeAssociations* autoTypeAssociations(); const AutoTypeAssociations* autoTypeAssociations() const; QString title() const; diff --git a/src/fdosecrets/objects/Service.cpp b/src/fdosecrets/objects/Service.cpp index 957203d8b8..d0a77924f9 100644 --- a/src/fdosecrets/objects/Service.cpp +++ b/src/fdosecrets/objects/Service.cpp @@ -99,7 +99,7 @@ namespace FdoSecrets }); // make default alias track current activated database - connect(m_databases.data(), &DatabaseTabWidget::activateDatabaseChanged, this, &Service::ensureDefaultAlias); + connect(m_databases.data(), &DatabaseTabWidget::activeDatabaseChanged, this, &Service::ensureDefaultAlias); return true; } diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 2683cecec1..6f79873af8 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -57,8 +57,8 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent) // clang-format off connect(this, SIGNAL(tabCloseRequested(int)), SLOT(closeDatabaseTab(int))); - connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged())); - connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), + connect(this, SIGNAL(currentChanged(int)), SLOT(emitActiveDatabaseChanged())); + connect(this, SIGNAL(activeDatabaseChanged(DatabaseWidget*)), m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*))); connect(autoType(), SIGNAL(globalAutoTypeTriggered()), SLOT(performGlobalAutoType())); connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase())); @@ -715,9 +715,9 @@ void DatabaseTabWidget::updateLastDatabases(const QString& filename) } } -void DatabaseTabWidget::emitActivateDatabaseChanged() +void DatabaseTabWidget::emitActiveDatabaseChanged() { - emit activateDatabaseChanged(currentDatabaseWidget()); + emit activeDatabaseChanged(currentDatabaseWidget()); } void DatabaseTabWidget::emitDatabaseLockChanged() diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index e59681ea73..fbb6411f88 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -89,7 +89,7 @@ public slots: void databaseClosed(const QString& filePath); void databaseUnlocked(DatabaseWidget* dbWidget); void databaseLocked(DatabaseWidget* dbWidget); - void activateDatabaseChanged(DatabaseWidget* dbWidget); + void activeDatabaseChanged(DatabaseWidget* dbWidget); void tabNameChanged(); void tabVisibilityChanged(bool tabsVisible); void messageGlobal(const QString&, MessageWidget::MessageType type); @@ -98,7 +98,7 @@ public slots: private slots: void toggleTabbar(); - void emitActivateDatabaseChanged(); + void emitActiveDatabaseChanged(); void emitDatabaseLockChanged(); private: diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 83ef4cee21..6916085968 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -159,14 +159,13 @@ MainWindow::MainWindow() restoreGeometry(config()->get(Config::GUI_MainWindowGeometry).toByteArray()); restoreState(config()->get(Config::GUI_MainWindowState).toByteArray()); + + connect(m_ui->tabWidget, &DatabaseTabWidget::databaseLocked, this, &MainWindow::databaseLocked); + connect(m_ui->tabWidget, &DatabaseTabWidget::databaseUnlocked, this, &MainWindow::databaseUnlocked); + connect(m_ui->tabWidget, &DatabaseTabWidget::activeDatabaseChanged, this, &MainWindow::activeDatabaseChanged); + #ifdef WITH_XC_BROWSER m_ui->settingsWidget->addSettingsPage(new BrowserSettingsPage()); - connect(m_ui->tabWidget, &DatabaseTabWidget::databaseLocked, browserService(), &BrowserService::databaseLocked); - connect(m_ui->tabWidget, &DatabaseTabWidget::databaseUnlocked, browserService(), &BrowserService::databaseUnlocked); - connect(m_ui->tabWidget, - &DatabaseTabWidget::activateDatabaseChanged, - browserService(), - &BrowserService::activeDatabaseChanged); connect( browserService(), &BrowserService::requestUnlock, m_ui->tabWidget, &DatabaseTabWidget::performBrowserUnlock); #endif @@ -410,7 +409,7 @@ MainWindow::MainWindow() // Notify search when the active database changes or gets locked connect(m_ui->tabWidget, - SIGNAL(activateDatabaseChanged(DatabaseWidget*)), + SIGNAL(activeDatabaseChanged(DatabaseWidget*)), m_searchWidget, SLOT(databaseChanged(DatabaseWidget*))); connect(m_ui->tabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), m_searchWidget, SLOT(databaseChanged())); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 187b36d55d..eeca558084 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -59,6 +59,11 @@ class MainWindow : public QMainWindow PasswordGeneratorScreen = 3 }; +signals: + void databaseUnlocked(DatabaseWidget* dbWidget); + void databaseLocked(DatabaseWidget* dbWidget); + void activeDatabaseChanged(DatabaseWidget* dbWidget); + public slots: void openDatabase(const QString& filePath, const QString& password = {}, const QString& keyfile = {}); void appExit(); @@ -135,8 +140,6 @@ private slots: void obtainContextFocusLock(); void releaseContextFocusLock(); void agentEnabled(bool enabled); - -private slots: void updateTrayIcon(); private: diff --git a/src/gui/SearchHelpWidget.ui b/src/gui/SearchHelpWidget.ui index ebc62e991d..bd8731eb9f 100644 --- a/src/gui/SearchHelpWidget.ui +++ b/src/gui/SearchHelpWidget.ui @@ -118,7 +118,7 @@ - + 10 diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index 31ee7e9b4c..3bb95d2d66 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -51,8 +51,8 @@ SearchWidget::SearchWidget(QWidget* parent) connect(m_clearSearchTimer, SIGNAL(timeout()), m_ui->searchEdit, SLOT(clear())); connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear())); - new QShortcut(QKeySequence::Find, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut); - new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut); + new QShortcut(QKeySequence::Find, this, SLOT(searchFocus())); + new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear())); m_ui->searchEdit->setPlaceholderText(tr("Search (%1)...", "Search placeholder text, %1 is the keyboard shortcut") .arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText))); diff --git a/src/gui/entry/AutoTypeMatchView.cpp b/src/gui/entry/AutoTypeMatchView.cpp deleted file mode 100644 index 72ee32fde6..0000000000 --- a/src/gui/entry/AutoTypeMatchView.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2015 David Wu - * Copyright (C) 2017 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 "AutoTypeMatchView.h" - -#include "core/Entry.h" -#include "gui/Clipboard.h" -#include "gui/SortFilterHideProxyModel.h" - -#include -#include -#include - -AutoTypeMatchView::AutoTypeMatchView(QWidget* parent) - : QTreeView(parent) - , m_model(new AutoTypeMatchModel(this)) - , m_sortModel(new SortFilterHideProxyModel(this)) -{ - m_sortModel->setSourceModel(m_model); - m_sortModel->setDynamicSortFilter(true); - m_sortModel->setSortLocaleAware(true); - m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive); - setModel(m_sortModel); - - setUniformRowHeights(true); - setRootIsDecorated(false); - setAlternatingRowColors(true); - setDragEnabled(false); - setSortingEnabled(true); - setSelectionMode(QAbstractItemView::SingleSelection); - header()->setDefaultSectionSize(150); - - setContextMenuPolicy(Qt::ActionsContextMenu); - auto* copyUserNameAction = new QAction(tr("Copy &username"), this); - auto* copyPasswordAction = new QAction(tr("Copy &password"), this); - addAction(copyUserNameAction); - addAction(copyPasswordAction); - - connect(copyUserNameAction, SIGNAL(triggered()), this, SLOT(userNameCopied())); - connect(copyPasswordAction, SIGNAL(triggered()), this, SLOT(passwordCopied())); - - connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitMatchActivated(QModelIndex))); - // clang-format off - connect(selectionModel(), - SIGNAL(selectionChanged(QItemSelection,QItemSelection)), - SIGNAL(matchSelectionChanged())); - // clang-format on -} - -void AutoTypeMatchView::userNameCopied() -{ - clipboard()->setText(currentMatch().entry->username()); - emit matchTextCopied(); -} - -void AutoTypeMatchView::passwordCopied() -{ - clipboard()->setText(currentMatch().entry->password()); - emit matchTextCopied(); -} - -void AutoTypeMatchView::keyPressEvent(QKeyEvent* event) -{ - if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && currentIndex().isValid()) { - emitMatchActivated(currentIndex()); -#ifdef Q_OS_MACOS - // Pressing return does not emit the QTreeView::activated signal on mac os - emit activated(currentIndex()); -#endif - } - - QTreeView::keyPressEvent(event); -} - -void AutoTypeMatchView::setMatchList(const QList& matches) -{ - m_model->setMatchList(matches); - - bool sameSequences = true; - if (matches.count() > 1) { - QString sequenceTest = matches[0].sequence; - for (const auto& match : matches) { - if (match.sequence != sequenceTest) { - sameSequences = false; - break; - } - } - } - setColumnHidden(AutoTypeMatchModel::Sequence, sameSequences); - - for (int i = 0; i < m_model->columnCount(); ++i) { - resizeColumnToContents(i); - if (columnWidth(i) > 250) { - setColumnWidth(i, 250); - } - } - - setFirstMatchActive(); -} - -void AutoTypeMatchView::setFirstMatchActive() -{ - if (m_model->rowCount() > 0) { - QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(0, 0)); - setCurrentMatch(m_model->matchFromIndex(index)); - } else { - emit matchSelectionChanged(); - } -} - -void AutoTypeMatchView::emitMatchActivated(const QModelIndex& index) -{ - AutoTypeMatch match = matchFromIndex(index); - - emit matchActivated(match); -} - -AutoTypeMatch AutoTypeMatchView::currentMatch() -{ - QModelIndexList list = selectionModel()->selectedRows(); - if (list.size() == 1) { - return m_model->matchFromIndex(m_sortModel->mapToSource(list.first())); - } - return AutoTypeMatch(); -} - -void AutoTypeMatchView::setCurrentMatch(const AutoTypeMatch& match) -{ - selectionModel()->setCurrentIndex(m_sortModel->mapFromSource(m_model->indexFromMatch(match)), - QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); -} - -AutoTypeMatch AutoTypeMatchView::matchFromIndex(const QModelIndex& index) -{ - if (index.isValid()) { - return m_model->matchFromIndex(m_sortModel->mapToSource(index)); - } - return AutoTypeMatch(); -} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 28d8516cfe..b532acba94 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -149,9 +149,6 @@ add_unit_test(NAME testkeepass1reader SOURCES TestKeePass1Reader.cpp add_unit_test(NAME testopvaultreader SOURCES TestOpVaultReader.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testwildcardmatcher SOURCES TestWildcardMatcher.cpp - LIBS ${TEST_LIBRARIES}) - if(WITH_XC_NETWORKING) add_unit_test(NAME testupdatecheck SOURCES TestUpdateCheck.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/TestWildcardMatcher.cpp b/tests/TestWildcardMatcher.cpp deleted file mode 100644 index b7b92fdd78..0000000000 --- a/tests/TestWildcardMatcher.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2012 Felix Geyer - * - * 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 "TestWildcardMatcher.h" -#include "TestGlobal.h" -#include "autotype/WildcardMatcher.h" - -QTEST_GUILESS_MAIN(TestWildcardMatcher) - -const QString TestWildcardMatcher::DefaultText = QString("some text"); -const QString TestWildcardMatcher::AlternativeText = QString("some other text"); - -void TestWildcardMatcher::testMatcher_data() -{ - QTest::addColumn("text"); - QTest::addColumn("pattern"); - QTest::addColumn("match"); - - QTest::newRow("MatchWithoutWildcard") << DefaultText << DefaultText << true; - QTest::newRow("NoMatchWithoutWildcard") << DefaultText << QString("no match text") << false; - QTest::newRow("MatchWithOneWildcardInTheMiddle") << AlternativeText << QString("some*text") << true; - QTest::newRow("NoMatchWithOneWildcardInTheMiddle") << AlternativeText << QString("differen*text") << false; - QTest::newRow("MatchWithOneWildcardAtBegin") << DefaultText << QString("*text") << true; - QTest::newRow("NoMatchWithOneWildcardAtBegin") << DefaultText << QString("*text something else") << false; - QTest::newRow("NatchWithOneWildcardAtEnd") << DefaultText << QString("some*") << true; - QTest::newRow("NoMatchWithOneWildcardAtEnd") << DefaultText << QString("some other*") << false; - QTest::newRow("MatchWithMultipleWildcards") << AlternativeText << QString("some*th*text") << true; - QTest::newRow("NoMatchWithMultipleWildcards") << AlternativeText << QString("some*abc*text") << false; - QTest::newRow("MatchJustWildcard") << DefaultText << QString("*") << true; - QTest::newRow("MatchFollowingWildcards") << DefaultText << QString("some t**t") << true; - QTest::newRow("CaseSensitivity") << DefaultText.toUpper() << QString("some t**t") << true; -} - -void TestWildcardMatcher::testMatcher() -{ - QFETCH(QString, text); - QFETCH(QString, pattern); - QFETCH(bool, match); - - initMatcher(text); - verifyMatchResult(pattern, match); - cleanupMatcher(); -} - -void TestWildcardMatcher::initMatcher(QString text) -{ - m_matcher = new WildcardMatcher(text); -} - -void TestWildcardMatcher::cleanupMatcher() -{ - delete m_matcher; -} - -void TestWildcardMatcher::verifyMatchResult(QString pattern, bool expected) -{ - if (expected) { - verifyMatch(pattern); - } else { - verifyNoMatch(pattern); - } -} - -void TestWildcardMatcher::verifyMatch(QString pattern) -{ - bool matchResult = m_matcher->match(pattern); - QVERIFY(matchResult); -} - -void TestWildcardMatcher::verifyNoMatch(QString pattern) -{ - bool matchResult = m_matcher->match(pattern); - QVERIFY(!matchResult); -} diff --git a/tests/TestWildcardMatcher.h b/tests/TestWildcardMatcher.h deleted file mode 100644 index e237709370..0000000000 --- a/tests/TestWildcardMatcher.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2012 Felix Geyer - * - * 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_TESTWILDCARDMATCHER_H -#define KEEPASSX_TESTWILDCARDMATCHER_H - -#include - -class WildcardMatcher; - -class TestWildcardMatcher : public QObject -{ - Q_OBJECT - -private slots: - void testMatcher(); - void testMatcher_data(); - -private: - static const QString DefaultText; - static const QString AlternativeText; - - void initMatcher(QString text); - void cleanupMatcher(); - void verifyMatchResult(QString pattern, bool expected); - void verifyMatch(QString pattern); - void verifyNoMatch(QString pattern); - - WildcardMatcher* m_matcher; -}; - -#endif // KEEPASSX_TESTWILDCARDMATCHER_H