From b5cabbeb43fb655a93d2a4cd14fc98a40e356f58 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Tue, 16 Jan 2018 22:05:58 +0100 Subject: [PATCH] add support for multiple autotype sequence, fix #559 --- src/CMakeLists.txt | 3 + src/autotype/AutoType.cpp | 96 +++++------- src/autotype/AutoType.h | 6 +- src/autotype/AutoTypeSelectDialog.cpp | 31 ++-- src/autotype/AutoTypeSelectDialog.h | 14 +- src/autotype/AutoTypeSelectView.cpp | 11 +- src/autotype/AutoTypeSelectView.h | 8 +- src/core/AutoTypeMatch.cpp | 39 +++++ src/core/AutoTypeMatch.h | 41 ++++++ src/gui/entry/AutoTypeMatchModel.cpp | 202 ++++++++++++++++++++++++++ src/gui/entry/AutoTypeMatchModel.h | 66 +++++++++ src/gui/entry/AutoTypeMatchView.cpp | 116 +++++++++++++++ src/gui/entry/AutoTypeMatchView.h | 58 ++++++++ 13 files changed, 599 insertions(+), 92 deletions(-) create mode 100644 src/core/AutoTypeMatch.cpp create mode 100644 src/core/AutoTypeMatch.h create mode 100644 src/gui/entry/AutoTypeMatchModel.cpp create mode 100644 src/gui/entry/AutoTypeMatchModel.h create mode 100644 src/gui/entry/AutoTypeMatchView.cpp create mode 100644 src/gui/entry/AutoTypeMatchView.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ccdc955f..4b7c07cd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,6 +39,7 @@ configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY) set(keepassx_SOURCES core/AutoTypeAssociations.cpp core/AsyncTask.h + core/AutoTypeMatch.cpp core/Config.cpp core/CsvParser.cpp core/Database.cpp @@ -137,6 +138,8 @@ 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/EditEntryWidget_p.h gui/entry/EntryAttachmentsModel.cpp diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 2a77c4c1..e3b5b54c 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -27,6 +27,7 @@ #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" @@ -136,7 +137,12 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c QString sequence; if (customSequence.isEmpty()) { - sequence = autoTypeSequence(entry); + QList sequences = autoTypeSequences(entry); + if(sequences.isEmpty()) { + sequence = ""; + } else { + sequence = sequences.first(); + } } else { sequence = customSequence; } @@ -199,36 +205,36 @@ void AutoType::performGlobalAutoType(const QList& dbList) m_inAutoType = true; - QList entryList; - QHash sequenceHash; + QList matchList; for (Database* db : dbList) { const QList dbEntries = db->rootGroup()->entriesRecursive(); for (Entry* entry : dbEntries) { - QString sequence = autoTypeSequence(entry, windowTitle); - if (!sequence.isEmpty()) { - entryList << entry; - sequenceHash.insert(entry, sequence); + const QList sequences = autoTypeSequences(entry, windowTitle); + for (QString sequence : sequences) { + if (!sequence.isEmpty()) { + matchList << AutoTypeMatch(entry,sequence); + } } } } - if (entryList.isEmpty()) { + if (matchList.isEmpty()) { m_inAutoType = false; QString message = tr("Couldn't find an entry that matches the window title:"); message.append("\n\n"); message.append(windowTitle); MessageBox::information(nullptr, tr("Auto-Type - KeePassXC"), message); - } else if ((entryList.size() == 1) && !config()->get("security/autotypeask").toBool()) { + } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { m_inAutoType = false; - performAutoType(entryList.first(), nullptr, sequenceHash[entryList.first()]); + performAutoType(matchList.first().entry, nullptr, matchList.first().sequence); } else { m_windowFromGlobal = m_plugin->activeWindow(); AutoTypeSelectDialog* selectDialog = new AutoTypeSelectDialog(); - connect( - selectDialog, SIGNAL(entryActivated(Entry*, QString)), SLOT(performAutoTypeFromGlobal(Entry*, QString))); + connect(selectDialog, SIGNAL(matchActivated(AutoTypeMatch)), + SLOT(performAutoTypeFromGlobal(AutoTypeMatch))); connect(selectDialog, SIGNAL(rejected()), SLOT(resetInAutoType())); - selectDialog->setEntries(entryList, sequenceHash); + selectDialog->setMatchList(matchList); #if defined(Q_OS_MAC) m_plugin->raiseOwnWindow(); Tools::wait(500); @@ -239,7 +245,7 @@ void AutoType::performGlobalAutoType(const QList& dbList) } } -void AutoType::performAutoTypeFromGlobal(Entry* entry, const QString& sequence) +void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) { Q_ASSERT(m_inAutoType); @@ -247,7 +253,7 @@ void AutoType::performAutoTypeFromGlobal(Entry* entry, const QString& sequence) m_inAutoType = false; - performAutoType(entry, nullptr, sequence, m_windowFromGlobal); + performAutoType(match.entry, nullptr, match.sequence, m_windowFromGlobal); } void AutoType::resetInAutoType() @@ -506,78 +512,56 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c return list; } -QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitle) +QList AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle) { + QList sequenceList; + if (!entry->autoTypeEnabled()) { - return QString(); + return sequenceList; } - bool enableSet = false; - QString sequence; if (!windowTitle.isEmpty()) { - bool match = false; 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()) { - sequence = assoc.sequence; + sequenceList.append(assoc.sequence); } else { - sequence = entry->defaultAutoTypeSequence(); + sequenceList.append(entry->effectiveAutoTypeSequence()); } - match = true; - break; } } - if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && + if (config()->get("AutoTypeEntryTitleMatch").toBool() && windowMatchesTitle(windowTitle, entry->resolvePlaceholder(entry->title()))) { - sequence = entry->defaultAutoTypeSequence(); - match = true; + sequenceList.append(entry->effectiveAutoTypeSequence()); } - if (!match && config()->get("AutoTypeEntryURLMatch").toBool() && + if (config()->get("AutoTypeEntryURLMatch").toBool() && windowMatchesUrl(windowTitle, entry->resolvePlaceholder(entry->url()))) { - sequence = entry->defaultAutoTypeSequence(); - match = true; + sequenceList.append(entry->effectiveAutoTypeSequence()); } - if (!match) { - return QString(); + if (sequenceList.isEmpty()) { + return sequenceList; } } else { - sequence = entry->defaultAutoTypeSequence(); + sequenceList.append(entry->effectiveAutoTypeSequence()); } const Group* group = entry->group(); do { - if (!enableSet) { - if (group->autoTypeEnabled() == Group::Disable) { - return QString(); - } else if (group->autoTypeEnabled() == Group::Enable) { - enableSet = true; - } + if (group->autoTypeEnabled() == Group::Disable) { + return QList(); + } else if (group->autoTypeEnabled() == Group::Enable) { + return sequenceList; } - - if (sequence.isEmpty()) { - sequence = group->defaultAutoTypeSequence(); - } - group = group->parentGroup(); - } while (group && (!enableSet || sequence.isEmpty())); - if (sequence.isEmpty() && (!entry->resolvePlaceholder(entry->username()).isEmpty() || - !entry->resolvePlaceholder(entry->password()).isEmpty())) { - if (entry->resolvePlaceholder(entry->username()).isEmpty()) { - sequence = "{PASSWORD}{ENTER}"; - } else if (entry->resolvePlaceholder(entry->password()).isEmpty()) { - sequence = "{USERNAME}{ENTER}"; - } else { - sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; - } - } + } while (group); - return sequence; + return sequenceList; } bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPattern) diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index eb366ae9..5c89b4fa 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -23,6 +23,8 @@ #include #include +#include "core/AutoTypeMatch.h" + class AutoTypeAction; class AutoTypeExecutor; class AutoTypePlatformInterface; @@ -69,7 +71,7 @@ signals: void globalShortcutTriggered(); private slots: - void performAutoTypeFromGlobal(Entry* entry, const QString& sequence); + void performAutoTypeFromGlobal(AutoTypeMatch match); void resetInAutoType(); void unloadPlugin(); @@ -79,7 +81,7 @@ private: void loadPlugin(const QString& pluginPath); bool parseActions(const QString& sequence, const Entry* entry, QList& actions); QList createActionFromTemplate(const QString& tmpl, const Entry* entry); - QString autoTypeSequence(const Entry* entry, const QString& windowTitle = QString()); + 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); diff --git a/src/autotype/AutoTypeSelectDialog.cpp b/src/autotype/AutoTypeSelectDialog.cpp index b39c78e1..3ef08648 100644 --- a/src/autotype/AutoTypeSelectDialog.cpp +++ b/src/autotype/AutoTypeSelectDialog.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * 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 @@ -25,14 +26,15 @@ #include #include "autotype/AutoTypeSelectView.h" +#include "core/AutoTypeMatch.h" #include "core/Config.h" #include "core/FilePath.h" -#include "gui/entry/EntryModel.h" +#include "gui/entry/AutoTypeMatchModel.h" AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) : QDialog(parent) , m_view(new AutoTypeSelectView(this)) - , m_entryActivatedEmitted(false) + , m_matchActivatedEmitted(false) { setAttribute(Qt::WA_DeleteOnClose); // Places the window on the active (virtual) desktop instead of where the main window is. @@ -42,7 +44,7 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) setWindowIcon(filePath()->applicationIcon()); QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos()); - QSize size = config()->get("GUI/AutoTypeSelectDialogSize", QSize(400, 250)).toSize(); + QSize size = config()->get("GUI/AutoTypeSelectDialogSize", QSize(600, 250)).toSize(); size.setWidth(qMin(size.width(), screenGeometry.width())); size.setHeight(qMin(size.height(), screenGeometry.height())); resize(size); @@ -56,10 +58,10 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) QLabel* descriptionLabel = new QLabel(tr("Select entry to Auto-Type:"), this); layout->addWidget(descriptionLabel); - connect(m_view, SIGNAL(activated(QModelIndex)), SLOT(emitEntryActivated(QModelIndex))); - connect(m_view, SIGNAL(clicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex))); + 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->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(entryRemoved())); layout->addWidget(m_view); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, Qt::Horizontal, this); @@ -67,10 +69,9 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) layout->addWidget(buttonBox); } -void AutoTypeSelectDialog::setEntries(const QList& entries, const QHash& sequences) +void AutoTypeSelectDialog::setMatchList(const QList& matchList) { - m_sequences = sequences; - m_view->setEntryList(entries); + m_view->setMatchList(matchList); m_view->header()->resizeSections(QHeaderView::ResizeToContents); } @@ -82,20 +83,20 @@ void AutoTypeSelectDialog::done(int r) QDialog::done(r); } -void AutoTypeSelectDialog::emitEntryActivated(const QModelIndex& index) +void AutoTypeSelectDialog::emitMatchActivated(const QModelIndex& index) { // make sure we don't emit the signal twice when both activated() and clicked() are triggered - if (m_entryActivatedEmitted) { + if (m_matchActivatedEmitted) { return; } - m_entryActivatedEmitted = true; + m_matchActivatedEmitted = true; - Entry* entry = m_view->entryFromIndex(index); + AutoTypeMatch match = m_view->matchFromIndex(index); accept(); - emit entryActivated(entry, m_sequences[entry]); + emit matchActivated(match); } -void AutoTypeSelectDialog::entryRemoved() +void AutoTypeSelectDialog::matchRemoved() { if (m_view->model()->rowCount() == 0) { reject(); diff --git a/src/autotype/AutoTypeSelectDialog.h b/src/autotype/AutoTypeSelectDialog.h index 3d9c684e..83abd2d8 100644 --- a/src/autotype/AutoTypeSelectDialog.h +++ b/src/autotype/AutoTypeSelectDialog.h @@ -22,8 +22,9 @@ #include #include +#include "core/AutoTypeMatch.h" + class AutoTypeSelectView; -class Entry; class AutoTypeSelectDialog : public QDialog { @@ -31,22 +32,21 @@ class AutoTypeSelectDialog : public QDialog public: explicit AutoTypeSelectDialog(QWidget* parent = nullptr); - void setEntries(const QList& entries, const QHash& sequences); + void setMatchList(const QList& matchList); signals: - void entryActivated(Entry* entry, const QString& sequence); + void matchActivated(AutoTypeMatch match); public slots: void done(int r) override; private slots: - void emitEntryActivated(const QModelIndex& index); - void entryRemoved(); + void emitMatchActivated(const QModelIndex& index); + void matchRemoved(); private: AutoTypeSelectView* const m_view; - QHash m_sequences; - bool m_entryActivatedEmitted; + bool m_matchActivatedEmitted; }; #endif // KEEPASSX_AUTOTYPESELECTDIALOG_H diff --git a/src/autotype/AutoTypeSelectView.cpp b/src/autotype/AutoTypeSelectView.cpp index 7d9db413..e4dba051 100644 --- a/src/autotype/AutoTypeSelectView.cpp +++ b/src/autotype/AutoTypeSelectView.cpp @@ -21,15 +21,12 @@ #include AutoTypeSelectView::AutoTypeSelectView(QWidget* parent) - : EntryView(parent) + : AutoTypeMatchView(parent) { - hideColumn(3); setMouseTracking(true); setAllColumnsShowFocus(true); - setDragEnabled(false); - setSelectionMode(QAbstractItemView::SingleSelection); - connect(model(), SIGNAL(modelReset()), SLOT(selectFirstEntry())); + connect(model(), SIGNAL(modelReset()), SLOT(selectFirstMatch())); } void AutoTypeSelectView::mouseMoveEvent(QMouseEvent* event) @@ -44,10 +41,10 @@ void AutoTypeSelectView::mouseMoveEvent(QMouseEvent* event) unsetCursor(); } - EntryView::mouseMoveEvent(event); + AutoTypeMatchView::mouseMoveEvent(event); } -void AutoTypeSelectView::selectFirstEntry() +void AutoTypeSelectView::selectFirstMatch() { QModelIndex index = model()->index(0, 0); diff --git a/src/autotype/AutoTypeSelectView.h b/src/autotype/AutoTypeSelectView.h index aadf99fa..e6a2ec65 100644 --- a/src/autotype/AutoTypeSelectView.h +++ b/src/autotype/AutoTypeSelectView.h @@ -18,11 +18,9 @@ #ifndef KEEPASSX_AUTOTYPESELECTVIEW_H #define KEEPASSX_AUTOTYPESELECTVIEW_H -#include "gui/entry/EntryView.h" +#include "gui/entry/AutoTypeMatchView.h" -class Entry; - -class AutoTypeSelectView : public EntryView +class AutoTypeSelectView : public AutoTypeMatchView { Q_OBJECT @@ -34,7 +32,7 @@ protected: void keyReleaseEvent(QKeyEvent* e) override; private slots: - void selectFirstEntry(); + void selectFirstMatch(); signals: void rejected(); diff --git a/src/core/AutoTypeMatch.cpp b/src/core/AutoTypeMatch.cpp new file mode 100644 index 00000000..c1faab9e --- /dev/null +++ b/src/core/AutoTypeMatch.cpp @@ -0,0 +1,39 @@ +/* + * 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" + +AutoTypeMatch::AutoTypeMatch() + : entry(nullptr), + sequence() +{} + +AutoTypeMatch::AutoTypeMatch(Entry* entry, QString sequence) + : entry(entry), + sequence(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/AutoTypeMatch.h b/src/core/AutoTypeMatch.h new file mode 100644 index 00000000..768cf168 --- /dev/null +++ b/src/core/AutoTypeMatch.h @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +#ifndef KEEPASSX_AUTOTYPEMATCH_H +#define KEEPASSX_AUTOTYPEMATCH_H + +#include +#include + +class Entry; + +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 diff --git a/src/gui/entry/AutoTypeMatchModel.cpp b/src/gui/entry/AutoTypeMatchModel.cpp new file mode 100644 index 00000000..3a48e173 --- /dev/null +++ b/src/gui/entry/AutoTypeMatchModel.cpp @@ -0,0 +1,202 @@ +/* + * 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 "AutoTypeMatchModel.h" + +#include + +#include "core/DatabaseIcons.h" +#include "core/Entry.h" +#include "core/Global.h" +#include "core/Group.h" +#include "core/Metadata.h" + +AutoTypeMatchModel::AutoTypeMatchModel(QObject* parent) + : QAbstractTableModel(parent) +{ +} + +AutoTypeMatch AutoTypeMatchModel::matchFromIndex(const QModelIndex& index) const +{ + Q_ASSERT(index.isValid() && index.row() < m_matches.size()); + return m_matches.at(index.row()); +} + +QModelIndex AutoTypeMatchModel::indexFromMatch(AutoTypeMatch match) const +{ + int row = m_matches.indexOf(match); + Q_ASSERT(row != -1); + return index(row, 1); +} + +void AutoTypeMatchModel::setMatchList(const QList& matches) +{ + beginResetModel(); + + severConnections(); + + m_allGroups.clear(); + m_matches = matches; + + QSet databases; + + for (AutoTypeMatch match : asConst(m_matches)) { + databases.insert(match.entry->group()->database()); + } + + for (Database* db : asConst(databases)) { + Q_ASSERT(db); + for (const Group* group : db->rootGroup()->groupsRecursive(true)) { + m_allGroups.append(group); + } + + if (db->metadata()->recycleBin()) { + m_allGroups.removeOne(db->metadata()->recycleBin()); + } + } + + for (const Group* group : asConst(m_allGroups)) { + makeConnections(group); + } + + endResetModel(); +} + +int AutoTypeMatchModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } else { + return m_matches.size(); + } +} + +int AutoTypeMatchModel::columnCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent); + + return 4; +} + +QVariant AutoTypeMatchModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + AutoTypeMatch match = matchFromIndex(index); + + if (role == Qt::DisplayRole) { + QString result; + switch (index.column()) { + case ParentGroup: + if (match.entry->group()) { + return match.entry->group()->name(); + } + break; + case Title: + return match.entry->resolveMultiplePlaceholders(match.entry->title()); + case Username: + return match.entry->resolveMultiplePlaceholders(match.entry->username()); + case Sequence: + return match.sequence; + } + } else if (role == Qt::DecorationRole) { + switch (index.column()) { + case ParentGroup: + if (match.entry->group()) { + return match.entry->group()->iconScaledPixmap(); + } + break; + case Title: + if (match.entry->isExpired()) { + return databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex); + } else { + return match.entry->iconScaledPixmap(); + } + } + } else if (role == Qt::FontRole) { + QFont font; + if (match.entry->isExpired()) { + font.setStrikeOut(true); + } + return font; + } + + return QVariant(); +} + +QVariant AutoTypeMatchModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + switch (section) { + case ParentGroup: + return tr("Group"); + case Title: + return tr("Title"); + case Username: + return tr("Username"); + case Sequence: + return tr("Sequence"); + } + } + + return QVariant(); +} + +void AutoTypeMatchModel::entryDataChanged(Entry* entry) +{ + for (int row = 0; row < m_matches.size(); row++) { + AutoTypeMatch match = m_matches[row]; + if (match.entry == entry) { + emit dataChanged(index(row, 0), index(row, columnCount()-1)); + } + } +} + + +void AutoTypeMatchModel::entryAboutToRemove(Entry* entry) +{ + for (int row = 0; row < m_matches.size(); row++) { + AutoTypeMatch match = m_matches[row]; + if (match.entry == entry) { + beginRemoveRows(QModelIndex(), row, row); + m_matches.removeAt(row); + endRemoveRows(); + row--; + } + } +} + +void AutoTypeMatchModel::entryRemoved() +{ +} + +void AutoTypeMatchModel::severConnections() +{ + for (const Group* group : asConst(m_allGroups)) { + disconnect(group, nullptr, this, nullptr); + } +} + +void AutoTypeMatchModel::makeConnections(const Group* group) +{ + connect(group, SIGNAL(entryAboutToRemove(Entry*)), SLOT(entryAboutToRemove(Entry*))); + connect(group, SIGNAL(entryRemoved(Entry*)), SLOT(entryRemoved())); + connect(group, SIGNAL(entryDataChanged(Entry*)), SLOT(entryDataChanged(Entry*))); +} diff --git a/src/gui/entry/AutoTypeMatchModel.h b/src/gui/entry/AutoTypeMatchModel.h new file mode 100644 index 00000000..8d341f5f --- /dev/null +++ b/src/gui/entry/AutoTypeMatchModel.h @@ -0,0 +1,66 @@ +/* + * 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 . + */ + +#ifndef KEEPASSX_AUTOTYPEMATCHMODEL_H +#define KEEPASSX_AUTOTYPEMATCHMODEL_H + +#include + +#include "core/AutoTypeMatch.h" + +class Entry; +class Group; + +class AutoTypeMatchModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + enum ModelColumn + { + ParentGroup = 0, + Title = 1, + Username = 2, + Sequence = 3 + }; + + explicit AutoTypeMatchModel(QObject* parent = nullptr); + AutoTypeMatch matchFromIndex(const QModelIndex& index) const; + QModelIndex indexFromMatch(AutoTypeMatch match) const; + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + void setMatchList(const QList& matches); + +private Q_SLOTS: + void entryAboutToRemove(Entry* entry); + void entryRemoved(); + void entryDataChanged(Entry* entry); + +private: + void severConnections(); + void makeConnections(const Group* group); + + QList m_matches; + QList m_allGroups; +}; + +#endif // KEEPASSX_AUTOTYPEMATCHMODEL_H diff --git a/src/gui/entry/AutoTypeMatchView.cpp b/src/gui/entry/AutoTypeMatchView.cpp new file mode 100644 index 00000000..013d192f --- /dev/null +++ b/src/gui/entry/AutoTypeMatchView.cpp @@ -0,0 +1,116 @@ +/* + * 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 +#include + +#include "gui/SortFilterHideProxyModel.h" + +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); + QTreeView::setModel(m_sortModel); + + setUniformRowHeights(true); + setRootIsDecorated(false); + setAlternatingRowColors(true); + setDragEnabled(false); + setSortingEnabled(true); + setSelectionMode(QAbstractItemView::SingleSelection); + header()->setDefaultSectionSize(150); + + connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitMatchActivated(QModelIndex))); + connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SIGNAL(matchSelectionChanged())); +} + +void AutoTypeMatchView::keyPressEvent(QKeyEvent* event) +{ + if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && currentIndex().isValid()) { + emitMatchActivated(currentIndex()); + } + + QTreeView::keyPressEvent(event); +} + +void AutoTypeMatchView::setMatchList(const QList& matches) +{ + m_model->setMatchList(matches); + 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); +} + +void AutoTypeMatchView::setModel(QAbstractItemModel* model) +{ + Q_UNUSED(model); + Q_ASSERT(false); +} + +AutoTypeMatch AutoTypeMatchView::currentMatch() +{ + QModelIndexList list = selectionModel()->selectedRows(); + if (list.size() == 1) { + return m_model->matchFromIndex(m_sortModel->mapToSource(list.first())); + } else { + return AutoTypeMatch(); + } +} + +void AutoTypeMatchView::setCurrentMatch(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)); + } else { + return AutoTypeMatch(); + } +} diff --git a/src/gui/entry/AutoTypeMatchView.h b/src/gui/entry/AutoTypeMatchView.h new file mode 100644 index 00000000..08c17700 --- /dev/null +++ b/src/gui/entry/AutoTypeMatchView.h @@ -0,0 +1,58 @@ +/* + * 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 . + */ + +#ifndef KEEPASSX_AUTOTYPEMATCHVIEW_H +#define KEEPASSX_AUTOTYPEMATCHVIEW_H + +#include + +#include "core/AutoTypeMatch.h" + +#include "gui/entry/AutoTypeMatchModel.h" + +class SortFilterHideProxyModel; + +class AutoTypeMatchView : public QTreeView +{ + Q_OBJECT + +public: + explicit AutoTypeMatchView(QWidget* parent = nullptr); + void setModel(QAbstractItemModel* model) override; + AutoTypeMatch currentMatch(); + void setCurrentMatch(AutoTypeMatch match); + AutoTypeMatch matchFromIndex(const QModelIndex& index); + void setMatchList(const QList& matches); + void setFirstMatchActive(); + +Q_SIGNALS: + void matchActivated(AutoTypeMatch match); + void matchSelectionChanged(); + +protected: + void keyPressEvent(QKeyEvent* event) override; + +private Q_SLOTS: + void emitMatchActivated(const QModelIndex& index); + +private: + AutoTypeMatchModel* const m_model; + SortFilterHideProxyModel* const m_sortModel; +}; + +#endif // KEEPASSX_AUTOTYPEMATCHVIEW_H