Auto-Type: Shortcuts for selection dialog (#6361)

* Shortcut to toggle search all entries
* Select first match only when we have a window match

When we default to full database search it's possible the user would select the first match without by accident.

In this case when our query is empty we don't select anything and you need to either type something or press down to select the first item.

* Added username, password, and TOTP keyboard shortcuts and a help tip

* Closes #6176

Co-authored-by: Jonathan White <support@dmapps.us>
This commit is contained in:
Toni Spets 2021-04-01 06:27:56 +03:00 committed by GitHub
parent c19efb5b19
commit 371bd2e51b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 112 additions and 44 deletions

View File

@ -509,6 +509,11 @@ void AutoType::resetAutoTypeState()
QList<QSharedPointer<AutoTypeAction>> QList<QSharedPointer<AutoTypeAction>>
AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QString& error, bool syntaxOnly) AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QString& error, bool syntaxOnly)
{ {
if (!entry) {
error = tr("Invalid entry provided");
return {};
}
const int maxTypeDelay = 100; const int maxTypeDelay = 100;
const int maxWaitDelay = 10000; const int maxWaitDelay = 10000;
const int maxRepetition = 100; const int maxRepetition = 100;

View File

@ -78,14 +78,20 @@ void AutoTypeMatchView::keyPressEvent(QKeyEvent* event)
QTableView::keyPressEvent(event); QTableView::keyPressEvent(event);
} }
void AutoTypeMatchView::setMatchList(const QList<AutoTypeMatch>& matches) void AutoTypeMatchView::setMatchList(const QList<AutoTypeMatch>& matches, bool selectFirst)
{ {
m_model->setMatchList(matches); m_model->setMatchList(matches);
m_sortModel->setFilterWildcard({}); m_sortModel->setFilterWildcard({});
horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
selectionModel()->setCurrentIndex(m_sortModel->index(0, 0),
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); if (selectFirst) {
selectionModel()->setCurrentIndex(m_sortModel->index(0, 0),
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
} else {
selectionModel()->clear();
}
emit currentMatchChanged(currentMatch()); emit currentMatchChanged(currentMatch());
} }

View File

@ -34,7 +34,7 @@ public:
explicit AutoTypeMatchView(QWidget* parent = nullptr); explicit AutoTypeMatchView(QWidget* parent = nullptr);
AutoTypeMatch currentMatch(); AutoTypeMatch currentMatch();
AutoTypeMatch matchFromIndex(const QModelIndex& index); AutoTypeMatch matchFromIndex(const QModelIndex& index);
void setMatchList(const QList<AutoTypeMatch>& matches); void setMatchList(const QList<AutoTypeMatch>& matches, bool selectFirst);
void filterList(const QString& filter); void filterList(const QString& filter);
signals: signals:

View File

@ -67,16 +67,14 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
connect(m_ui->search, SIGNAL(returnPressed()), SLOT(activateCurrentMatch())); connect(m_ui->search, SIGNAL(returnPressed()), SLOT(activateCurrentMatch()));
connect(&m_searchTimer, SIGNAL(timeout()), SLOT(performSearch())); connect(&m_searchTimer, SIGNAL(timeout()), SLOT(performSearch()));
connect(m_ui->filterRadio, &QRadioButton::toggled, this, [this](bool checked) { m_ui->searchCheckBox->setShortcut(Qt::CTRL + Qt::Key_F);
connect(m_ui->searchCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
if (checked) { if (checked) {
// Reset to original match list
m_ui->view->setMatchList(m_matches);
performSearch(); performSearch();
m_ui->search->setFocus(); m_ui->search->setFocus();
} } else {
}); // Reset to original match list
connect(m_ui->searchRadio, &QRadioButton::toggled, this, [this](bool checked) { m_ui->view->setMatchList(m_matches, true);
if (checked) {
performSearch(); performSearch();
m_ui->search->setFocus(); m_ui->search->setFocus();
} }
@ -100,24 +98,22 @@ void AutoTypeSelectDialog::setMatches(const QList<AutoTypeMatch>& matches, const
m_matches = matches; m_matches = matches;
m_dbs = dbs; m_dbs = dbs;
m_ui->view->setMatchList(m_matches); m_ui->view->setMatchList(m_matches, !m_matches.isEmpty() || !m_ui->search->text().isEmpty());
if (m_matches.isEmpty()) { m_ui->searchCheckBox->setChecked(m_matches.isEmpty());
m_ui->searchRadio->setChecked(true);
} else {
m_ui->filterRadio->setChecked(true);
}
} }
void AutoTypeSelectDialog::submitAutoTypeMatch(AutoTypeMatch match) void AutoTypeSelectDialog::submitAutoTypeMatch(AutoTypeMatch match)
{ {
m_accepted = true; if (match.first) {
accept(); m_accepted = true;
emit matchActivated(std::move(match)); accept();
emit matchActivated(std::move(match));
}
} }
void AutoTypeSelectDialog::performSearch() void AutoTypeSelectDialog::performSearch()
{ {
if (m_ui->filterRadio->isChecked()) { if (!m_ui->searchCheckBox->isChecked()) {
m_ui->view->filterList(m_ui->search->text()); m_ui->view->filterList(m_ui->search->text());
return; return;
} }
@ -148,7 +144,7 @@ void AutoTypeSelectDialog::performSearch()
} }
} }
m_ui->view->setMatchList(matches); m_ui->view->setMatchList(matches, !m_ui->search->text().isEmpty());
} }
void AutoTypeSelectDialog::moveSelectionUp() void AutoTypeSelectDialog::moveSelectionUp()
@ -164,6 +160,13 @@ void AutoTypeSelectDialog::moveSelectionUp()
void AutoTypeSelectDialog::moveSelectionDown() void AutoTypeSelectDialog::moveSelectionDown()
{ {
auto current = m_ui->view->currentIndex(); auto current = m_ui->view->currentIndex();
// special case where we have no default selection (empty search)
if (!current.isValid()) {
m_ui->view->setCurrentIndex(m_ui->view->indexAt({0, 0}));
return;
}
auto next = current.sibling(current.row() + 1, 0); auto next = current.sibling(current.row() + 1, 0);
if (next.isValid()) { if (next.isValid()) {
@ -274,16 +277,24 @@ void AutoTypeSelectDialog::buildActionMenu()
m_actionMenu->addAction(copyPasswordAction); m_actionMenu->addAction(copyPasswordAction);
m_actionMenu->addAction(copyTotpAction); m_actionMenu->addAction(copyTotpAction);
auto shortcut = new QShortcut(Qt::CTRL + Qt::Key_1, this);
connect(shortcut, &QShortcut::activated, typeUsernameAction, &QAction::trigger);
connect(typeUsernameAction, &QAction::triggered, this, [&] { connect(typeUsernameAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch(); auto match = m_ui->view->currentMatch();
match.second = "{USERNAME}"; match.second = "{USERNAME}";
submitAutoTypeMatch(match); submitAutoTypeMatch(match);
}); });
shortcut = new QShortcut(Qt::CTRL + Qt::Key_2, this);
connect(shortcut, &QShortcut::activated, typePasswordAction, &QAction::trigger);
connect(typePasswordAction, &QAction::triggered, this, [&] { connect(typePasswordAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch(); auto match = m_ui->view->currentMatch();
match.second = "{PASSWORD}"; match.second = "{PASSWORD}";
submitAutoTypeMatch(match); submitAutoTypeMatch(match);
}); });
shortcut = new QShortcut(Qt::CTRL + Qt::Key_3, this);
connect(shortcut, &QShortcut::activated, typeTotpAction, &QAction::trigger);
connect(typeTotpAction, &QAction::triggered, this, [&] { connect(typeTotpAction, &QAction::triggered, this, [&] {
auto match = m_ui->view->currentMatch(); auto match = m_ui->view->currentMatch();
match.second = "{TOTP}"; match.second = "{TOTP}";

View File

@ -7,19 +7,74 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>418</width> <width>418</width>
<height>295</height> <height>303</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Auto-Type - KeePassXC</string> <string>Auto-Type - KeePassXC</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="topMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item> <item>
<widget class="QLabel" name="label"> <layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="text"> <item>
<string>Double click a row to perform Auto-Type or find an entry using the search:</string> <widget class="QLabel" name="label">
</property> <property name="text">
</widget> <string>Double click a row to perform Auto-Type or find an entry using the search:</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<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="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>14</width>
<height>14</height>
</size>
</property>
<property name="toolTip">
<string>&lt;p&gt;You can use advanced search queries to find any entry in your open databases. The following shortcuts are useful:&lt;br/&gt;
Ctrl+F - Toggle database search&lt;br/&gt;
Ctrl+1 - Type username&lt;br/&gt;
Ctrl+2 - Type password&lt;br/&gt;
Ctrl+3 - Type TOTP&lt;/p&gt;</string>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../../share/icons/icons.qrc">:/icons/application/scalable/actions/system-help.svg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item> </item>
<item> <item>
<widget class="AutoTypeMatchView" name="view"> <widget class="AutoTypeMatchView" name="view">
@ -86,19 +141,9 @@
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QRadioButton" name="filterRadio"> <widget class="QCheckBox" name="searchCheckBox">
<property name="text"> <property name="text">
<string>&amp;Filter Matches</string> <string>Search all open databases</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="searchRadio">
<property name="text">
<string>&amp;Search Database</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -133,7 +178,7 @@
</size> </size>
</property> </property>
<property name="placeholderText"> <property name="placeholderText">
<string>Filter or Search…</string> <string>Search…</string>
</property> </property>
<property name="clearButtonEnabled"> <property name="clearButtonEnabled">
<bool>true</bool> <bool>true</bool>
@ -188,10 +233,11 @@
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>view</tabstop> <tabstop>view</tabstop>
<tabstop>filterRadio</tabstop> <tabstop>searchCheckBox</tabstop>
<tabstop>searchRadio</tabstop>
<tabstop>search</tabstop> <tabstop>search</tabstop>
</tabstops> </tabstops>
<resources/> <resources>
<include location="../../share/icons/icons.qrc"/>
</resources>
<connections/> <connections/>
</ui> </ui>