diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index f974db170..3e2cd9eb7 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -5997,6 +5997,10 @@ Are you sure you want to continue with this file?
Show Toolbar
+
+ Show Entries of Subgroups
+
+
Show Preview Panel
@@ -6350,6 +6354,10 @@ Expect some bugs and minor issues, this version is meant for testing purposes.
Toggle Show Toolbar
+
+ Toggle Show Entries of Subgroups
+
+
Toggle Show Preview Panel
diff --git a/src/core/Config.cpp b/src/core/Config.cpp
index 2c2b0bc57..e0bd6b76d 100644
--- a/src/core/Config.cpp
+++ b/src/core/Config.cpp
@@ -122,6 +122,7 @@ static const QHash configStrings = {
{Config::GUI_SearchWaitForEnter, {QS("GUI/SearchWaitForEnter"), Roaming, false}},
{Config::GUI_ShowExpiredEntriesOnDatabaseUnlock, {QS("GUI/ShowExpiredEntriesOnDatabaseUnlock"), Roaming, true}},
{Config::GUI_ShowExpiredEntriesOnDatabaseUnlockOffsetDays, {QS("GUI/ShowExpiredEntriesOnDatabaseUnlockOffsetDays"), Roaming, 3}},
+ {Config::GUI_ShowSubgroupEntries, {QS("GUI/ShowSubgroupEntries"), Roaming, false}},
{Config::GUI_FontSizeOffset, {QS("GUI/FontSizeOffset"), Local, 0}},
{Config::GUI_MainWindowGeometry, {QS("GUI/MainWindowGeometry"), Local, {}}},
diff --git a/src/core/Config.h b/src/core/Config.h
index 8f54f9c01..563e18097 100644
--- a/src/core/Config.h
+++ b/src/core/Config.h
@@ -103,6 +103,7 @@ public:
GUI_SearchWaitForEnter,
GUI_ShowExpiredEntriesOnDatabaseUnlock,
GUI_ShowExpiredEntriesOnDatabaseUnlockOffsetDays,
+ GUI_ShowSubgroupEntries,
GUI_FontSizeOffset,
GUI_MainWindowGeometry,
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 25b96c5ae..6cf3ac46e 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -2059,6 +2059,11 @@ void MainWindow::initViewMenu()
applySettingsChanges();
});
+ m_ui->actionShowEntriesOfSubgroups->setChecked(config()->get(Config::GUI_ShowSubgroupEntries).toBool());
+ connect(m_ui->actionShowEntriesOfSubgroups, &QAction::toggled, this, [](bool checked) {
+ config()->set(Config::GUI_ShowSubgroupEntries, checked);
+ });
+
m_ui->actionShowGroupPanel->setChecked(!config()->get(Config::GUI_HideGroupPanel).toBool());
connect(m_ui->actionShowGroupPanel, &QAction::toggled, this, [](bool checked) {
config()->set(Config::GUI_HideGroupPanel, !checked);
diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui
index 79f2ab36c..7627a8874 100644
--- a/src/gui/MainWindow.ui
+++ b/src/gui/MainWindow.ui
@@ -399,6 +399,8 @@
+
+
@@ -1244,6 +1246,17 @@
Ctrl+Shift+A
+
+
+ true
+
+
+ Show Entries of Subgroups
+
+
+ Toggle Show Entries of Subgroups
+
+
true
diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp
index 9ae51743d..51f54ea08 100644
--- a/src/gui/entry/EntryModel.cpp
+++ b/src/gui/entry/EntryModel.cpp
@@ -68,10 +68,38 @@ void EntryModel::setGroup(Group* group)
m_group = group;
m_allGroups.clear();
- m_entries = group->entries();
- m_orgEntries.clear();
- makeConnections(group);
+ // Check if we should show subgroup entries
+ if (config()->get(Config::GUI_ShowSubgroupEntries).toBool()) {
+ m_entries = group->entriesRecursive();
+
+ // When showing subgroup entries, we need to connect to all groups
+ // that contain the entries being displayed
+ for (const auto& entry : asConst(m_entries)) {
+ if (entry->group() && !m_allGroups.contains(entry->group())) {
+ m_allGroups.append(entry->group());
+ }
+ }
+
+ // Always include the current group itself to handle new entries added directly to it
+ if (!m_allGroups.contains(group)) {
+ m_allGroups.append(group);
+ }
+
+ // Connect to all groups that have entries in the view (or could have entries)
+ for (const auto& groupToConnect : m_allGroups) {
+ if (groupToConnect) {
+ makeConnections(groupToConnect);
+ }
+ }
+
+ // Also connect to groupAdded signal from the main group to detect new subgroups
+ connect(group, SIGNAL(groupAdded()), SLOT(groupAdded()));
+ } else {
+ m_entries = group->entries();
+ makeConnections(group);
+ }
+ m_orgEntries.clear();
endResetModel();
}
@@ -88,13 +116,15 @@ void EntryModel::setEntries(const QList& entries)
m_orgEntries = entries;
for (const auto entry : asConst(m_entries)) {
- if (entry->group()) {
- m_allGroups.insert(entry->group());
+ if (entry->group() && !m_allGroups.contains(entry->group())) {
+ m_allGroups.append(entry->group());
}
}
for (const auto group : m_allGroups) {
- makeConnections(group);
+ if (group) {
+ makeConnections(group);
+ }
}
endResetModel();
@@ -530,7 +560,12 @@ void EntryModel::entryAdded(Entry* entry)
}
if (m_group) {
- m_entries = m_group->entries();
+ // Check if we should show subgroup entries
+ if (config()->get(Config::GUI_ShowSubgroupEntries).toBool()) {
+ m_entries = m_group->entriesRecursive();
+ } else {
+ m_entries = m_group->entries();
+ }
}
endInsertRows();
}
@@ -546,7 +581,12 @@ void EntryModel::entryAboutToRemove(Entry* entry)
void EntryModel::entryRemoved()
{
if (m_group) {
- m_entries = m_group->entries();
+ // Check if we should show subgroup entries
+ if (config()->get(Config::GUI_ShowSubgroupEntries).toBool()) {
+ m_entries = m_group->entriesRecursive();
+ } else {
+ m_entries = m_group->entries();
+ }
}
endRemoveRows();
}
@@ -562,7 +602,12 @@ void EntryModel::entryAboutToMoveUp(int row)
void EntryModel::entryMovedUp()
{
if (m_group) {
- m_entries = m_group->entries();
+ // Check if we should show subgroup entries
+ if (config()->get(Config::GUI_ShowSubgroupEntries).toBool()) {
+ m_entries = m_group->entriesRecursive();
+ } else {
+ m_entries = m_group->entries();
+ }
}
endMoveRows();
}
@@ -578,11 +623,26 @@ void EntryModel::entryAboutToMoveDown(int row)
void EntryModel::entryMovedDown()
{
if (m_group) {
- m_entries = m_group->entries();
+ // Check if we should show subgroup entries
+ if (config()->get(Config::GUI_ShowSubgroupEntries).toBool()) {
+ m_entries = m_group->entriesRecursive();
+ } else {
+ m_entries = m_group->entries();
+ }
}
endMoveRows();
}
+void EntryModel::groupAdded()
+{
+ // When a new subgroup is added to the current group and we're showing subgroup entries,
+ // we need to refresh our connections to include the new subgroup
+ if (m_group && config()->get(Config::GUI_ShowSubgroupEntries).toBool()) {
+ // Refresh the entry list and connections to include new subgroups
+ setGroup(m_group);
+ }
+}
+
void EntryModel::entryDataChanged(Entry* entry)
{
int row = m_entries.indexOf(entry);
@@ -598,6 +658,12 @@ void EntryModel::onConfigChanged(Config::ConfigKey key)
case Config::GUI_HidePasswords:
emit dataChanged(index(0, Password), index(rowCount() - 1, Password), {Qt::DisplayRole});
break;
+ case Config::GUI_ShowSubgroupEntries:
+ // Refresh the entry list when the subgroup setting changes
+ if (m_group) {
+ setGroup(m_group);
+ }
+ break;
default:
break;
}
@@ -609,8 +675,17 @@ void EntryModel::severConnections()
disconnect(m_group, nullptr, this, nullptr);
}
- for (const Group* group : asConst(m_allGroups)) {
- disconnect(group, nullptr, this, nullptr);
+ // Use an iterator to safely remove null pointers while iterating
+ auto it = m_allGroups.begin();
+ while (it != m_allGroups.end()) {
+ if (*it) {
+ // Group is still valid, disconnect from it
+ disconnect(*it, nullptr, this, nullptr);
+ ++it;
+ } else {
+ // Group has been deleted, remove the null pointer
+ it = m_allGroups.erase(it);
+ }
}
}
diff --git a/src/gui/entry/EntryModel.h b/src/gui/entry/EntryModel.h
index ff467ee97..54f90c29e 100644
--- a/src/gui/entry/EntryModel.h
+++ b/src/gui/entry/EntryModel.h
@@ -19,7 +19,9 @@
#define KEEPASSX_ENTRYMODEL_H
#include
+#include
#include
+#include
#include
#include "core/Config.h"
@@ -81,6 +83,7 @@ private slots:
void entryAboutToMoveDown(int row);
void entryMovedDown();
void entryDataChanged(Entry* entry);
+ void groupAdded();
void onConfigChanged(Config::ConfigKey key);
@@ -89,10 +92,10 @@ private:
void makeConnections(const Group* group);
bool m_backgroundColorVisible = true;
- Group* m_group;
+ QPointer m_group;
QList m_entries;
QList m_orgEntries;
- QSet m_allGroups;
+ QList> m_allGroups;
const QString HiddenContentDisplay;
};
diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp
index 2c176eccf..5ffa41c95 100644
--- a/src/gui/entry/EntryView.cpp
+++ b/src/gui/entry/EntryView.cpp
@@ -33,6 +33,8 @@
#include "gui/Icons.h"
#include "gui/SortFilterHideProxyModel.h"
+#include "core/Config.h"
+
#define ICON_ONLY_SECTION_SIZE 26
class PasswordStrengthItemDelegate : public QStyledItemDelegate
@@ -94,6 +96,9 @@ EntryView::EntryView(QWidget* parent)
emit entrySelectionChanged(currentEntry());
});
+ // Listen for config changes to update Group column visibility
+ connect(config(), &Config::changed, this, &EntryView::onConfigChanged);
+
new QShortcut(Qt::CTRL + Qt::Key_F10, this, SLOT(contextMenuShortcutPressed()), nullptr, Qt::WidgetShortcut);
new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_J, this, SLOT(jumpToGroupShortcut()), nullptr, Qt::WidgetShortcut);
@@ -214,7 +219,16 @@ void EntryView::focusInEvent(QFocusEvent* event)
void EntryView::displayGroup(Group* group)
{
m_model->setGroup(group);
- header()->hideSection(EntryModel::ParentGroup);
+
+ // Show Group column when subgroup entries are enabled, since entries from different groups will be shown
+ // But respect user's preference if they've manually hidden it
+ if (config()->get(Config::GUI_ShowSubgroupEntries).toBool() && !m_userHidGroupColumnInSubgroupMode) {
+ header()->showSection(EntryModel::ParentGroup);
+ } else if (!config()->get(Config::GUI_ShowSubgroupEntries).toBool()) {
+ header()->hideSection(EntryModel::ParentGroup);
+ }
+ // If user has hidden the column in subgroup mode, don't force it to show
+
setFirstEntryActive();
m_inSearchMode = false;
}
@@ -360,7 +374,8 @@ void EntryView::showHeaderMenu(const QPoint& position)
int columnIndex = action->data().toInt();
action->setChecked(!isColumnHidden(columnIndex));
}
- actions[EntryModel::ParentGroup]->setVisible(inSearchMode());
+ actions[EntryModel::ParentGroup]->setVisible(inSearchMode()
+ || config()->get(Config::GUI_ShowSubgroupEntries).toBool());
m_headerMenu->popup(mapToGlobal(position));
}
@@ -390,11 +405,21 @@ void EntryView::toggleColumnVisibility(QAction* action)
if (header()->sectionSize(columnIndex) == 0) {
header()->resizeSection(columnIndex, header()->defaultSectionSize());
}
+ // Reset flag when user manually shows Group column
+ if (columnIndex == EntryModel::ParentGroup && !m_inSearchMode
+ && config()->get(Config::GUI_ShowSubgroupEntries).toBool()) {
+ m_userHidGroupColumnInSubgroupMode = false;
+ }
resetFixedColumns();
return;
}
if ((header()->count() - header()->hiddenSectionCount()) > 1) {
header()->hideSection(columnIndex);
+ // Track when user manually hides Group column while subgroup entries is enabled
+ if (columnIndex == EntryModel::ParentGroup && !m_inSearchMode
+ && config()->get(Config::GUI_ShowSubgroupEntries).toBool()) {
+ m_userHidGroupColumnInSubgroupMode = true;
+ }
return;
}
action->setChecked(true);
@@ -466,7 +491,8 @@ void EntryView::resetFixedColumns()
void EntryView::resetViewToDefaults()
{
// Reduce number of columns that are shown by default
- if (m_inSearchMode) {
+ if (m_inSearchMode
+ || (config()->get(Config::GUI_ShowSubgroupEntries).toBool() && !m_userHidGroupColumnInSubgroupMode)) {
header()->showSection(EntryModel::ParentGroup);
} else {
header()->hideSection(EntryModel::ParentGroup);
@@ -602,6 +628,22 @@ bool EntryView::isColumnHidden(int logicalIndex)
return header()->isSectionHidden(logicalIndex) || header()->sectionSize(logicalIndex) == 0;
}
+void EntryView::onConfigChanged(Config::ConfigKey key)
+{
+ if (key == Config::GUI_ShowSubgroupEntries && !m_inSearchMode) {
+ // Reset user preference when setting is toggled - this allows the
+ // Group column to auto-appear when re-enabling the feature
+ m_userHidGroupColumnInSubgroupMode = false;
+
+ // Update Group column visibility when subgroup entries setting changes
+ if (config()->get(Config::GUI_ShowSubgroupEntries).toBool()) {
+ header()->showSection(EntryModel::ParentGroup);
+ } else {
+ header()->hideSection(EntryModel::ParentGroup);
+ }
+ }
+}
+
void EntryView::jumpToGroupShortcut()
{
// Only allow jump to group in search mode
diff --git a/src/gui/entry/EntryView.h b/src/gui/entry/EntryView.h
index f74e6a3e7..90a161fa2 100644
--- a/src/gui/entry/EntryView.h
+++ b/src/gui/entry/EntryView.h
@@ -21,6 +21,7 @@
#include
+#include "core/Config.h"
#include "gui/entry/EntryModel.h"
class Entry;
@@ -72,6 +73,7 @@ private slots:
void resetViewToDefaults();
void contextMenuShortcutPressed();
void sortIndicatorChanged(int logicalIndex, Qt::SortOrder order);
+ void onConfigChanged(Config::ConfigKey key);
void jumpToGroupShortcut();
private:
@@ -85,6 +87,7 @@ private:
Qt::SortOrder m_lastOrder;
bool m_inSearchMode = false;
bool m_columnsNeedRelayout = true;
+ bool m_userHidGroupColumnInSubgroupMode = false;
QMenu* m_headerMenu;
QActionGroup* m_columnActions;