Improve Auto-Type Select Dialog
Significant improvements to the Auto-Type select dialog. Reduce stale and unnecessary code paths. * Close select dialog when databases are locked. * Close open modal dialogs prior to showing the Auto-Type select dialog to prevent interference. * Never perform Auto-Type on the KeePassXC window. * Only filter match list based on Group, Title, and Username column data (ie, ignore sequence column) * Always show the sequence column (revert feature) * Show selection dialog if there are no matches to allow for a database search * Close #3630 - Allow typing {USERNAME} and {PASSWORD} from selection dialog (right-click menu). * Close #429 - Ability to search open databases for an entry from the Auto-Type selection dialog. * Fix #5361 - Default size of selection dialog doesn't cut off matches
This commit is contained in:
@@ -21,13 +21,12 @@
|
||||
#include <QApplication>
|
||||
#include <QPluginLoader>
|
||||
#include <QRegularExpression>
|
||||
#include <QWindow>
|
||||
|
||||
#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<QString> 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<QSharedPointer<Database>>& dbLi
|
||||
|
||||
for (const auto& db : dbList) {
|
||||
const QList<Entry*> 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<QString> 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<QApplication*>(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<AutoTypeAction*> 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<QString> AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle)
|
||||
{
|
||||
QList<QString> 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<AutoTypeAssociations::Association> 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
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <QStringList>
|
||||
#include <QWidget>
|
||||
|
||||
#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<AutoTypeAction*>& actions);
|
||||
QList<AutoTypeAction*> createActionFromTemplate(const QString& tmpl, const Entry* entry);
|
||||
QList<QString> 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;
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AutoTypeFilterLineEdit.h"
|
||||
#include <QKeyEvent>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_AUTOTYPEFILTERLINEEDIT_H
|
||||
#define KEEPASSX_AUTOTYPEFILTERLINEEDIT_H
|
||||
|
||||
#include <QLineEdit>
|
||||
|
||||
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
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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
|
||||
@@ -15,27 +15,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_AUTOTYPESELECTVIEW_H
|
||||
#define KEEPASSX_AUTOTYPESELECTVIEW_H
|
||||
#ifndef KPXC_AUTOTYPEMATCH_H
|
||||
#define KPXC_AUTOTYPEMATCH_H
|
||||
|
||||
#include "gui/entry/AutoTypeMatchView.h"
|
||||
#include <QPair>
|
||||
#include <QPointer>
|
||||
#include <QString>
|
||||
|
||||
class AutoTypeSelectView : public AutoTypeMatchView
|
||||
{
|
||||
Q_OBJECT
|
||||
class Entry;
|
||||
typedef QPair<QPointer<Entry>, QString> AutoTypeMatch;
|
||||
|
||||
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
|
||||
#endif // KPXC_AUTOTYPEMATCH_H
|
||||
194
src/autotype/AutoTypeMatchModel.cpp
Normal file
194
src/autotype/AutoTypeMatchModel.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AutoTypeMatchModel.h"
|
||||
|
||||
#include <QFont>
|
||||
|
||||
#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(const AutoTypeMatch& match) const
|
||||
{
|
||||
int row = m_matches.indexOf(match);
|
||||
Q_ASSERT(row != -1);
|
||||
return index(row, 1);
|
||||
}
|
||||
|
||||
void AutoTypeMatchModel::setMatchList(const QList<AutoTypeMatch>& matches)
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
severConnections();
|
||||
|
||||
m_allGroups.clear();
|
||||
m_matches = matches;
|
||||
|
||||
QSet<Database*> databases;
|
||||
|
||||
for (AutoTypeMatch& match : m_matches) {
|
||||
databases.insert(match.first->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;
|
||||
}
|
||||
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 {};
|
||||
}
|
||||
|
||||
AutoTypeMatch match = matchFromIndex(index);
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
switch (index.column()) {
|
||||
case ParentGroup:
|
||||
if (match.first->group()) {
|
||||
return match.first->group()->name();
|
||||
}
|
||||
break;
|
||||
case Title:
|
||||
return match.first->resolveMultiplePlaceholders(match.first->title());
|
||||
case Username:
|
||||
return match.first->resolveMultiplePlaceholders(match.first->username());
|
||||
case Sequence:
|
||||
return match.second;
|
||||
}
|
||||
} else if (role == Qt::DecorationRole) {
|
||||
switch (index.column()) {
|
||||
case ParentGroup:
|
||||
if (match.first->group()) {
|
||||
return match.first->group()->iconPixmap();
|
||||
}
|
||||
break;
|
||||
case Title:
|
||||
return match.first->iconPixmap();
|
||||
}
|
||||
} else if (role == Qt::FontRole) {
|
||||
QFont font;
|
||||
if (match.first->isExpired()) {
|
||||
font.setStrikeOut(true);
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
void AutoTypeMatchModel::entryDataChanged(Entry* entry)
|
||||
{
|
||||
for (int row = 0; row < m_matches.size(); ++row) {
|
||||
AutoTypeMatch match = m_matches[row];
|
||||
if (match.first == 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.first == 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*)));
|
||||
}
|
||||
66
src/autotype/AutoTypeMatchModel.h
Normal file
66
src/autotype/AutoTypeMatchModel.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_AUTOTYPEMATCHMODEL_H
|
||||
#define KEEPASSX_AUTOTYPEMATCHMODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
#include "autotype/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(const 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<AutoTypeMatch>& matches);
|
||||
|
||||
private slots:
|
||||
void entryAboutToRemove(Entry* entry);
|
||||
void entryRemoved();
|
||||
void entryDataChanged(Entry* entry);
|
||||
|
||||
private:
|
||||
void severConnections();
|
||||
void makeConnections(const Group* group);
|
||||
|
||||
QList<AutoTypeMatch> m_matches;
|
||||
QList<const Group*> m_allGroups;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_AUTOTYPEMATCHMODEL_H
|
||||
120
src/autotype/AutoTypeMatchView.cpp
Normal file
120
src/autotype/AutoTypeMatchView.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AutoTypeMatchView.h"
|
||||
|
||||
#include "core/Entry.h"
|
||||
#include "gui/Clipboard.h"
|
||||
#include "gui/Icons.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QHeaderView>
|
||||
#include <QKeyEvent>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
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<AutoTypeMatch>& 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);
|
||||
}
|
||||
55
src/autotype/AutoTypeMatchView.h
Normal file
55
src/autotype/AutoTypeMatchView.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_AUTOTYPEMATCHVIEW_H
|
||||
#define KEEPASSX_AUTOTYPEMATCHVIEW_H
|
||||
|
||||
#include <QTableView>
|
||||
|
||||
#include "autotype/AutoTypeMatch.h"
|
||||
#include "autotype/AutoTypeMatchModel.h"
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
class AutoTypeMatchView : public QTableView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AutoTypeMatchView(QWidget* parent = nullptr);
|
||||
AutoTypeMatch currentMatch();
|
||||
AutoTypeMatch matchFromIndex(const QModelIndex& index);
|
||||
void setMatchList(const QList<AutoTypeMatch>& matches);
|
||||
void filterList(const QString& filter);
|
||||
|
||||
signals:
|
||||
void currentMatchChanged(AutoTypeMatch match);
|
||||
void matchActivated(AutoTypeMatch match);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
protected slots:
|
||||
void currentChanged(const QModelIndex& current, const QModelIndex& previous) override;
|
||||
|
||||
private:
|
||||
AutoTypeMatchModel* const m_model;
|
||||
QSortFilterProxyModel* const m_sortModel;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_AUTOTYPEMATCHVIEW_H
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <QApplication>
|
||||
#include <QCloseEvent>
|
||||
#include <QMenu>
|
||||
#include <QShortcut>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
#include <QScreen>
|
||||
#else
|
||||
#include <QDesktopWidget>
|
||||
#endif
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#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<AutoTypeMatch>& matches, const QList<QSharedPointer<Database>>& 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<AutoTypeMatch> matches;
|
||||
for (const auto& db : m_dbs) {
|
||||
auto found = searcher.search(searchText, db->rootGroup());
|
||||
for (auto* entry : found) {
|
||||
QSet<QString> 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<QKeyEvent*>(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<QKeyEvent*>(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<QKeyEvent*>(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<QSortFilterProxyModel*>(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<AutoTypeMatch>& 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<QSortFilterProxyModel*>(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);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Team KeePassXC <team@keepassxc.org>
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* 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 <QAbstractItemModel>
|
||||
#include "autotype/AutoTypeMatch.h"
|
||||
#include <QDialog>
|
||||
#include <QHash>
|
||||
#include <QTimer>
|
||||
|
||||
#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<AutoTypeMatch>& matchList);
|
||||
~AutoTypeSelectDialog() override;
|
||||
|
||||
void setMatches(const QList<AutoTypeMatch>& matchList, const QList<QSharedPointer<Database>>& 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<Ui::AutoTypeSelectDialog> m_ui;
|
||||
|
||||
QList<QSharedPointer<Database>> m_dbs;
|
||||
QList<AutoTypeMatch> m_matches;
|
||||
QTimer m_searchTimer;
|
||||
QPointer<QMenu> m_actionMenu;
|
||||
|
||||
bool m_accepted = false;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_AUTOTYPESELECTDIALOG_H
|
||||
|
||||
197
src/autotype/AutoTypeSelectDialog.ui
Normal file
197
src/autotype/AutoTypeSelectDialog.ui
Normal file
@@ -0,0 +1,197 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AutoTypeSelectDialog</class>
|
||||
<widget class="QDialog" name="AutoTypeSelectDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>418</width>
|
||||
<height>295</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Auto-Type - KeePassXC</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Double click a row to perform Auto-Type or find an entry using the search:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="AutoTypeMatchView" name="view">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>175</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="cursor" stdset="0">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="tabKeyNavigation">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="buttonBox_2" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="filterRadio">
|
||||
<property name="text">
|
||||
<string>&Filter Matches</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="searchRadio">
|
||||
<property name="text">
|
||||
<string>&Search Database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="search">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Filter or Search…</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="action">
|
||||
<property name="text">
|
||||
<string>Type Sequence</string>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::MenuButtonPopup</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>AutoTypeMatchView</class>
|
||||
<extends>QTableView</extends>
|
||||
<header>autotype/AutoTypeMatchView.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>view</tabstop>
|
||||
<tabstop>filterRadio</tabstop>
|
||||
<tabstop>searchRadio</tabstop>
|
||||
<tabstop>search</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AutoTypeSelectView.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "WildcardMatcher.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <utility>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_WILDCARDMATCHER_H
|
||||
#define KEEPASSX_WILDCARDMATCHER_H
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user