From bb8377ae6a65e5b9b2ddf2be405e70f7e51eebf3 Mon Sep 17 00:00:00 2001 From: Matthias Drexler <6493860+mddrex@users.noreply.github.com> Date: Wed, 19 Jun 2019 16:02:07 +0200 Subject: [PATCH] Allow setting group icons to children groups/entries (#3273) * Add combo menu button to apply an icon to children - allow more options to apply icons (child groups, child entries) - extend tests in TestGroup (applying icons for groups/entries only) - prevent blue folder icon being set for entries (on entry creation only) * Do not show the combo menu button for entries --- src/core/Group.cpp | 36 +++++++++- src/core/Group.h | 4 ++ src/fdosecrets/objects/Collection.cpp | 2 +- src/gui/DatabaseWidget.cpp | 2 +- src/gui/EditWidgetIcons.cpp | 36 ++++++++++ src/gui/EditWidgetIcons.h | 17 +++++ src/gui/EditWidgetIcons.ui | 36 +++++++++- src/gui/entry/EditEntryWidget.cpp | 1 + src/gui/group/EditGroupWidget.cpp | 11 +++ tests/TestGroup.cpp | 100 ++++++++++++++++++++++++++ tests/TestGroup.h | 1 + 11 files changed, 242 insertions(+), 4 deletions(-) diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 39c4a932cb..84ad531be8 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -1080,8 +1080,10 @@ Entry* Group::addEntryWithPath(const QString& entryPath) return entry; } -void Group::applyGroupIconTo(Entry* entry) +void Group::applyGroupIconOnCreateTo(Entry* entry) { + Q_ASSERT(entry); + if (!config()->get("UseGroupIconOnEntryCreation").toBool()) { return; } @@ -1090,6 +1092,13 @@ void Group::applyGroupIconTo(Entry* entry) return; } + applyGroupIconTo(entry); +} + +void Group::applyGroupIconTo(Entry* entry) +{ + Q_ASSERT(entry); + if (iconUuid().isNull()) { entry->setIcon(iconNumber()); } else { @@ -1097,6 +1106,31 @@ void Group::applyGroupIconTo(Entry* entry) } } +void Group::applyGroupIconTo(Group* other) +{ + Q_ASSERT(other); + + if (iconUuid().isNull()) { + other->setIcon(iconNumber()); + } else { + other->setIcon(iconUuid()); + } +} + +void Group::applyGroupIconToChildGroups() +{ + for (Group* recursiveChild : groupsRecursive(false)) { + applyGroupIconTo(recursiveChild); + } +} + +void Group::applyGroupIconToChildEntries() +{ + for (Entry* recursiveEntry : entriesRecursive(false)) { + applyGroupIconTo(recursiveEntry); + } +} + void Group::sortChildrenRecursively(bool reverse) { std::sort( diff --git a/src/core/Group.h b/src/core/Group.h index e9dbcdb905..9fe65d69d9 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -168,7 +168,11 @@ class Group : public QObject void addEntry(Entry* entry); void removeEntry(Entry* entry); + void applyGroupIconOnCreateTo(Entry* entry); void applyGroupIconTo(Entry* entry); + void applyGroupIconTo(Group* other); + void applyGroupIconToChildGroups(); + void applyGroupIconToChildEntries(); void sortChildrenRecursively(bool reverse = false); diff --git a/src/fdosecrets/objects/Collection.cpp b/src/fdosecrets/objects/Collection.cpp index 6926a6c20e..ccf88cecc3 100644 --- a/src/fdosecrets/objects/Collection.cpp +++ b/src/fdosecrets/objects/Collection.cpp @@ -308,7 +308,7 @@ namespace FdoSecrets entry->setUuid(QUuid::createUuid()); entry->setTitle(itemName); entry->setUsername(m_backend->database()->metadata()->defaultUserName()); - group->applyGroupIconTo(entry); + group->applyGroupIconOnCreateTo(entry); entry->setGroup(group); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index ed23273223..227e0dbd08 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -368,7 +368,7 @@ void DatabaseWidget::createEntry() m_newEntry->setUuid(QUuid::createUuid()); m_newEntry->setUsername(m_db->metadata()->defaultUserName()); m_newParent = m_groupView->currentGroup(); - m_newParent->applyGroupIconTo(m_newEntry.data()); + m_newParent->applyGroupIconOnCreateTo(m_newEntry.data()); switchToEntryEdit(m_newEntry.data(), true); } diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index dcc5160a32..fadfb1a1c0 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -45,6 +45,7 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) : QWidget(parent) , m_ui(new Ui::EditWidgetIcons()) , m_db(nullptr) + , m_applyIconTo(ApplyIconToOptions::THIS_ONLY) #ifdef WITH_XC_NETWORKING , m_netMgr(new QNetworkAccessManager(this)) , m_reply(nullptr) @@ -57,6 +58,8 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) m_ui->defaultIconsView->setModel(m_defaultIconModel); m_ui->customIconsView->setModel(m_customIconModel); + m_ui->applyIconToPushButton->setMenu(createApplyIconToMenu()); + // clang-format off connect(m_ui->defaultIconsView, SIGNAL(clicked(QModelIndex)), this, SLOT(updateRadioButtonDefaultIcons())); connect(m_ui->customIconsView, SIGNAL(clicked(QModelIndex)), this, SLOT(updateRadioButtonCustomIcons())); @@ -65,6 +68,7 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) connect(m_ui->addButton, SIGNAL(clicked()), SLOT(addCustomIconFromFile())); connect(m_ui->deleteButton, SIGNAL(clicked()), SLOT(removeCustomIcon())); connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon())); + connect(m_ui->applyIconToPushButton->menu(), SIGNAL(triggered(QAction*)), SLOT(confirmApplyIconTo(QAction*))); connect(m_ui->defaultIconsRadio, SIGNAL(toggled(bool)), this, SIGNAL(widgetUpdated())); connect(m_ui->defaultIconsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), @@ -103,6 +107,8 @@ IconStruct EditWidgetIcons::state() } } + iconStruct.applyTo = m_applyIconTo; + return iconStruct; } @@ -142,6 +148,30 @@ void EditWidgetIcons::load(const QUuid& currentUuid, m_ui->defaultIconsRadio->setChecked(true); } } + + m_applyIconTo = ApplyIconToOptions::THIS_ONLY; + m_ui->applyIconToPushButton->menu()->defaultAction()->activate(QAction::Trigger); +} + +void EditWidgetIcons::setShowApplyIconToButton(bool state) +{ + m_ui->applyIconToPushButton->setVisible(state); +} + +QMenu* EditWidgetIcons::createApplyIconToMenu() +{ + auto* applyIconToMenu = new QMenu(this); + QAction* defaultAction = applyIconToMenu->addAction(tr("Apply to this only")); + defaultAction->setData(QVariant::fromValue(ApplyIconToOptions::THIS_ONLY)); + applyIconToMenu->setDefaultAction(defaultAction); + applyIconToMenu->addSeparator(); + applyIconToMenu->addAction(tr("Also apply to child groups")) + ->setData(QVariant::fromValue(ApplyIconToOptions::CHILD_GROUPS)); + applyIconToMenu->addAction(tr("Also apply to child entries")) + ->setData(QVariant::fromValue(ApplyIconToOptions::CHILD_ENTRIES)); + applyIconToMenu->addAction(tr("Also apply to all children")) + ->setData(QVariant::fromValue(ApplyIconToOptions::ALL_CHILDREN)); + return applyIconToMenu; } void EditWidgetIcons::setUrl(const QString& url) @@ -528,3 +558,9 @@ void EditWidgetIcons::updateRadioButtonCustomIcons() { m_ui->customIconsRadio->setChecked(true); } + +void EditWidgetIcons::confirmApplyIconTo(QAction* action) +{ + m_applyIconTo = action->data().value(); + m_ui->applyIconToPushButton->setText(action->text()); +} diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index dcff02f565..e7c1b3a16d 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -19,6 +19,7 @@ #ifndef KEEPASSX_EDITWIDGETICONS_H #define KEEPASSX_EDITWIDGETICONS_H +#include #include #include #include @@ -40,12 +41,23 @@ namespace Ui class EditWidgetIcons; } +enum ApplyIconToOptions +{ + THIS_ONLY = 0b00, + CHILD_GROUPS = 0b10, + CHILD_ENTRIES = 0b01, + ALL_CHILDREN = 0b11 +}; + +Q_DECLARE_METATYPE(ApplyIconToOptions) + struct IconStruct { IconStruct(); QUuid uuid; int number; + ApplyIconToOptions applyTo; }; class EditWidgetIcons : public QWidget @@ -62,6 +74,7 @@ class EditWidgetIcons : public QWidget const QSharedPointer& database, const IconStruct& iconStruct, const QString& url = ""); + void setShowApplyIconToButton(bool state); public slots: void setUrl(const QString& url); @@ -84,11 +97,15 @@ private slots: void updateWidgetsCustomIcons(bool checked); void updateRadioButtonDefaultIcons(); void updateRadioButtonCustomIcons(); + void confirmApplyIconTo(QAction* action); private: + QMenu* createApplyIconToMenu(); + const QScopedPointer m_ui; QSharedPointer m_db; QUuid m_currentUuid; + ApplyIconToOptions m_applyIconTo; #ifdef WITH_XC_NETWORKING QUrl m_url; QUrl m_fetchUrl; diff --git a/src/gui/EditWidgetIcons.ui b/src/gui/EditWidgetIcons.ui index 84bfc8d157..932a25e912 100644 --- a/src/gui/EditWidgetIcons.ui +++ b/src/gui/EditWidgetIcons.ui @@ -88,7 +88,7 @@ - + @@ -112,6 +112,39 @@ + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + padding: 4px 10px + + + Apply icon &to ... + + + + + @@ -121,6 +154,7 @@ customIconsView addButton deleteButton + applyIconToPushButton diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 323bf22fc1..b3dd6e3b41 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -188,6 +188,7 @@ void EditEntryWidget::setupAdvanced() void EditEntryWidget::setupIcon() { + m_iconsWidget->setShowApplyIconToButton(false); addPage(tr("Icon"), FilePath::instance()->icon("apps", "preferences-desktop-icons"), m_iconsWidget); connect(this, SIGNAL(accepted()), m_iconsWidget, SLOT(abortRequests())); connect(this, SIGNAL(rejected()), m_iconsWidget, SLOT(abortRequests())); diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp index 051f23d4ba..65e03e82bd 100644 --- a/src/gui/group/EditGroupWidget.cpp +++ b/src/gui/group/EditGroupWidget.cpp @@ -212,6 +212,17 @@ void EditGroupWidget::apply() // Icons add/remove are applied globally outside the transaction! m_group->copyDataFrom(m_temporaryGroup.data()); + // Assign the icon to children if selected + if (iconStruct.applyTo == ApplyIconToOptions::CHILD_GROUPS + || iconStruct.applyTo == ApplyIconToOptions::ALL_CHILDREN) { + m_group->applyGroupIconToChildGroups(); + } + + if (iconStruct.applyTo == ApplyIconToOptions::CHILD_ENTRIES + || iconStruct.applyTo == ApplyIconToOptions::ALL_CHILDREN) { + m_group->applyGroupIconToChildEntries(); + } + setModified(false); } diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index 9c3e522314..f78cb96af0 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -1081,3 +1081,103 @@ void TestGroup::testHierarchy() QVERIFY(hierarchy.contains("group2")); QVERIFY(hierarchy.contains("group3")); } + +void TestGroup::testApplyGroupIconRecursively() +{ + // Create a database with two nested groups with one entry each + Database* database = new Database(); + + Group* subgroup = new Group(); + subgroup->setName("Subgroup"); + subgroup->setParent(database->rootGroup()); + QVERIFY(subgroup); + + Group* subsubgroup = new Group(); + subsubgroup->setName("Subsubgroup"); + subsubgroup->setParent(subgroup); + QVERIFY(subsubgroup); + + Entry* subgroupEntry = subgroup->addEntryWithPath("Subgroup entry"); + QVERIFY(subgroupEntry); + subgroup->setIcon(1); + + Entry* subsubgroupEntry = subsubgroup->addEntryWithPath("Subsubgroup entry"); + QVERIFY(subsubgroupEntry); + subsubgroup->setIcon(2); + + // Set an icon per number to the root group and apply recursively + // -> all groups and entries have the same icon + const int rootIconNumber = 42; + database->rootGroup()->setIcon(rootIconNumber); + QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber); + database->rootGroup()->applyGroupIconToChildGroups(); + database->rootGroup()->applyGroupIconToChildEntries(); + QVERIFY(subgroup->iconNumber() == rootIconNumber); + QVERIFY(subgroupEntry->iconNumber() == rootIconNumber); + QVERIFY(subsubgroup->iconNumber() == rootIconNumber); + QVERIFY(subsubgroupEntry->iconNumber() == rootIconNumber); + + // Set an icon per number to the subsubgroup and apply recursively + // -> only the subsubgroup related groups and entries have updated icons + const int subsubgroupIconNumber = 24; + subsubgroup->setIcon(subsubgroupIconNumber); + QVERIFY(subsubgroup->iconNumber() == subsubgroupIconNumber); + subsubgroup->applyGroupIconToChildGroups(); + subsubgroup->applyGroupIconToChildEntries(); + QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber); + QVERIFY(subgroup->iconNumber() == rootIconNumber); + QVERIFY(subgroupEntry->iconNumber() == rootIconNumber); + QVERIFY(subsubgroup->iconNumber() == subsubgroupIconNumber); + QVERIFY(subsubgroupEntry->iconNumber() == subsubgroupIconNumber); + + // Set an icon per UUID to the subgroup and apply recursively + // -> all groups and entries except the root group have the same icon + const QUuid subgroupIconUuid = QUuid::createUuid(); + QImage subgroupIcon(16, 16, QImage::Format_RGB32); + subgroupIcon.setPixel(0, 0, qRgb(255, 0, 0)); + database->metadata()->addCustomIcon(subgroupIconUuid, subgroupIcon); + subgroup->setIcon(subgroupIconUuid); + subgroup->applyGroupIconToChildGroups(); + subgroup->applyGroupIconToChildEntries(); + QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber); + QCOMPARE(subgroup->iconUuid(), subgroupIconUuid); + QCOMPARE(subgroup->icon(), subgroupIcon); + QCOMPARE(subgroupEntry->iconUuid(), subgroupIconUuid); + QCOMPARE(subgroupEntry->icon(), subgroupIcon); + QCOMPARE(subsubgroup->iconUuid(), subgroupIconUuid); + QCOMPARE(subsubgroup->icon(), subgroupIcon); + QCOMPARE(subsubgroupEntry->iconUuid(), subgroupIconUuid); + QCOMPARE(subsubgroupEntry->icon(), subgroupIcon); + + // Reset all icons to root icon + database->rootGroup()->setIcon(rootIconNumber); + QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber); + database->rootGroup()->applyGroupIconToChildGroups(); + database->rootGroup()->applyGroupIconToChildEntries(); + QVERIFY(subgroup->iconNumber() == rootIconNumber); + QVERIFY(subgroupEntry->iconNumber() == rootIconNumber); + QVERIFY(subsubgroup->iconNumber() == rootIconNumber); + QVERIFY(subsubgroupEntry->iconNumber() == rootIconNumber); + + // Apply only for child groups + const int iconForGroups = 10; + database->rootGroup()->setIcon(iconForGroups); + QVERIFY(database->rootGroup()->iconNumber() == iconForGroups); + database->rootGroup()->applyGroupIconToChildGroups(); + QVERIFY(database->rootGroup()->iconNumber() == iconForGroups); + QVERIFY(subgroup->iconNumber() == iconForGroups); + QVERIFY(subgroupEntry->iconNumber() == rootIconNumber); + QVERIFY(subsubgroup->iconNumber() == iconForGroups); + QVERIFY(subsubgroupEntry->iconNumber() == rootIconNumber); + + // Apply only for child entries + const int iconForEntries = 20; + database->rootGroup()->setIcon(iconForEntries); + QVERIFY(database->rootGroup()->iconNumber() == iconForEntries); + database->rootGroup()->applyGroupIconToChildEntries(); + QVERIFY(database->rootGroup()->iconNumber() == iconForEntries); + QVERIFY(subgroup->iconNumber() == iconForGroups); + QVERIFY(subgroupEntry->iconNumber() == iconForEntries); + QVERIFY(subsubgroup->iconNumber() == iconForGroups); + QVERIFY(subsubgroupEntry->iconNumber() == iconForEntries); +} diff --git a/tests/TestGroup.h b/tests/TestGroup.h index 6242e4ff41..0ee7359490 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -47,6 +47,7 @@ private slots: void testEquals(); void testChildrenSort(); void testHierarchy(); + void testApplyGroupIconRecursively(); }; #endif // KEEPASSX_TESTGROUP_H