File indexing completed on 2024-04-28 04:21:20

0001 // SPDX-FileCopyrightText: 2009-2022 Jesper K. Pedersen <jesper.pedersen@kdab.com>
0002 // SPDX-FileCopyrightText: 2010 Jan Kundrát <jkt@flaska.net>
0003 // SPDX-FileCopyrightText: 2012 Miika Turkia <miika.turkia@gmail.com>
0004 // SPDX-FileCopyrightText: 2013-2024 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0005 // SPDX-FileCopyrightText: 2014-2020 Tobias Leupold <tl@stonemx.de>
0006 // SPDX-FileCopyrightText: 2022 Friedrich W. H. Kossebau <kossebau@kde.org>
0007 //
0008 // SPDX-License-Identifier: GPL-2.0-or-later
0009 
0010 #include "TagGroupsPage.h"
0011 #include "CategoriesGroupsWidget.h"
0012 
0013 #include <DB/CategoryCollection.h>
0014 #include <MainWindow/DirtyIndicator.h>
0015 #include <kpabase/SettingsData.h>
0016 
0017 #include <KLocalizedString>
0018 #include <KMessageBox>
0019 #include <QAction>
0020 #include <QFont>
0021 #include <QGridLayout>
0022 #include <QHeaderView>
0023 #include <QInputDialog>
0024 #include <QLabel>
0025 #include <QListWidget>
0026 #include <QLocale>
0027 #include <QMenu>
0028 #include <QTreeWidget>
0029 #include <QTreeWidgetItem>
0030 
0031 Settings::TagGroupsPage::TagGroupsPage(QWidget *parent)
0032     : QWidget(parent)
0033 {
0034     QGridLayout *layout = new QGridLayout(this);
0035 
0036     // The category and group tree
0037     layout->addWidget(new QLabel(i18nc("@label", "Categories and groups:")), 0, 0);
0038     m_categoryTreeWidget = new CategoriesGroupsWidget(this);
0039     m_categoryTreeWidget->header()->hide();
0040     m_categoryTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
0041     layout->addWidget(m_categoryTreeWidget, 1, 0);
0042     connect(m_categoryTreeWidget, &CategoriesGroupsWidget::customContextMenuRequested, this, &TagGroupsPage::showTreeContextMenu);
0043     connect(m_categoryTreeWidget, &CategoriesGroupsWidget::itemClicked, this, &TagGroupsPage::slotGroupSelected);
0044 
0045     // The member list
0046     m_selectGroupToAddTags = i18nc("@label/rich", "<strong>Select a group on the left side to add tags to it</strong>");
0047     m_tagsInGroupLabel = new QLabel(m_selectGroupToAddTags);
0048     layout->addWidget(m_tagsInGroupLabel, 0, 1);
0049     m_membersListWidget = new QListWidget;
0050     m_membersListWidget->setEnabled(false);
0051     m_membersListWidget->setContextMenuPolicy(Qt::CustomContextMenu);
0052     layout->addWidget(m_membersListWidget, 1, 1);
0053     connect(m_membersListWidget, &QListWidget::itemChanged, this, &TagGroupsPage::checkItemSelection);
0054     connect(m_membersListWidget, &QListWidget::customContextMenuRequested, this, &TagGroupsPage::showMembersContextMenu);
0055 
0056     // The "pending rename actions" label
0057     m_pendingChangesLabel = new QLabel(i18nc("@label/rich", "<strong>There are pending changes on the categories page.<nl> "
0058                                                             "Please save the changes before working on tag groups.</strong>"));
0059     m_pendingChangesLabel->hide();
0060     layout->addWidget(m_pendingChangesLabel, 2, 0, 1, 2);
0061 
0062     QDialog *parentDialog = qobject_cast<QDialog *>(parent);
0063     connect(parentDialog, &QDialog::rejected, this, &TagGroupsPage::discardChanges);
0064 
0065     // Context menu actions
0066     m_newGroupAction = new QAction(i18nc("@action:inmenu", "Add group ..."), this);
0067     connect(m_newGroupAction, &QAction::triggered, this, &TagGroupsPage::slotAddGroup);
0068     m_renameAction = new QAction(this);
0069     connect(m_renameAction, &QAction::triggered, this, &TagGroupsPage::slotRenameGroup);
0070     m_deleteAction = new QAction(this);
0071     connect(m_deleteAction, &QAction::triggered, this, &TagGroupsPage::slotDeleteGroup);
0072     m_deleteMemberAction = new QAction(this);
0073     connect(m_deleteMemberAction, &QAction::triggered, this, &TagGroupsPage::slotDeleteMember);
0074     m_renameMemberAction = new QAction(this);
0075     connect(m_renameMemberAction, &QAction::triggered, this, &TagGroupsPage::slotRenameMember);
0076 
0077     m_memberMap = DB::ImageDB::instance()->memberMap();
0078 
0079     connect(DB::ImageDB::instance()->categoryCollection(), &DB::CategoryCollection::itemRemoved,
0080             &m_memberMap, &DB::MemberMap::deleteItem);
0081 
0082     connect(DB::ImageDB::instance()->categoryCollection(), &DB::CategoryCollection::itemRenamed,
0083             &m_memberMap, &DB::MemberMap::renameItem);
0084 
0085     connect(DB::ImageDB::instance()->categoryCollection(), &DB::CategoryCollection::categoryRemoved,
0086             &m_memberMap, &DB::MemberMap::deleteCategory);
0087 
0088     m_dataChanged = false;
0089 }
0090 
0091 void Settings::TagGroupsPage::updateCategoryTree()
0092 {
0093     // Store all expanded items so that they can be expanded after reload
0094     QList<QPair<QString, QString>> expandedItems = QList<QPair<QString, QString>>();
0095     for (QTreeWidgetItemIterator it { m_categoryTreeWidget }; *it; ++it) {
0096         if ((*it)->isExpanded()) {
0097             QString parentName;
0098             if ((*it)->parent() != nullptr) {
0099                 parentName = (*it)->parent()->text(0);
0100             }
0101             expandedItems.append(QPair<QString, QString>((*it)->text(0), parentName));
0102         }
0103     }
0104 
0105     m_categoryTreeWidget->clear();
0106 
0107     // Create a tree view of all groups and their sub-groups
0108 
0109     const QList<DB::CategoryPtr> categories = DB::ImageDB::instance()->categoryCollection()->categories();
0110     for (const DB::CategoryPtr &category : categories) {
0111         if (category->isSpecialCategory()) {
0112             continue;
0113         }
0114 
0115         // Add the real categories as top-level items
0116         QTreeWidgetItem *topLevelItem = new QTreeWidgetItem;
0117         topLevelItem->setText(0, category->name());
0118         topLevelItem->setFlags(topLevelItem->flags() & Qt::ItemIsEnabled);
0119         QFont font = topLevelItem->font(0);
0120         font.setWeight(QFont::Bold);
0121         topLevelItem->setFont(0, font);
0122         m_categoryTreeWidget->addTopLevelItem(topLevelItem);
0123 
0124         // Build a map with all members for each group
0125         QMap<QString, QStringList> membersForGroup;
0126         const QStringList allGroups = m_memberMap.groups(category->name());
0127         for (const QString &group : allGroups) {
0128             // FIXME: Why does the member map return an empty category?!
0129             if (group.isEmpty()) {
0130                 continue;
0131             }
0132 
0133             QStringList allMembers = m_memberMap.members(category->name(), group, true);
0134             membersForGroup[group] = allMembers;
0135         }
0136 
0137         // Add all groups (their sub-groups will be added recursively)
0138         addSubCategories(topLevelItem, membersForGroup, allGroups);
0139     }
0140 
0141     // Order the items alphabetically
0142     m_categoryTreeWidget->sortItems(0, Qt::AscendingOrder);
0143 
0144     // Re-expand all previously expanded items
0145     for (QTreeWidgetItemIterator it { m_categoryTreeWidget }; *it; ++it) {
0146         QString parentName;
0147         if ((*it)->parent() != nullptr) {
0148             parentName = (*it)->parent()->text(0);
0149         }
0150         if (expandedItems.contains(QPair<QString, QString>((*it)->text(0), parentName))) {
0151             (*it)->setExpanded(true);
0152         }
0153     }
0154 }
0155 
0156 void Settings::TagGroupsPage::addSubCategories(QTreeWidgetItem *superCategory,
0157                                                const QMap<QString, QStringList> &membersForGroup,
0158                                                const QStringList &allGroups)
0159 {
0160     // Process all group members
0161     for (auto memIt = membersForGroup.constBegin(); memIt != membersForGroup.constEnd(); ++memIt) {
0162         const QString &group = memIt.key();
0163         bool isSubGroup = false;
0164 
0165         // Search for a membership in another group for the current group
0166         for (const QStringList &members : membersForGroup) {
0167             if (members.contains(group)) {
0168                 isSubGroup = true;
0169                 break;
0170             }
0171         }
0172 
0173         // Add the group if it's not member of another group
0174         if (!isSubGroup) {
0175             QTreeWidgetItem *groupItem = new QTreeWidgetItem;
0176             groupItem->setText(0, group);
0177             superCategory->addChild(groupItem);
0178 
0179             // Search the member list for other groups
0180             QMap<QString, QStringList> subGroups;
0181             for (const QString &groupName : allGroups) {
0182                 if (membersForGroup[group].contains(groupName)) {
0183                     subGroups[groupName] = membersForGroup[groupName];
0184                 }
0185             }
0186 
0187             // If the list contains other groups, add them recursively
0188             if (subGroups.count() > 0) {
0189                 addSubCategories(groupItem, subGroups, allGroups);
0190             }
0191         }
0192     }
0193 }
0194 
0195 QString Settings::TagGroupsPage::getCategory(QTreeWidgetItem *currentItem)
0196 {
0197     while (currentItem->parent() != nullptr) {
0198         currentItem = currentItem->parent();
0199     }
0200 
0201     return currentItem->text(0);
0202 }
0203 
0204 void Settings::TagGroupsPage::showTreeContextMenu(QPoint point)
0205 {
0206     QTreeWidgetItem *currentItem = m_categoryTreeWidget->currentItem();
0207     if (currentItem == nullptr) {
0208         return;
0209     }
0210 
0211     m_currentSubCategory = currentItem->text(0);
0212 
0213     if (currentItem->parent() == nullptr) {
0214         // It's a top-level, "real" category
0215         m_currentSuperCategory.clear();
0216     } else {
0217         // It's a normal sub-category that belongs to another one
0218         m_currentSuperCategory = currentItem->parent()->text(0);
0219     }
0220 
0221     m_currentCategory = getCategory(currentItem);
0222 
0223     QMenu *menu = new QMenu;
0224     menu->addAction(m_newGroupAction);
0225 
0226     // "Real" top-level categories have to processed on the category page.
0227     if (!m_currentSuperCategory.isEmpty()) {
0228         menu->addSeparator();
0229         m_renameAction->setText(i18nc("@action:inmenu", "Rename group \"%1\"", m_currentSubCategory));
0230         menu->addAction(m_renameAction);
0231         m_deleteAction->setText(i18nc("@action:inmenu", "Delete group \"%1\"", m_currentSubCategory));
0232         menu->addAction(m_deleteAction);
0233     }
0234 
0235     menu->exec(m_categoryTreeWidget->mapToGlobal(point));
0236     delete menu;
0237 }
0238 
0239 void Settings::TagGroupsPage::categoryChanged(const QString &name)
0240 {
0241     if (name.isEmpty()) {
0242         return;
0243     }
0244 
0245     m_membersListWidget->blockSignals(true);
0246     m_membersListWidget->clear();
0247 
0248     const QStringList list = getCategoryObject(name)->items() + m_memberMap.groups(name);
0249     QStringList alreadyAdded;
0250 
0251     for (const QString &member : list) {
0252         if (member.isEmpty()) {
0253             // This can happen if we add group that currently has no members.
0254             continue;
0255         }
0256 
0257         if (!alreadyAdded.contains(member)) {
0258             alreadyAdded << member;
0259 
0260             if (DB::ImageDB::instance()->untaggedCategoryFeatureConfigured()
0261                 && !Settings::SettingsData::instance()->untaggedImagesTagVisible()) {
0262 
0263                 if (name == Settings::SettingsData::instance()->untaggedCategory()) {
0264                     if (member == Settings::SettingsData::instance()->untaggedTag()) {
0265                         continue;
0266                     }
0267                 }
0268             }
0269 
0270             QListWidgetItem *newItem = new QListWidgetItem(member, m_membersListWidget);
0271             newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable);
0272             newItem->setCheckState(Qt::Unchecked);
0273         }
0274     }
0275 
0276     m_currentGroup.clear();
0277     m_membersListWidget->clearSelection();
0278     m_membersListWidget->sortItems();
0279     m_membersListWidget->setEnabled(false);
0280     m_membersListWidget->blockSignals(false);
0281 }
0282 
0283 void Settings::TagGroupsPage::slotGroupSelected(QTreeWidgetItem *item)
0284 {
0285     // When something else than a "real" category has been selected before,
0286     // we have to save it's members.
0287     if (!m_currentGroup.isEmpty()) {
0288         saveOldGroup();
0289     }
0290 
0291     if (item->parent() == nullptr) {
0292         // A "real" category has been selected, not a group
0293         m_currentCategory.clear();
0294         m_currentGroup.clear();
0295         m_membersListWidget->setEnabled(false);
0296         categoryChanged(item->text(0));
0297         m_tagsInGroupLabel->setText(m_selectGroupToAddTags);
0298         return;
0299     }
0300 
0301     // Let's see if the category changed
0302     QString itemCategory = getCategory(item);
0303     if (m_currentCategory != itemCategory) {
0304         m_currentCategory = itemCategory;
0305         categoryChanged(m_currentCategory);
0306     }
0307 
0308     m_currentGroup = item->text(0);
0309     selectMembers(m_currentGroup);
0310     m_tagsInGroupLabel->setText(i18nc("@label", "Tags in group \"%1\" of category \"%2\"",
0311                                       m_currentGroup, m_currentCategory));
0312 }
0313 
0314 void Settings::TagGroupsPage::slotAddGroup()
0315 {
0316     bool ok;
0317     DB::CategoryPtr category = getCategoryObject(m_currentCategory);
0318     QStringList groups = m_memberMap.groups(m_currentCategory);
0319     QStringList tags;
0320     for (QString tag : category->items()) {
0321         if (!groups.contains(tag)) {
0322             tags << tag;
0323         }
0324     }
0325 
0326     //// reject existing group names:
0327     // KStringListValidator validator(groups);
0328     // QString newSubCategory = KInputDialog::getText(i18nc("@title:window","New Group"),
0329     //                                                i18nc("@label:textbox","Group name:"),
0330     //                                                QString() /*value*/,
0331     //                                                &ok,
0332     //                                                this /*parent*/,
0333     //                                                &validator,
0334     //                                                QString() /*mask*/,
0335     //                                                QString() /*WhatsThis*/,
0336     //                                                tags /*completion*/
0337     //                                                );
0338     //  FIXME: KF5-port: QInputDialog does not accept a validator,
0339     //  and KInputDialog was removed in KF5. -> Reimplement input validation using other stuff
0340     QString newSubCategory = QInputDialog::getText(this,
0341                                                    i18nc("@title:window", "New Group"),
0342                                                    i18nc("@label:textbox", "Group name:"),
0343                                                    QLineEdit::Normal,
0344                                                    QString(),
0345                                                    &ok)
0346                                  .trimmed();
0347     if (groups.contains(newSubCategory))
0348         return; // only a workaround until GUI-support for validation is restored
0349     if (!ok) {
0350         return;
0351     }
0352 
0353     // Let's see if we already have this group
0354     if (groups.contains(newSubCategory)) {
0355         // (with the validator working correctly, we should not get to this point)
0356         KMessageBox::error(this,
0357                            i18nc("@info", "<p>The group \"%1\" already exists.</p>", newSubCategory),
0358                            i18nc("@title:window", "Cannot add group"));
0359         return;
0360     }
0361 
0362     // Add the group as a new tag to the respective category
0363     MainWindow::DirtyIndicator::suppressMarkDirty(true);
0364     category->addItem(newSubCategory);
0365     MainWindow::DirtyIndicator::suppressMarkDirty(false);
0366     QMap<CategoryEdit, QString> categoryChange;
0367     categoryChange[CategoryEdit::Category] = m_currentCategory;
0368     categoryChange[CategoryEdit::Add] = newSubCategory;
0369     m_categoryChanges.append(categoryChange);
0370 
0371     // Add the group
0372     m_memberMap.addGroup(m_currentCategory, newSubCategory);
0373 
0374     // Display the new group
0375     categoryChanged(m_currentCategory);
0376 
0377     // Display the new item
0378     QTreeWidgetItem *parentItem = m_categoryTreeWidget->currentItem();
0379     addNewSubItem(newSubCategory, parentItem);
0380 
0381     // Check if we also have to update some other group (in case this is not a top-level group)
0382     if (!m_currentSuperCategory.isEmpty()) {
0383         m_memberMap.addMemberToGroup(m_currentCategory, parentItem->text(0), newSubCategory);
0384         slotGroupSelected(parentItem);
0385     }
0386 
0387     m_dataChanged = true;
0388 }
0389 
0390 void Settings::TagGroupsPage::addNewSubItem(QString &name, QTreeWidgetItem *parentItem)
0391 {
0392     QTreeWidgetItem *newItem = new QTreeWidgetItem;
0393     newItem->setText(0, name);
0394     parentItem->addChild(newItem);
0395 
0396     if (!parentItem->isExpanded()) {
0397         parentItem->setExpanded(true);
0398     }
0399 }
0400 
0401 QTreeWidgetItem *Settings::TagGroupsPage::findCategoryItem(QString category)
0402 {
0403     QTreeWidgetItem *categoryItem = nullptr;
0404     for (int i = 0; i < m_categoryTreeWidget->topLevelItemCount(); ++i) {
0405         categoryItem = m_categoryTreeWidget->topLevelItem(i);
0406         if (categoryItem->text(0) == category) {
0407             break;
0408         }
0409     }
0410 
0411     return categoryItem;
0412 }
0413 
0414 void Settings::TagGroupsPage::checkItemSelection(QListWidgetItem *)
0415 {
0416     m_dataChanged = true;
0417     saveOldGroup();
0418     updateCategoryTree();
0419 }
0420 
0421 void Settings::TagGroupsPage::slotRenameGroup()
0422 {
0423     bool ok;
0424     DB::CategoryPtr category = getCategoryObject(m_currentCategory);
0425     QStringList groups = m_memberMap.groups(m_currentCategory);
0426     QStringList tags;
0427     for (QString tag : category->items()) {
0428         if (!groups.contains(tag)) {
0429             tags << tag;
0430         }
0431     }
0432 
0433     // FIXME: reject existing group names
0434     QString newSubCategoryName = QInputDialog::getText(this,
0435                                                        i18nc("@title:window", "Rename Group"),
0436                                                        i18nc("@label:textbox", "New group name:"),
0437                                                        QLineEdit::Normal,
0438                                                        m_currentSubCategory,
0439                                                        &ok)
0440                                      .trimmed();
0441 
0442     if (!ok || m_currentSubCategory == newSubCategoryName) {
0443         return;
0444     }
0445 
0446     if (groups.contains(newSubCategoryName)) {
0447         // (with the validator working correctly, we should not get to this point)
0448         KMessageBox::error(this,
0449                            xi18nc("@info", "<para>Cannot rename group \"%1\" to \"%2\": "
0450                                            "\"%2\" already exists in category \"%3\"</para>",
0451                                   m_currentSubCategory,
0452                                   newSubCategoryName,
0453                                   m_currentCategory),
0454                            i18nc("@title:window", "Rename Group"));
0455         return;
0456     }
0457 
0458     QTreeWidgetItem *selectedGroup = m_categoryTreeWidget->currentItem();
0459 
0460     saveOldGroup();
0461 
0462     // Update the group
0463     m_memberMap.renameGroup(m_currentCategory, m_currentSubCategory, newSubCategoryName);
0464 
0465     // Update the tag in the respective category
0466     MainWindow::DirtyIndicator::suppressMarkDirty(true);
0467     category->renameItem(m_currentSubCategory, newSubCategoryName);
0468     MainWindow::DirtyIndicator::suppressMarkDirty(false);
0469     QMap<CategoryEdit, QString> categoryChange;
0470     categoryChange[CategoryEdit::Category] = m_currentCategory;
0471     categoryChange[CategoryEdit::Rename] = m_currentSubCategory;
0472     categoryChange[CategoryEdit::NewName] = newSubCategoryName;
0473     m_categoryChanges.append(categoryChange);
0474     m_dataChanged = true;
0475 
0476     // Search for all possible sub-category items in this category that have to be renamed
0477     QTreeWidgetItem *categoryItem = findCategoryItem(m_currentCategory);
0478     for (int i = 0; i < categoryItem->childCount(); ++i) {
0479         renameAllSubCategories(categoryItem->child(i), m_currentSubCategory, newSubCategoryName);
0480     }
0481 
0482     // Update the displayed items
0483     categoryChanged(m_currentCategory);
0484     slotGroupSelected(selectedGroup);
0485 
0486     m_dataChanged = true;
0487 }
0488 
0489 void Settings::TagGroupsPage::renameAllSubCategories(QTreeWidgetItem *categoryItem,
0490                                                      QString oldName,
0491                                                      QString newName)
0492 {
0493     // Probably, it item itself has to be renamed
0494     if (categoryItem->text(0) == oldName) {
0495         categoryItem->setText(0, newName);
0496     }
0497 
0498     // Also check all sub-categories recursively
0499     for (int i = 0; i < categoryItem->childCount(); ++i) {
0500         renameAllSubCategories(categoryItem->child(i), oldName, newName);
0501     }
0502 }
0503 
0504 void Settings::TagGroupsPage::slotDeleteGroup()
0505 {
0506     QTreeWidgetItem *currentItem = m_categoryTreeWidget->currentItem();
0507     QString message;
0508 
0509     if (currentItem->childCount() > 0) {
0510         message = xi18nc("@info", "<para>Really delete group \"%1\"?</para>"
0511                                   "<para>Sub-categories of this group will be moved to the super category of \"%1\" (\"%2\").<nl/> "
0512                                   "All other memberships of the sub-categories will stay intact.</para>",
0513                          m_currentSubCategory,
0514                          m_currentSuperCategory);
0515     } else {
0516         message = xi18nc("@info", "<para>Really delete group \"%1\"?</para>", m_currentSubCategory);
0517     }
0518 
0519     int res = KMessageBox::warningContinueCancel(this,
0520                                                  message,
0521                                                  i18nc("@title:window", "Delete Group"),
0522                                                  KGuiItem(i18n("&Delete"),
0523                                                           QString::fromUtf8("editdelete")));
0524     if (res == KMessageBox::Cancel) {
0525         return;
0526     }
0527 
0528     // Delete the group
0529     m_memberMap.deleteGroup(m_currentCategory, m_currentSubCategory);
0530 
0531     // Delete the tag
0532     MainWindow::DirtyIndicator::suppressMarkDirty(true);
0533     getCategoryObject(m_currentCategory)->removeItem(m_currentSubCategory);
0534     MainWindow::DirtyIndicator::suppressMarkDirty(false);
0535     QMap<CategoryEdit, QString> categoryChange;
0536     categoryChange[CategoryEdit::Category] = m_currentCategory;
0537     categoryChange[CategoryEdit::Remove] = m_currentSubCategory;
0538     m_categoryChanges.append(categoryChange);
0539     m_dataChanged = true;
0540 
0541     slotPageChange();
0542 
0543     m_dataChanged = true;
0544 }
0545 
0546 void Settings::TagGroupsPage::saveOldGroup()
0547 {
0548     QStringList list;
0549     for (int i = 0; i < m_membersListWidget->count(); ++i) {
0550         QListWidgetItem *item = m_membersListWidget->item(i);
0551         if (item->checkState() == Qt::Checked) {
0552             list << item->text();
0553         }
0554     }
0555 
0556     if (!m_currentCategory.isEmpty() && !m_currentGroup.isEmpty())
0557         m_memberMap.setMembers(m_currentCategory, m_currentGroup, list);
0558 }
0559 
0560 void Settings::TagGroupsPage::selectMembers(const QString &group)
0561 {
0562     m_membersListWidget->blockSignals(true);
0563     m_membersListWidget->setEnabled(false);
0564 
0565     m_currentGroup = group;
0566     QStringList memberList = m_memberMap.members(m_currentCategory, group, false);
0567 
0568     for (int i = 0; i < m_membersListWidget->count(); ++i) {
0569         QListWidgetItem *item = m_membersListWidget->item(i);
0570         item->setCheckState(Qt::Unchecked);
0571 
0572         if (!m_memberMap.canAddMemberToGroup(m_currentCategory, group, item->text())) {
0573             item->setFlags(item->flags() & ~Qt::ItemIsSelectable & ~Qt::ItemIsEnabled);
0574         } else {
0575             item->setFlags(item->flags() | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0576             if (memberList.contains(item->text())) {
0577                 item->setCheckState(Qt::Checked);
0578             }
0579         }
0580     }
0581 
0582     m_membersListWidget->setEnabled(true);
0583     m_membersListWidget->blockSignals(false);
0584 }
0585 
0586 void Settings::TagGroupsPage::slotPageChange()
0587 {
0588     m_tagsInGroupLabel->setText(m_selectGroupToAddTags);
0589     m_membersListWidget->setEnabled(false);
0590     m_membersListWidget->clear();
0591     m_currentCategory.clear();
0592     updateCategoryTree();
0593 }
0594 
0595 void Settings::TagGroupsPage::saveSettings()
0596 {
0597     saveOldGroup();
0598     slotPageChange();
0599     DB::ImageDB::instance()->memberMap() = m_memberMap;
0600     m_categoryChanges.clear();
0601 
0602     if (m_dataChanged) {
0603         m_dataChanged = false;
0604         MainWindow::DirtyIndicator::markDirty();
0605     }
0606 
0607     m_categoryTreeWidget->setEnabled(true);
0608     m_membersListWidget->setEnabled(true);
0609     m_pendingChangesLabel->hide();
0610 }
0611 
0612 void Settings::TagGroupsPage::discardChanges()
0613 {
0614     m_memberMap = DB::ImageDB::instance()->memberMap();
0615     slotPageChange();
0616     m_dataChanged = false;
0617 
0618     // Revert all changes to the "real" category objects
0619     MainWindow::DirtyIndicator::suppressMarkDirty(true);
0620     for (int i = m_categoryChanges.size() - 1; i >= 0; i--) {
0621         DB::CategoryPtr category = getCategoryObject(m_categoryChanges.at(i)[CategoryEdit::Category]);
0622 
0623         if (m_categoryChanges.at(i).contains(CategoryEdit::Add)) {
0624             // Remove added tags
0625             category->removeItem(m_categoryChanges.at(i)[CategoryEdit::Add]);
0626         } else if (m_categoryChanges.at(i).contains(CategoryEdit::Remove)) {
0627             // Add removed tags
0628             category->addItem(m_categoryChanges.at(i)[CategoryEdit::Add]);
0629         } else if (m_categoryChanges.at(i).contains(CategoryEdit::Rename)) {
0630             // Re-rename tags to their old name
0631             category->renameItem(m_categoryChanges.at(i)[CategoryEdit::NewName],
0632                                  m_categoryChanges.at(i)[Rename]);
0633         }
0634     }
0635     MainWindow::DirtyIndicator::suppressMarkDirty(false);
0636 
0637     m_categoryChanges.clear();
0638 
0639     m_categoryTreeWidget->setEnabled(true);
0640     m_membersListWidget->setEnabled(true);
0641     m_pendingChangesLabel->hide();
0642 }
0643 
0644 void Settings::TagGroupsPage::loadSettings()
0645 {
0646     categoryChanged(m_currentCategory);
0647     updateCategoryTree();
0648 }
0649 
0650 void Settings::TagGroupsPage::categoryChangesPending()
0651 {
0652     m_categoryTreeWidget->setEnabled(false);
0653     m_membersListWidget->setEnabled(false);
0654     m_pendingChangesLabel->show();
0655 }
0656 
0657 DB::MemberMap *Settings::TagGroupsPage::memberMap()
0658 {
0659     return &m_memberMap;
0660 }
0661 
0662 void Settings::TagGroupsPage::processDrop(QTreeWidgetItem *draggedItem, QTreeWidgetItem *targetItem)
0663 {
0664     if (targetItem->parent() != nullptr) {
0665         // Dropped on a group
0666 
0667         // Select the group
0668         m_categoryTreeWidget->setCurrentItem(targetItem);
0669         slotGroupSelected(targetItem);
0670 
0671         // Check the dragged group on the member side to make it a sub-group of the target group
0672         m_membersListWidget->findItems(draggedItem->text(0), Qt::MatchExactly)[0]->setCheckState(Qt::Checked);
0673     } else {
0674         // Dropped on a top-level category
0675 
0676         // Check if it's already a direct child of the category.
0677         // If so, we don't need to do anything.
0678         QTreeWidgetItem *parent = draggedItem->parent();
0679         if (parent->parent() == nullptr) {
0680             return;
0681         }
0682 
0683         // Select the former super group
0684         m_categoryTreeWidget->setCurrentItem(parent);
0685         slotGroupSelected(parent);
0686 
0687         // Deselect the dragged group (this will bring it to the top level)
0688         m_membersListWidget->findItems(draggedItem->text(0), Qt::MatchExactly)[0]->setCheckState(Qt::Unchecked);
0689     }
0690 }
0691 
0692 void Settings::TagGroupsPage::showMembersContextMenu(QPoint point)
0693 {
0694     if (m_membersListWidget->currentItem() == nullptr) {
0695         return;
0696     }
0697 
0698     QMenu *menu = new QMenu;
0699 
0700     m_renameMemberAction->setText(i18nc("@action:inmenu", "Rename \"%1\"", m_membersListWidget->currentItem()->text()));
0701     menu->addAction(m_renameMemberAction);
0702     m_deleteMemberAction->setText(i18nc("@action:inmenu", "Delete \"%1\"", m_membersListWidget->currentItem()->text()));
0703     menu->addAction(m_deleteMemberAction);
0704 
0705     menu->exec(m_membersListWidget->mapToGlobal(point));
0706     delete menu;
0707 }
0708 
0709 void Settings::TagGroupsPage::slotRenameMember()
0710 {
0711     bool ok;
0712     QString newTagName = QInputDialog::getText(this,
0713                                                i18nc("@title:window", "New Tag Name"),
0714                                                i18nc("@label:textbox", "Tag name:"),
0715                                                QLineEdit::Normal,
0716                                                m_membersListWidget->currentItem()->text(),
0717                                                &ok)
0718                              .trimmed();
0719     if (!ok || newTagName == m_membersListWidget->currentItem()->text()) {
0720         return;
0721     }
0722 
0723     // Update the tag name in the database
0724     MainWindow::DirtyIndicator::suppressMarkDirty(true);
0725     getCategoryObject(m_currentCategory)->renameItem(m_membersListWidget->currentItem()->text(), newTagName);
0726     MainWindow::DirtyIndicator::suppressMarkDirty(false);
0727     QMap<CategoryEdit, QString> categoryChange;
0728     categoryChange[CategoryEdit::Category] = m_currentCategory;
0729     categoryChange[CategoryEdit::Rename] = m_membersListWidget->currentItem()->text();
0730     categoryChange[CategoryEdit::NewName] = newTagName;
0731     m_categoryChanges.append(categoryChange);
0732 
0733     // Update the displayed tag name
0734     m_membersListWidget->currentItem()->setText(newTagName);
0735 
0736     // Re-order the tags, as their alphabetial order may have changed
0737     m_membersListWidget->sortItems();
0738 }
0739 
0740 void Settings::TagGroupsPage::slotDeleteMember()
0741 {
0742     QString memberToDelete = m_membersListWidget->currentItem()->text();
0743 
0744     if (m_memberMap.groups(m_currentCategory).contains(memberToDelete)) {
0745         // The item to delete is a group
0746 
0747         // Find the tag in the tree view and select it ...
0748         QTreeWidgetItemIterator it(m_categoryTreeWidget);
0749         while (*it) {
0750             if ((*it)->text(0) == memberToDelete && getCategory((*it)) == m_currentCategory) {
0751                 m_categoryTreeWidget->setCurrentItem((*it));
0752                 m_currentSubCategory = (*it)->text(0);
0753                 m_currentSuperCategory = (*it)->parent()->text(0);
0754                 break;
0755             }
0756             ++it;
0757         }
0758         // ... then delete it like it had been requested by the TreeWidget's context menu
0759         slotDeleteGroup();
0760 
0761     } else {
0762         // The item to delete is a normal tag
0763         int res = KMessageBox::warningContinueCancel(this,
0764                                                      xi18nc("@info", "<para>Do you really want to delete \"%1\"?</para>"
0765                                                                      "<para>Deleting the item will remove any information "
0766                                                                      "about it from any image containing the item.</para>",
0767                                                             memberToDelete),
0768                                                      i18nc("@title:window", "Really delete %1?", memberToDelete),
0769                                                      KGuiItem(i18n("&Delete"), QString::fromUtf8("editdelete")));
0770         if (res != KMessageBox::Continue) {
0771             return;
0772         }
0773 
0774         // Delete the tag as if it had been deleted from the annotation dialog.
0775         getCategoryObject(m_currentCategory)->removeItem(memberToDelete);
0776         slotPageChange();
0777     }
0778 }
0779 
0780 DB::CategoryPtr Settings::TagGroupsPage::getCategoryObject(QString category) const
0781 {
0782     return DB::ImageDB::instance()->categoryCollection()->categoryForName(category);
0783 }
0784 
0785 // vi:expandtab:tabstop=4 shiftwidth=4:
0786 
0787 #include "moc_TagGroupsPage.cpp"