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
195 lines
5.1 KiB
C++
195 lines
5.1 KiB
C++
/*
|
|
* 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*)));
|
|
}
|