mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2026-03-11 08:54:48 +00:00
Add Show Subgroup Entries feature
Co-authored-by: juzu-o <3142026+juzu-o@users.noreply.github.com> Fix drag-and-drop entry refresh to use subgroup entries setting Co-authored-by: juzu-o <3142026+juzu-o@users.noreply.github.com> Fix EntryModel connections for subgroup entries to receive all group signals Co-authored-by: juzu-o <3142026+juzu-o@users.noreply.github.com> Fix EntryModel to handle new subgroup entries immediately when Show Subgroup Entries is enabled Co-authored-by: juzu-o <3142026+juzu-o@users.noreply.github.com> Fix EntryModel to always connect to current group when Show Subgroup Entries is enabled Co-authored-by: juzu-o <3142026+juzu-o@users.noreply.github.com> Fix memory crash by using QPointer for group references in EntryModel Co-authored-by: juzu-o <3142026+juzu-o@users.noreply.github.com> Fix compilation errors by changing QSet<QPointer<const Group>> to QList<QPointer<const Group>> Co-authored-by: juzu-o <3142026+juzu-o@users.noreply.github.com> Update src/gui/entry/EntryModel.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Update src/gui/entry/EntryModel.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
61fdb85a3c
commit
a299e5796a
9 changed files with 156 additions and 17 deletions
|
|
@ -577,6 +577,10 @@
|
|||
<source>Skip confirmation for main window Auto-Type actions</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show subgroup entries in entry list view</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto-generate password for new entries</source>
|
||||
<translation type="unfinished"></translation>
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
|||
{Config::GUI_CheckForUpdatesIncludeBetas, {QS("GUI/CheckForUpdatesIncludeBetas"), 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, {}}},
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ public:
|
|||
SearchWaitForEnter,
|
||||
GUI_ShowExpiredEntriesOnDatabaseUnlock,
|
||||
GUI_ShowExpiredEntriesOnDatabaseUnlockOffsetDays,
|
||||
GUI_ShowSubgroupEntries,
|
||||
GUI_FontSizeOffset,
|
||||
|
||||
GUI_MainWindowGeometry,
|
||||
|
|
|
|||
|
|
@ -218,6 +218,7 @@ void ApplicationSettingsWidget::loadSettings()
|
|||
m_generalUi->dropToBackgroundOnCopyRadioButton->setChecked(config()->get(Config::DropToBackgroundOnCopy).toBool());
|
||||
m_generalUi->useGroupIconOnEntryCreationCheckBox->setChecked(
|
||||
config()->get(Config::UseGroupIconOnEntryCreation).toBool());
|
||||
m_generalUi->showSubgroupEntriesCheckBox->setChecked(config()->get(Config::GUI_ShowSubgroupEntries).toBool());
|
||||
m_generalUi->autoTypeEntryTitleMatchCheckBox->setChecked(config()->get(Config::AutoTypeEntryTitleMatch).toBool());
|
||||
m_generalUi->autoTypeEntryURLMatchCheckBox->setChecked(config()->get(Config::AutoTypeEntryURLMatch).toBool());
|
||||
m_generalUi->autoTypeHideExpiredEntryCheckBox->setChecked(config()->get(Config::AutoTypeHideExpiredEntry).toBool());
|
||||
|
|
@ -394,6 +395,7 @@ void ApplicationSettingsWidget::saveSettings()
|
|||
config()->set(Config::MinimizeOnCopy, m_generalUi->minimizeOnCopyRadioButton->isChecked());
|
||||
config()->set(Config::DropToBackgroundOnCopy, m_generalUi->dropToBackgroundOnCopyRadioButton->isChecked());
|
||||
config()->set(Config::UseGroupIconOnEntryCreation, m_generalUi->useGroupIconOnEntryCreationCheckBox->isChecked());
|
||||
config()->set(Config::GUI_ShowSubgroupEntries, m_generalUi->showSubgroupEntriesCheckBox->isChecked());
|
||||
config()->set(Config::AutoTypeEntryTitleMatch, m_generalUi->autoTypeEntryTitleMatchCheckBox->isChecked());
|
||||
config()->set(Config::AutoTypeEntryURLMatch, m_generalUi->autoTypeEntryURLMatchCheckBox->isChecked());
|
||||
config()->set(Config::AutoTypeHideExpiredEntry, m_generalUi->autoTypeHideExpiredEntryCheckBox->isChecked());
|
||||
|
|
|
|||
|
|
@ -573,6 +573,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showSubgroupEntriesCheckBox">
|
||||
<property name="text">
|
||||
<string>Show subgroup entries in entry list view</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="minimizeOnOpenUrlCheckBox">
|
||||
<property name="text">
|
||||
|
|
@ -1441,6 +1448,7 @@
|
|||
<tabstop>EnableCopyOnDoubleClickCheckBox</tabstop>
|
||||
<tabstop>openUrlOnDoubleClick</tabstop>
|
||||
<tabstop>useGroupIconOnEntryCreationCheckBox</tabstop>
|
||||
<tabstop>showSubgroupEntriesCheckBox</tabstop>
|
||||
<tabstop>minimizeOnOpenUrlCheckBox</tabstop>
|
||||
<tabstop>hideWindowOnCopyCheckBox</tabstop>
|
||||
<tabstop>minimizeOnCopyRadioButton</tabstop>
|
||||
|
|
|
|||
|
|
@ -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<Entry*>& 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();
|
||||
|
|
@ -539,7 +569,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();
|
||||
}
|
||||
|
|
@ -555,7 +590,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();
|
||||
}
|
||||
|
|
@ -571,7 +611,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();
|
||||
}
|
||||
|
|
@ -587,11 +632,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);
|
||||
|
|
@ -607,6 +667,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;
|
||||
}
|
||||
|
|
@ -618,8 +684,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@
|
|||
#define KEEPASSX_ENTRYMODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QList>
|
||||
#include <QPixmap>
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
|
||||
#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<Group> m_group;
|
||||
QList<Entry*> m_entries;
|
||||
QList<Entry*> m_orgEntries;
|
||||
QSet<const Group*> m_allGroups;
|
||||
QList<QPointer<const Group>> m_allGroups;
|
||||
|
||||
const QString HiddenContentDisplay;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
@ -210,7 +215,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;
|
||||
}
|
||||
|
|
@ -356,7 +370,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));
|
||||
}
|
||||
|
|
@ -386,11 +401,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);
|
||||
|
|
@ -462,7 +487,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);
|
||||
|
|
@ -597,6 +623,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
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include <QTreeView>
|
||||
|
||||
#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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue