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;