diff --git a/docs/images/autotype_selection_dialog.png b/docs/images/autotype_selection_dialog.png index 6f15a6ea6..ce94f6cfa 100644 Binary files a/docs/images/autotype_selection_dialog.png and b/docs/images/autotype_selection_dialog.png differ diff --git a/docs/images/autotype_selection_dialog_search.png b/docs/images/autotype_selection_dialog_search.png new file mode 100644 index 000000000..96d0fbeec Binary files /dev/null and b/docs/images/autotype_selection_dialog_search.png differ diff --git a/docs/images/autotype_selection_dialog_type_menu.png b/docs/images/autotype_selection_dialog_type_menu.png new file mode 100644 index 000000000..f0e83ee80 Binary files /dev/null and b/docs/images/autotype_selection_dialog_type_menu.png differ diff --git a/docs/topics/AutoType.adoc b/docs/topics/AutoType.adoc index 6e608a9ab..eb6a76909 100644 --- a/docs/topics/AutoType.adoc +++ b/docs/topics/AutoType.adoc @@ -69,12 +69,22 @@ image::autotype_entry_sequences.png[] === Performing Global Auto-Type The global Auto-Type keyboard shortcut is used when you have focus on the window you want to type into. To make use of this feature, you must have previously configured an Auto-Type hotkey. -Pressing the global Auto-Type hotkey cause KeePassXC to search the database for entries that match the window title. Multiple matches may be returned and will cause the sequence selection dialog to appear. Click on a sequence line will immediately execute the Auto-Type action. A search box is also available in case numerous matches are returned. +When you press the global Auto-Type hotkey, KeePassXC searches all unlocked databases for entries that match the focused window title. The Auto-Type selection dialog will appear in the following circumstances: there are no matches found, there are multiple matches found, or the setting "Always ask before performing Auto-Type" is enabled. .Auto-Type sequence selection image::autotype_selection_dialog.png[,70%] -TIP: The _Sequence_ column will only appear when there are different sequences defined by one or more entries displayed in the selection dialog. +Perform the selected Auto-Type sequence by double clicking the desired row or pressing _Enter_. Press the up and down arrows to navigate the list. Sequences can be filtered through the text edit field. + +.Auto-Type search database +image::autotype_selection_dialog_search.png[,70%] + +Search the unlocked databases by activating Search Database radio button. Use the text edit field to issue search queries using the same syntax as database searching. + +.Additional Auto-Type choices +image::autotype_selection_dialog_type_menu.png[,70%] + +The option to type just the username, password, or current TOTP value is available by right clicking the desired row or expanding the Type Sequence button options. === Performing Entry-Level Auto-Type You can quickly activate the default Auto-Type sequence for a particular entry using Entry-Level Auto-Type. For this operation, the KeePassXC window will be minimized and the Auto-Type sequence occurs in the previously selected window. You can perform Entry-Level Auto-Type from the toolbar icon *(A)*, entry context menu *(B)*, or by pressing `Ctrl+Shift+V`. diff --git a/docs/topics/DownloadInstall.adoc b/docs/topics/DownloadInstall.adoc index cc6f1fcb7..5f692638c 100644 --- a/docs/topics/DownloadInstall.adoc +++ b/docs/topics/DownloadInstall.adoc @@ -60,3 +60,5 @@ To install the KeePassXC app on macOS, double click on the downloaded DMG file a image::macos_install.png[,80%] // end::content[] +// tag::advanced[] +// end::advanced[] \ No newline at end of file diff --git a/docs/topics/Welcome.adoc b/docs/topics/Welcome.adoc index 507419029..4eb168e6c 100644 --- a/docs/topics/Welcome.adoc +++ b/docs/topics/Welcome.adoc @@ -44,3 +44,5 @@ KeePassXC has numerous features for novice and power users alike. This guide wil ** FreeDesktop.org Secret Service (replace Gnome keyring, etc.) ** Additional encryption choices: Twofish and ChaCha20 // end::content[] +// tag::advanced[] +// end::advanced[] \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b308df2f3..ba8acdc77 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 94dd432c9..7a52491b3 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) +void AutoType::resetAutoTypeState() { - 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() -{ - // 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 8d0cf3d14..0af78c4c5 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 @@ signals: private slots: void startGlobalAutoType(); - void performAutoTypeFromGlobal(AutoTypeMatch match); - void autoTypeRejectedFromGlobal(); void unloadPlugin(); private: @@ -89,11 +87,8 @@ private: 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 d94292d26..000000000 --- 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 6f8ca570d..000000000 --- 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 768cf1682..4f396546a 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) 2021 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -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 ac2d0f874..f15df233b 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 58b89465b..e5c86e84d 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 000000000..453d9072e --- /dev/null +++ b/src/autotype/AutoTypeMatchView.cpp @@ -0,0 +1,120 @@ +/* + * 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); + + sortByColumn(0, Qt::AscendingOrder); + + 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 69d0795d9..90442baa6 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 e91355f4a..836ba3070 100644 --- a/src/autotype/AutoTypeSelectDialog.cpp +++ b/src/autotype/AutoTypeSelectDialog.cpp @@ -1,6 +1,6 @@ /* + * Copyright (C) 2021 KeePassXC Team * Copyright (C) 2012 Felix Geyer - * 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 @@ -17,168 +17,324 @@ */ #include "AutoTypeSelectDialog.h" +#include "ui_AutoTypeSelectDialog.h" -#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()); + buildActionMenu(); + + m_ui->setupUi(this); + + 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_ui->search->setFocus(); + m_ui->search->installEventFilter(this); + + m_searchTimer.setInterval(300); + m_searchTimer.setSingleShot(true); + + 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())); + + 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(); + } + }); + + m_actionMenu->installEventFilter(this); + m_ui->action->setMenu(m_actionMenu); + m_ui->action->installEventFilter(this); + connect(m_ui->action, &QToolButton::clicked, this, &AutoTypeSelectDialog::activateCurrentMatch); + + connect(m_ui->cancelButton, SIGNAL(clicked()), SLOT(reject())); +} + +// Required for QScopedPointer +AutoTypeSelectDialog::~AutoTypeSelectDialog() +{ +} + +void AutoTypeSelectDialog::setMatches(const QList& matches, const QList>& dbs) +{ + m_matches = matches; + m_dbs = dbs; + + m_ui->view->setMatchList(m_matches); + if (m_matches.isEmpty()) { + m_ui->searchRadio->setChecked(true); + } else { + m_ui->filterRadio->setChecked(true); + } +} + +void AutoTypeSelectDialog::submitAutoTypeMatch(AutoTypeMatch match) +{ + m_accepted = true; + accept(); + emit matchActivated(std::move(match)); +} + +void AutoTypeSelectDialog::performSearch() +{ + if (m_ui->filterRadio->isChecked()) { + m_ui->view->filterList(m_ui->search->text()); + return; + } + + auto searchText = m_ui->search->text(); + // If no search text, find all entries + if (searchText.isEmpty()) { + searchText.append("*"); + } + + EntrySearcher searcher; + QList matches; + for (const auto& db : m_dbs) { + auto found = searcher.search(searchText, db->rootGroup()); + for (auto* entry : found) { + QSet sequences; + auto defSequence = entry->effectiveAutoTypeSequence(); + if (!defSequence.isEmpty()) { + matches.append({entry, defSequence}); + sequences << defSequence; + } + for (const 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_ui->view->currentIndex(); + auto previous = current.sibling(current.row() - 1, 0); + + if (previous.isValid()) { + m_ui->view->setCurrentIndex(previous); + } +} + +void AutoTypeSelectDialog::moveSelectionDown() +{ + auto current = m_ui->view->currentIndex(); + auto next = current.sibling(current.row() + 1, 0); + + if (next.isValid()) { + 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) { + auto* 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) { + auto* 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(const 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::buildActionMenu() +{ + 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 = "{USERNAME}"; + submitAutoTypeMatch(match); + }); + connect(typePasswordAction, &QAction::triggered, this, [&] { + auto match = m_ui->view->currentMatch(); + match.second = "{PASSWORD}"; + submitAutoTypeMatch(match); + }); + connect(typeTotpAction, &QAction::triggered, this, [&] { + auto match = m_ui->view->currentMatch(); + match.second = "{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) - QRect screenGeometry = QApplication::screenAt(QCursor::pos())->availableGeometry(); + 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 - 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); - } - - layout->addWidget(m_view); - - 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())); - - 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_filterLineEdit->setFocus(); + move(screenGeometry.center().x() - (size.width() / 2), screenGeometry.center().y() - (size.height() / 2)); } -void AutoTypeSelectDialog::setMatchList(const QList& matchList) -{ - m_view->setMatchList(matchList); - - m_view->header()->resizeSections(QHeaderView::ResizeToContents); -} - -void AutoTypeSelectDialog::done(int r) +void AutoTypeSelectDialog::hideEvent(QHideEvent* event) { config()->set(Config::GUI_AutoTypeSelectDialogSize, size()); - - QDialog::done(r); -} - -void AutoTypeSelectDialog::reject() -{ - m_rejected = true; - - QDialog::reject(); -} - -void AutoTypeSelectDialog::emitMatchActivated(const QModelIndex& index) -{ - // make sure we don't emit the signal twice when both activated() and clicked() are triggered - if (m_matchActivatedEmitted) { - return; + if (!m_accepted) { + emit rejected(); } - m_matchActivatedEmitted = true; - - AutoTypeMatch match = m_view->matchFromIndex(index); - accept(); - emit matchActivated(match); -} - -void AutoTypeSelectDialog::matchRemoved() -{ - if (m_rejected) { - return; - } - - if (m_view->model()->rowCount() == 0 && m_filterLineEdit->text().isEmpty()) { - reject(); - } -} - -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)); - } - } -} - -void AutoTypeSelectDialog::moveSelectionUp() -{ - auto current = m_view->currentIndex(); - auto previous = current.sibling(current.row() - 1, 0); - - if (previous.isValid()) { - m_view->setCurrentIndex(previous); - } -} - -void AutoTypeSelectDialog::moveSelectionDown() -{ - auto current = m_view->currentIndex(); - auto next = current.sibling(current.row() + 1, 0); - - if (next.isValid()) { - m_view->setCurrentIndex(next); - } -} - -void AutoTypeSelectDialog::activateCurrentIndex() -{ - emitMatchActivated(m_view->currentIndex()); + QDialog::hideEvent(event); } diff --git a/src/autotype/AutoTypeSelectDialog.h b/src/autotype/AutoTypeSelectDialog.h index a22218f1f..2adf67424 100644 --- a/src/autotype/AutoTypeSelectDialog.h +++ b/src/autotype/AutoTypeSelectDialog.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2021 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,37 @@ class AutoTypeSelectDialog : public QDialog public: explicit AutoTypeSelectDialog(QWidget* parent = nullptr); - void setMatchList(const QList& matchList); + ~AutoTypeSelectDialog() override; + + 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 hideEvent(QHideEvent* 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(const AutoTypeMatch& match); private: - AutoTypeSelectView* const m_view; - AutoTypeFilterLineEdit* const m_filterLineEdit; - bool m_matchActivatedEmitted; - bool m_rejected; + void buildActionMenu(); + + QScopedPointer 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 000000000..2f77bd2b2 --- /dev/null +++ b/src/autotype/AutoTypeSelectDialog.ui @@ -0,0 +1,197 @@ + + + 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 + + + + PointingHandCursor + + + false + + + false + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + true + + + false + + + + + + + + 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 cfa113671..000000000 --- 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 e6a2ec652..000000000 --- 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 b69425ee1..000000000 --- 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 d8ee1dc79..000000000 --- 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 ae6d9f1f5..69367fbac 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 13b037bb6..000000000 --- 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 c749b03e4..61f48b780 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" @@ -280,6 +281,75 @@ 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 +{ + // If no window just return the effective sequence + if (windowTitle.isEmpty()) { + return {effectiveAutoTypeSequence()}; + } + + // Define helper functions to match window titles + 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; + + // Add window association matches + const auto assocList = autoTypeAssociations()->getAll(); + for (const auto& assoc : assocList) { + auto window = resolveMultiplePlaceholders(assoc.window); + if (windowMatches(window)) { + if (!assoc.sequence.isEmpty()) { + sequenceList << assoc.sequence; + } else { + sequenceList << effectiveAutoTypeSequence(); + } + } + } + + // Try to match window title + if (config()->get(Config::AutoTypeEntryTitleMatch).toBool() && windowMatchesTitle(resolvePlaceholder(title()))) { + sequenceList << effectiveAutoTypeSequence(); + } + + // Try to match url in window title + if (config()->get(Config::AutoTypeEntryURLMatch).toBool() && windowMatchesUrl(resolvePlaceholder(url()))) { + sequenceList << effectiveAutoTypeSequence(); + } + + return sequenceList; +} + AutoTypeAssociations* Entry::autoTypeAssociations() { return m_autoTypeAssociations; diff --git a/src/core/Entry.h b/src/core/Entry.h index a86aded10..be666b9a0 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -95,6 +95,7 @@ public: 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 38dc0aff6..603aaba1e 100644 --- a/src/fdosecrets/objects/Service.cpp +++ b/src/fdosecrets/objects/Service.cpp @@ -82,7 +82,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 2683cecec..6f79873af 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 e59681ea7..fbb6411f8 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -89,7 +89,7 @@ signals: 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 @@ signals: private slots: void toggleTabbar(); - void emitActivateDatabaseChanged(); + void emitActiveDatabaseChanged(); void emitDatabaseLockChanged(); private: diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index de19f37a9..5b40a9b8e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -160,14 +160,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 @@ -411,7 +410,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 63163c8d2..13e59b5ae 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -60,6 +60,11 @@ public: 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(); @@ -136,8 +141,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 ebc62e991..bd8731eb9 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 7daea45d8..787b544ce 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 72ee32fde..000000000 --- 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 28d8516cf..b532acba9 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 b7b92fdd7..000000000 --- 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 e23770937..000000000 --- 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