diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index f974db170b..7b6ba6c537 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -6432,6 +6432,14 @@ Expect some bugs and minor issues, this version is meant for testing purposes.
+
+ Search Entries in selected Group
+
+
+
+ Search Here…
+
+
ManageDatabase
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 8ef9ede40f..63f44bc102 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -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"
@@ -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"));
@@ -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)));
@@ -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());
@@ -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()) {
@@ -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,
diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h
index 8effd06f43..b5456b95b3 100644
--- a/src/gui/MainWindow.h
+++ b/src/gui/MainWindow.h
@@ -155,6 +155,7 @@ private slots:
void enableMenuAndToolbar();
void disableMenuAndToolbar();
void clearSSHAgent();
+ void searchInGroup();
private:
static const QString BaseWindowTitle;
diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui
index 79f2ab36ca..2e6ff6c31e 100644
--- a/src/gui/MainWindow.ui
+++ b/src/gui/MainWindow.ui
@@ -366,6 +366,8 @@
+
+
diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp
index 13c08318ae..adbe13f331 100644
--- a/src/gui/SearchWidget.cpp
+++ b/src/gui/SearchWidget.cpp
@@ -223,6 +223,18 @@ 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("\"", "\\\"");
+ QString searchText = QStringLiteral("g:\"") + escapedName + QStringLiteral("\" ");
+
+ m_ui->searchEdit->setText(searchText);
+ m_ui->searchEdit->setFocus();
+}
+
void SearchWidget::focusSearch()
{
m_ui->searchEdit->setFocus();
diff --git a/src/gui/SearchWidget.h b/src/gui/SearchWidget.h
index bfc75de673..ed4eba6d10 100644
--- a/src/gui/SearchWidget.h
+++ b/src/gui/SearchWidget.h
@@ -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
diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp
index 4c95669290..eacf888570 100644
--- a/tests/gui/TestGui.cpp
+++ b/tests/gui/TestGui.cpp
@@ -2500,6 +2500,62 @@ void TestGui::addCannedEntries()
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
QTest::keyClicks(titleEdit, "something 3");
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
+
+ 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::addGroup(const QString& name)
+{
+ // Find buttons for group creation
+ auto* editGroupWidget = m_dbWidget->findChild("editGroupWidget");
+ auto* nameEdit = editGroupWidget->findChild("editName");
+ auto* editGroupWidgetButtonBox = editGroupWidget->findChild("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("toolBar");
+ QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild("actionEntryNew"));
+ auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget");
+ auto* titleEdit = editEntryWidget->findChild("titleEdit");
+ auto* usernameComboBox = editEntryWidget->findChild("usernameComboBox");
+ auto* passwordEdit =
+ editEntryWidget->findChild("passwordEdit")->findChild("passwordEdit");
+ auto* editEntryWidgetButtonBox = editEntryWidget->findChild("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::checkDatabase(const QString& filePath, const QString& expectedDbName)
@@ -2584,3 +2640,22 @@ void TestGui::clickIndex(const QModelIndex& index,
view->scrollTo(index);
QTest::mouseClick(view->viewport(), button, stateKey, view->visualRect(index).center());
}
+
+void TestGui::testSearchHere()
+{
+ addCannedEntries();
+
+ QToolBar* toolBar = m_mainWindow->findChild("toolBar");
+ SearchWidget* searchWidget = toolBar->findChild("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("searchEdit");
+ QString expectedText = QString("g:\"") + entertainmentGroup->fullPath() + QString("\" ");
+ QCOMPARE(searchEdit->text(), expectedText);
+}
diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h
index 514f7ce95b..dc40f21a5a 100644
--- a/tests/gui/TestGui.h
+++ b/tests/gui/TestGui.h
@@ -71,9 +71,12 @@ 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 checkDatabase(const QString& filePath, const QString& expectedDbName);
void checkDatabase(const QString& filePath = {});
void triggerAction(const QString& name);