Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6432,6 +6432,14 @@ Expect some bugs and minor issues, this version is meant for testing purposes.</
<numerusform></numerusform>
</translation>
</message>
<message>
<source>Search Here...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Search Entries in selected Group</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageDatabase</name>
Expand Down
15 changes: 15 additions & 0 deletions src/gui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "gui/SearchWidget.h"
#include "gui/ShortcutSettingsPage.h"
#include "gui/entry/EntryView.h"
#include "gui/group/GroupView.h"
#include "gui/osutils/OSUtils.h"
#include "gui/remote/RemoteSettings.h"

Expand Down Expand Up @@ -412,6 +413,7 @@ MainWindow::MainWindow()
m_ui->actionEntryDownloadIcon->setIcon(icons()->icon("favicon-download"));
m_ui->actionGroupSortAsc->setIcon(icons()->icon("sort-alphabetical-ascending"));
m_ui->actionGroupSortDesc->setIcon(icons()->icon("sort-alphabetical-descending"));
m_ui->actionGroupSearchHere->setIcon(icons()->icon("system-search"));

m_ui->actionGroupNew->setIcon(icons()->icon("group-new"));
m_ui->actionGroupEdit->setIcon(icons()->icon("group-edit"));
Expand Down Expand Up @@ -550,6 +552,7 @@ MainWindow::MainWindow()
m_actionMultiplexer.connect(m_ui->actionGroupSortAsc, SIGNAL(triggered()), SLOT(sortGroupsAsc()));
m_actionMultiplexer.connect(m_ui->actionGroupSortDesc, SIGNAL(triggered()), SLOT(sortGroupsDesc()));
m_actionMultiplexer.connect(m_ui->actionGroupDownloadFavicons, SIGNAL(triggered()), SLOT(downloadAllFavicons()));
connect(m_ui->actionGroupSearchHere, SIGNAL(triggered()), this, SLOT(searchInGroup()));

connect(m_ui->actionSettings, SIGNAL(toggled(bool)), SLOT(switchToSettings(bool)));
connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(togglePasswordGenerator(bool)));
Expand Down Expand Up @@ -1039,6 +1042,7 @@ void MainWindow::updateMenuActionState()
m_ui->actionGroupDownloadFavicons->setVisible(!inRecycleBin);
#endif
m_ui->actionGroupDownloadFavicons->setEnabled(groupSelected && groupHasEntries && !inRecycleBin);
m_ui->actionGroupSearchHere->setEnabled(groupSelected);

// Database Menu
m_ui->actionDatabaseSave->setEnabled(databaseUnlocked && m_ui->tabWidget->canSave());
Expand Down Expand Up @@ -1535,6 +1539,16 @@ void MainWindow::clearSSHAgent()
#endif
}

void MainWindow::searchInGroup()
{
auto dbWidget = m_ui->tabWidget->currentDatabaseWidget();
if (dbWidget && dbWidget->isVisible() && dbWidget->isEntryViewActive() && dbWidget->groupView()->currentGroup()) {
focusSearchWidget();
Group* currentGroup = dbWidget->groupView()->currentGroup();
m_searchWidget->setSearchGroupName(currentGroup->fullPath());
}
}

void MainWindow::saveWindowInformation()
{
if (isVisible()) {
Expand Down Expand Up @@ -2156,6 +2170,7 @@ void MainWindow::initActionCollection()
m_ui->actionGroupSortAsc,
m_ui->actionGroupSortDesc,
m_ui->actionGroupEmptyRecycleBin,
m_ui->actionGroupSearchHere,
// Tools Menu
m_ui->actionPasswordGenerator,
m_ui->actionClearSSHAgent,
Expand Down
1 change: 1 addition & 0 deletions src/gui/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ private slots:
void enableMenuAndToolbar();
void disableMenuAndToolbar();
void clearSSHAgent();
void searchInGroup();

private:
static const QString BaseWindowTitle;
Expand Down
10 changes: 10 additions & 0 deletions src/gui/MainWindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@
<addaction name="actionGroupSortDesc"/>
<addaction name="separator"/>
<addaction name="actionGroupDownloadFavicons"/>
<addaction name="separator"/>
<addaction name="actionGroupSearchHere"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
Expand Down Expand Up @@ -1367,6 +1369,14 @@
<enum>QAction::TextHeuristicRole</enum>
</property>
</action>
<action name="actionGroupSearchHere">
<property name="text">
<string>Search Here...</string>
</property>
<property name="toolTip">
<string>Search Entries in selected Group</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
Expand Down
14 changes: 14 additions & 0 deletions src/gui/SearchWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,20 @@ void SearchWidget::setLimitGroup(bool state)
updateLimitGroup();
}

void SearchWidget::setSearchGroupName(const QString& name)
{
// Quote and escape the group name so it is treated as a single group term,
// even if it contains spaces or special characters.
QString escapedName = name;
escapedName.replace("\\", "\\\\");
escapedName.replace("\"", "\\\"");
QString searchText = QStringLiteral("g:\"") + escapedName + QStringLiteral("\" ");

m_ui->searchEdit->clear();
m_ui->searchEdit->setText(searchText);
m_ui->searchEdit->setFocus();
}

void SearchWidget::focusSearch()
{
m_ui->searchEdit->setFocus();
Expand Down
1 change: 1 addition & 0 deletions src/gui/SearchWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class SearchWidget : public QWidget
void connectSignals(SignalMultiplexer& mx);
void setCaseSensitive(bool state);
void setLimitGroup(bool state);
void setSearchGroupName(const QString& name);

protected:
// Filter key presses in the search field
Expand Down
77 changes: 77 additions & 0 deletions tests/gui/TestGui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2502,6 +2502,65 @@ void TestGui::addCannedEntries()
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
}

void TestGui::addGroup(const QString& name)
{
// Find buttons for group creation
auto* editGroupWidget = m_dbWidget->findChild<EditGroupWidget*>("editGroupWidget");
auto* nameEdit = editGroupWidget->findChild<QLineEdit*>("editName");
auto* editGroupWidgetButtonBox = editGroupWidget->findChild<QDialogButtonBox*>("buttonBox");

// Add group with specified name
Group* rootGroup = m_db->rootGroup();
m_dbWidget->groupView()->setCurrentGroup(rootGroup); // Add group on root level
m_dbWidget->createGroup();
QTest::keyClicks(nameEdit, name);
QTest::mouseClick(editGroupWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
m_dbWidget->groupView()->setCurrentGroup(rootGroup); // Reset to root level
}

void TestGui::addEntry(const QString& groupName, const QString& title, const QString& username, const QString& password)
{
// Find buttons
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild<QAction*>("actionEntryNew"));
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
auto* usernameComboBox = editEntryWidget->findChild<QComboBox*>("usernameComboBox");
auto* passwordEdit =
editEntryWidget->findChild<PasswordWidget*>("passwordEdit")->findChild<QLineEdit*>("passwordEdit");
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");

// Add entry to specified group
QVERIFY(m_dbWidget->currentGroup());
Group* group = m_dbWidget->currentGroup()->findChildByName(groupName);
m_dbWidget->groupView()->setCurrentGroup(group);

QTest::mouseClick(entryNewWidget, Qt::LeftButton);
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditEntryMode);

QTest::keyClicks(titleEdit, title);
QTest::keyClicks(usernameComboBox, username);
QTest::keyClicks(passwordEdit, password);
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);

QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::ViewMode);
m_dbWidget->groupView()->setCurrentGroup(m_db->rootGroup());
}

void TestGui::addCannedGroupsAndEntries()
{
addGroup("Finance");
addGroup("Entertainment");

addEntry("Finance", "Chase", "user1", "password");
addEntry("Finance", "Amex", "user1", "password123");
addEntry("Finance", "Capital One", "user1", "password456");

addEntry("Entertainment", "Netflix", "user1", "password");
addEntry("Entertainment", "Hulu", "user1", "password321");
addEntry("Entertainment", "Apple TV", "user1", "password123");
}

void TestGui::checkDatabase(const QString& filePath, const QString& expectedDbName)
{
auto key = QSharedPointer<CompositeKey>::create();
Expand Down Expand Up @@ -2584,3 +2643,21 @@ void TestGui::clickIndex(const QModelIndex& index,
view->scrollTo(index);
QTest::mouseClick(view->viewport(), button, stateKey, view->visualRect(index).center());
}

void TestGui::testSearchHere()
{
addCannedGroupsAndEntries();

QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
SearchWidget* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
QVERIFY(searchWidget->isEnabled());

Group* entertainmentGroup = m_dbWidget->currentGroup()->findChildByName("Entertainment");
m_dbWidget->groupView()->setCurrentGroup(entertainmentGroup);

triggerAction("actionGroupSearchHere");
QVERIFY(searchWidget->hasFocus());

QLineEdit* searchEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
QCOMPARE(searchEdit->text(), QString("g:\"/NewDatabase/Entertainment\" "));
}
4 changes: 4 additions & 0 deletions tests/gui/TestGui.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,13 @@ private slots:
void testTrayRestoreHide();
void testShortcutConfig();
void testMenuActionStates();
void testSearchHere();

private:
void addCannedEntries();
void addGroup(const QString& name);
void addEntry(const QString& groupName, const QString& title, const QString& username, const QString& password);
void addCannedGroupsAndEntries();
void checkDatabase(const QString& filePath, const QString& expectedDbName);
void checkDatabase(const QString& filePath = {});
void triggerAction(const QString& name);
Expand Down
Loading