diff --git a/core/libs/tags/manager/models/tagmngrlistitem.cpp b/core/libs/tags/manager/models/tagmngrlistitem.cpp index 2be1e23247..d288402bee 100644 --- a/core/libs/tags/manager/models/tagmngrlistitem.cpp +++ b/core/libs/tags/manager/models/tagmngrlistitem.cpp @@ -1,208 +1,213 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-08-22 * Description : List View Item for List View Model * * Copyright (C) 2013 by Veaceslav Munteanu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "tagmngrlistitem.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "album.h" namespace Digikam { class Q_DECL_HIDDEN ListItem::Private { public: explicit Private() { parentItem = nullptr; } QList childItems; + QList toDelItems; QList itemData; QList tagIds; - QList tagsToDel; ListItem* parentItem; }; ListItem::ListItem(QList& data, ListItem* const parent) : d(new Private()) { d->parentItem = parent; d->itemData.append(data); data.removeFirst(); foreach (const QVariant& val, data) { d->tagIds.append(val.toInt()); } } ListItem::~ListItem() { qDeleteAll(d->childItems); + qDeleteAll(d->toDelItems); delete d; } void ListItem::deleteChild(ListItem* const item) { - d->childItems.removeOne(item); + int row = d->childItems.indexOf(item); + + if (row != -1) + deleteChild(row); } QList ListItem::allChildren() const { return d->childItems; } QList ListItem::getTagIds() const { return d->tagIds; } void ListItem::appendChild(ListItem* const item) { d->childItems.append(item); } void ListItem::removeTagId(int tagId) { d->tagIds.removeOne(tagId); } ListItem* ListItem::child(int row) const { return d->childItems.value(row); } int ListItem::childCount() const { return d->childItems.count(); } void ListItem::deleteChild(int row) { - return d->childItems.removeAt(row); + d->toDelItems << d->childItems.takeAt(row); } void ListItem::removeAll() { + d->toDelItems << d->childItems; d->childItems.clear(); } void ListItem::appendList(const QList& items) { d->childItems.append(items); } int ListItem::columnCount() const { return d->itemData.count(); } QVariant ListItem::data(int role) const { switch(role) { case Qt::DisplayRole: case Qt::ToolTipRole: { QString display; foreach (int tagId, d->tagIds) { TAlbum* const album = AlbumManager::instance()->findTAlbum(tagId); if (!album) { continue; } display.append(album->title()+ QLatin1String(", ")); if (role == Qt::DisplayRole && display.size() > 30) break; } if (display.isEmpty()) display.append(i18n("All Tags")); else display.remove(display.size()-2, 2); return QVariant(display); } default: { return QVariant(); } } } void ListItem::setData(const QList& data) { d->itemData = data; } ListItem* ListItem::parent() const { return d->parentItem; } int ListItem::row() const { if (d->parentItem) { return d->parentItem->allChildren().indexOf(const_cast(this)); } return 0; } ListItem* ListItem::containsItem(ListItem* const item) const { // We need to compare items and not pointers for (int it = 0 ; it < d->childItems.size() ; ++it) { if (item->equal(d->childItems.at(it))) { return d->childItems.at(it); } } return nullptr; } bool ListItem::equal(ListItem* const item) const { - return (this->d->tagIds) == (item->getTagIds()); + return (d->tagIds == item->getTagIds()); } } // namespace Digikam diff --git a/core/libs/tags/manager/models/tagmngrlistmodel.cpp b/core/libs/tags/manager/models/tagmngrlistmodel.cpp index 57e24f3fcf..418f01e1b2 100644 --- a/core/libs/tags/manager/models/tagmngrlistmodel.cpp +++ b/core/libs/tags/manager/models/tagmngrlistmodel.cpp @@ -1,322 +1,322 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-08-22 * Description : List View Model with support for mime data and drag-n-drop * * Copyright (C) 2013 by Veaceslav Munteanu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "tagmngrlistmodel.h" // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "tagmngrlistitem.h" namespace Digikam { class Q_DECL_HIDDEN TagMngrListModel::Private { public: explicit Private() { rootItem = nullptr; } ListItem* rootItem; QList dragNewSelection; }; TagMngrListModel::TagMngrListModel(QObject* const parent) : QAbstractItemModel(parent), d(new Private()) { QList rootData; rootData << QLatin1String("Quick List"); d->rootItem = new ListItem(rootData); } TagMngrListModel::~TagMngrListModel() { delete d->rootItem; delete d; } ListItem* TagMngrListModel::addItem(QList values) { emit layoutAboutToBeChanged(); ListItem* const item = new ListItem(values, d->rootItem); /** containsItem will return a valid pointer if item with the same * values is already added to it's children list. */ ListItem* const existingItem = d->rootItem->containsItem(item); if (!existingItem) { d->rootItem->appendChild(item); emit layoutChanged(); return item; } else { delete item; return existingItem; } } QList TagMngrListModel::allItems() const { return d->rootItem->allChildren(); } QList TagMngrListModel::getDragNewSelection() const { return d->dragNewSelection; } void TagMngrListModel::deleteItem(ListItem* const item) { if (!item) return; emit layoutAboutToBeChanged(); d->rootItem->deleteChild(item); emit layoutChanged(); } int TagMngrListModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 1; } Qt::DropActions TagMngrListModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList TagMngrListModel::mimeTypes() const { QStringList types; types << QLatin1String("application/vnd.text.list"); return types; } bool TagMngrListModel::setData(const QModelIndex& index, const QVariant& value, int role) { Q_UNUSED(role); ListItem* const parent = static_cast(index.internalPointer()); if (!parent) { qCDebug(DIGIKAM_GENERAL_LOG) << "No node found"; return false; } QList itemDa; itemDa << value; parent->appendChild(new ListItem(itemDa,parent)); return true; } QMimeData* TagMngrListModel::mimeData(const QModelIndexList& indexes) const { QMimeData* const mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); - foreach(const QModelIndex& index, indexes) + foreach (const QModelIndex& index, indexes) { if (index.isValid()) { stream << index.row(); } } mimeData->setData(QLatin1String("application/vnd.text.list"), encodedData); return mimeData; } bool TagMngrListModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { Q_UNUSED(column); Q_UNUSED(parent); if (action == Qt::IgnoreAction) return true; if (!(data->hasFormat(QLatin1String("application/vnd.text.list")))) return false; QByteArray encodedData = data->data(QLatin1String("application/vnd.text.list")); QDataStream stream(&encodedData, QIODevice::ReadOnly); QList newItems; QList finalItems; QList toRemove; int itemPoz; int temp = 0; while (!stream.atEnd()) { stream >> itemPoz; newItems << d->rootItem->child(itemPoz); if (itemPoz < row) { - temp++; + ++temp; } toRemove.append(itemPoz); } row -= temp; emit layoutAboutToBeChanged(); for (QList::iterator itr = toRemove.end() -1 ; itr != toRemove.begin() -1 ; --itr) { d->rootItem->deleteChild(*itr); } emit layoutChanged(); for (int it = 0 ; it < d->rootItem->childCount() ; ++it) { finalItems.append(d->rootItem->child(it)); if (it == row) { finalItems.append(newItems); /** After drag-n-drop selection is messed up, store the interval were * new items are and TagsMngrListView will update selection */ d->dragNewSelection.clear(); d->dragNewSelection << row; d->dragNewSelection << row + newItems.size(); } } d->rootItem->removeAll(); d->rootItem->appendList(finalItems); emit layoutChanged(); return true; } QVariant TagMngrListModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); if (role == Qt::SizeHintRole) return QSize(30,30); if (role == Qt::TextAlignmentRole) return Qt::AlignCenter; ListItem* const item = static_cast(index.internalPointer()); return item->data(role); } Qt::ItemFlags TagMngrListModel::flags(const QModelIndex& index) const { if (!index.isValid()) return nullptr; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } QVariant TagMngrListModel::headerData(int /*section*/, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return QVariant(i18n("Quick Access List")); return QVariant(); } QModelIndex TagMngrListModel::index(int row, int column, const QModelIndex& parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); ListItem* parentItem = nullptr; if (!parent.isValid()) parentItem = d->rootItem; else parentItem = static_cast(parent.internalPointer()); ListItem* const childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex TagMngrListModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); ListItem* const childItem = static_cast(index.internalPointer()); ListItem* const parentItem = childItem->parent(); if (parentItem == d->rootItem) return QModelIndex(); return createIndex(parentItem->row(), 0, parentItem); } int TagMngrListModel::rowCount(const QModelIndex &parent) const { ListItem* parentItem = nullptr; if (parent.column() > 0) return 0; if (!parent.isValid()) parentItem = d->rootItem; else parentItem = static_cast(parent.internalPointer()); return parentItem->childCount(); } } // namespace Digikam diff --git a/core/libs/tags/manager/models/tagmngrlistview.cpp b/core/libs/tags/manager/models/tagmngrlistview.cpp index 7e9d3509b1..4684e886a7 100644 --- a/core/libs/tags/manager/models/tagmngrlistview.cpp +++ b/core/libs/tags/manager/models/tagmngrlistview.cpp @@ -1,182 +1,182 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-08-22 * Description : Reimplemented QListView for Tags Manager, with support for * drag-n-drop * * Copyright (C) 2013 by Veaceslav Munteanu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "tagmngrlistview.h" // Qt includes #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "contextmenuhelper.h" #include "tagmngrlistmodel.h" #include "tagmngrlistitem.h" #include "taglist.h" namespace Digikam { TagMngrListView::TagMngrListView(QWidget* const parent) : QTreeView(parent) { setRootIsDecorated(false); setAlternatingRowColors(true); } void TagMngrListView::startDrag(Qt::DropActions supportedActions) { QModelIndexList list = selectionModel()->selectedIndexes(); TagMngrListModel* const tagmodel = dynamic_cast(model()); if (!tagmodel) { qCDebug(DIGIKAM_GENERAL_LOG) << "Error! no model available!"; return; } QMimeData* const data = tagmodel->mimeData(list); if (!data) { qCDebug(DIGIKAM_GENERAL_LOG) << "Error! no data obtained!"; return; } QDrag* const drag = new QDrag(this); drag->setMimeData(data); drag->exec(supportedActions, Qt::IgnoreAction); } QModelIndexList TagMngrListView::mySelectedIndexes() { return selectedIndexes(); } void TagMngrListView::dropEvent(QDropEvent* e) { QModelIndex index = indexVisuallyAt(e->pos()); TagMngrListModel* const tagmodel = dynamic_cast(model()); if (!tagmodel) { qCDebug(DIGIKAM_GENERAL_LOG) << "Error! no model available!"; return; } tagmodel->dropMimeData(e->mimeData(), e->dropAction(), index.row(), index.column(), index.parent()); QList toSel = tagmodel->getDragNewSelection(); if (toSel.size() != 2) { return; } QItemSelectionModel* const model = selectionModel(); model->clearSelection(); setCurrentIndex(tagmodel->index(toSel.first()+1, 0)); for (int it = toSel.first()+1 ; it <= toSel.last() ; ++it) { model->select(tagmodel->index(it, 0), model->Select); } } QModelIndex TagMngrListView::indexVisuallyAt(const QPoint& p) { if (viewport()->rect().contains(p)) { QModelIndex index = indexAt(p); if (index.isValid() && visualRect(index).contains(p)) { return index; } } return QModelIndex(); } void TagMngrListView::contextMenuEvent(QContextMenuEvent* event) { Q_UNUSED(event); QMenu popmenu(this); ContextMenuHelper cmhelper(&popmenu); TagList* const tagList = dynamic_cast(parent()); if (!tagList) { return; } QAction* const delAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Selected from List"), this); cmhelper.addAction(delAction, tagList, SLOT(slotDeleteSelected()), false); QModelIndexList sel = selectionModel()->selectedIndexes(); if (sel.size() == 1 && sel.first().row() == 0) delAction->setDisabled(true); cmhelper.exec(QCursor::pos()); } void TagMngrListView::slotDeleteSelected() { QModelIndexList sel = selectionModel()->selectedIndexes(); if (sel.isEmpty()) return; TagMngrListModel* const tagmodel = dynamic_cast(model()); if (!tagmodel) { qCDebug(DIGIKAM_GENERAL_LOG) << "Error! no model available!"; return; } - foreach(const QModelIndex& index, sel) + foreach (const QModelIndex& index, sel) { ListItem* const item = static_cast(index.internalPointer()); tagmodel->deleteItem(item); } } } // namespace Digikam diff --git a/core/libs/tags/manager/taglist.cpp b/core/libs/tags/manager/taglist.cpp index 9080c6ec79..6155c702d8 100644 --- a/core/libs/tags/manager/taglist.cpp +++ b/core/libs/tags/manager/taglist.cpp @@ -1,292 +1,292 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-07-31 * Description : Tag List implementation as Quick Access for various * subtrees in Tag Manager * * Copyright (C) 2013 by Veaceslav Munteanu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "taglist.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "albumtreeview.h" #include "tagmngrtreeview.h" #include "tagmngrlistmodel.h" #include "tagmngrlistview.h" #include "tagmngrlistitem.h" namespace Digikam { class Q_DECL_HIDDEN TagList::Private { public: explicit Private() { addButton = nullptr; tagList = nullptr; tagListModel = nullptr; treeView = nullptr; } QPushButton* addButton; TagMngrListView* tagList; TagMngrListModel* tagListModel; TagMngrTreeView* treeView; QMap > tagMap; }; TagList::TagList(TagMngrTreeView* const treeView, QWidget* const parent) - : QWidget(parent), - d(new Private()) + : QWidget(parent), + d(new Private()) { d->treeView = treeView; QVBoxLayout* const layout = new QVBoxLayout(); d->addButton = new QPushButton(i18n("Add to List")); d->addButton->setToolTip(i18n("Add selected tags to Quick Access List")); d->tagList = new TagMngrListView(this); d->tagListModel = new TagMngrListModel(this); d->tagList->setModel(d->tagListModel); d->tagList->setSelectionMode(QAbstractItemView::ExtendedSelection); d->tagList->setDragEnabled(true); d->tagList->setAcceptDrops(true); d->tagList->setDropIndicatorShown(true); layout->addWidget(d->addButton); layout->addWidget(d->tagList); connect(d->addButton, SIGNAL(clicked()), this, SLOT(slotAddPressed())); connect(d->tagList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged())); connect(AlbumManager::instance(), SIGNAL(signalAlbumDeleted(Album*)), this, SLOT(slotTagDeleted(Album*))); restoreSettings(); this->setLayout(layout); } TagList::~TagList() { delete d; } void TagList::saveSettings() { KConfig conf(QLatin1String("digikam_tagsmanagerrc")); conf.deleteGroup(QLatin1String("List Content")); KConfigGroup group = conf.group(QLatin1String("List Content")); QList currentItems = d->tagListModel->allItems(); group.writeEntry(QLatin1String("Size"), currentItems.count()-1); for (int it = 1 ; it < currentItems.size() ; ++it) { QList ids = currentItems.at(it)->getTagIds(); QString saveData; for (int jt = 0 ; jt < ids.size() ; ++jt) { saveData.append(QString::number(ids.at(jt)) + QLatin1Char(' ')); } group.writeEntry(QString::fromUtf8("item%1").arg(it-1), saveData); } } void TagList::restoreSettings() { KConfig conf(QLatin1String("digikam_tagsmanagerrc")); KConfigGroup group = conf.group(QLatin1String("List Content")); QStringList items; int size = group.readEntry(QLatin1String("Size"), -1); /** * If config is empty add generic All Tags */ d->tagListModel->addItem(QList() << QBrush(Qt::cyan, Qt::Dense2Pattern)); if (size == 0 || size < 0) { return; } for (int it = 0 ; it < size ; ++it) { QString data = group.readEntry(QString::fromUtf8("item%1").arg(it), ""); if (data.isEmpty()) continue; QStringList ids = data.split(QLatin1Char(' '), QString::SkipEmptyParts); QList itemData; itemData << QBrush(Qt::cyan, Qt::Dense2Pattern); foreach (const QString& tagId, ids) { TAlbum* const item = AlbumManager::instance()->findTAlbum(tagId.toInt()); if (item) { itemData << item->id(); } } ListItem* const listItem = d->tagListModel->addItem(itemData); /** Use this map to find all List Items that contain specific tag * usually to remove deleted tag */ foreach (int tagId, listItem->getTagIds()) { d->tagMap[tagId].append(listItem); } } /** "All Tags" item should be selected **/ QModelIndex rootIndex = d->tagList->model()->index(0, 0); d->tagList->setCurrentIndex(rootIndex); } void TagList::slotAddPressed() { QModelIndexList selected = d->treeView->selectionModel()->selectedIndexes(); if (selected.isEmpty()) { return; } QList itemData; itemData << QBrush(Qt::cyan, Qt::Dense2Pattern); foreach (const QModelIndex& index, selected) { TAlbum* const album = static_cast(d->treeView->albumForIndex(index)); itemData << album->id(); } ListItem* listItem = d->tagListModel->addItem(itemData); /** Use this map to find all List Items that contain specific tag * usually to remove deleted tag */ foreach (int tagId, listItem->getTagIds()) { d->tagMap[tagId].append(listItem); } } void TagList::slotSelectionChanged() { QModelIndexList indexList = d->tagList->mySelectedIndexes(); QSet mySet; foreach (const QModelIndex& index, indexList) { ListItem* const item = static_cast(index.internalPointer()); if (item->getTagIds().isEmpty()) { mySet.clear(); break; } foreach (int tagId, item->getTagIds()) { mySet.insert(tagId); } } TagsManagerFilterModel* const filterModel = d->treeView->getFilterModel(); filterModel->setQuickListTags(QList::fromSet(mySet)); } void TagList::slotTagDeleted(Album* album) { TAlbum* const talbum = dynamic_cast(album); if (!talbum) { return; } int delId = talbum->id(); QList items = d->tagMap[delId]; foreach (ListItem* const item, items) { item->removeTagId(delId); if (item->getTagIds().isEmpty()) { d->tagListModel->deleteItem(item); d->tagMap[delId].removeOne(item); d->treeView->getFilterModel()->setQuickListTags(QList()); } } } void TagList::slotDeleteSelected() { QModelIndexList sel = d->tagList->selectionModel()->selectedIndexes(); if (sel.isEmpty()) { return; } foreach (const QModelIndex& index, sel) { ListItem* const item = static_cast(index.internalPointer()); d->tagListModel->deleteItem(item); } d->tagList->selectionModel()->select(d->tagList->model()->index(0, 0), QItemSelectionModel::SelectCurrent); } void TagList::enableAddButton(bool value) { d->addButton->setEnabled(value); } } // namespace Digikam diff --git a/core/libs/tags/manager/tagmngrtreeview.cpp b/core/libs/tags/manager/tagmngrtreeview.cpp index f10fcf0718..aa4bd750ea 100644 --- a/core/libs/tags/manager/tagmngrtreeview.cpp +++ b/core/libs/tags/manager/tagmngrtreeview.cpp @@ -1,241 +1,242 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-08-05 * Description : Tag Manager Tree View derived from TagsFolderView to implement * a custom context menu and some batch view options, such as * expanding multiple items * * Copyright (C) 2013 by Veaceslav Munteanu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "tagmngrtreeview.h" // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "contextmenuhelper.h" #include "tagsmanager.h" namespace Digikam { class Q_DECL_HIDDEN TagMngrTreeView::Private { public: explicit Private() { tagMngr = nullptr; } TagsManager* tagMngr; }; TagMngrTreeView::TagMngrTreeView(TagsManager* const parent, TagModel* const model) - : TagFolderView(parent, model), d(new Private()) + : TagFolderView(parent, model), + d(new Private()) { d->tagMngr = parent; setAlbumFilterModel(new TagsManagerFilterModel(this), albumFilterModel()); setSelectAlbumOnClick(false); expand(albumFilterModel()->rootAlbumIndex()); } TagMngrTreeView::~TagMngrTreeView() { delete d; } void TagMngrTreeView::contextMenuEvent(QContextMenuEvent* event) { QModelIndexList selectedItems = selectionModel()->selectedIndexes(); std::sort(selectedItems.begin(), selectedItems.end()); QList items; - foreach(const QModelIndex& mIndex, selectedItems) + foreach (const QModelIndex& mIndex, selectedItems) { TAlbum* const temp = static_cast(albumForIndex(mIndex)); items.append(temp); } /** * Append root tag if no nodes are selected */ if (items.isEmpty()) { QModelIndex root = model()->index(0, 0); items.append(static_cast(albumForIndex(root))); } QMenu popmenu(this); popmenu.addSection(contextMenuIcon(), contextMenuTitle()); ContextMenuHelper cmhelper(&popmenu); setContexMenuItems(cmhelper, items); QAction* const choice = cmhelper.exec(QCursor::pos()); Q_UNUSED(choice); Q_UNUSED(event); } void TagMngrTreeView::setAlbumFilterModel(TagsManagerFilterModel* const filteredModel, CheckableAlbumFilterModel* const filterModel) { Q_UNUSED(filterModel); m_tfilteredModel = filteredModel; albumFilterModel()->setSourceFilterModel(m_tfilteredModel); } void TagMngrTreeView::setContexMenuItems(ContextMenuHelper& cmh, const QList& albums) { bool isRoot = false; if (albums.size() == 1) { TAlbum* const tag = dynamic_cast (albums.first()); if (!tag) { return; } if (tag->isRoot()) { isRoot = true; } cmh.addActionNewTag(tagModificationHelper(), tag); } if (!isRoot) { cmh.addActionDeleteTags(tagModificationHelper(), albums); } else { /** This is a dummy action, delete is disable for root tag **/ QAction* deleteTagsAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Tags"), this); cmh.addAction(deleteTagsAction); deleteTagsAction->setEnabled(false); } cmh.addSeparator(); QAction* const titleEdit = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Edit Tag Title"), this); titleEdit->setShortcut(QKeySequence(Qt::Key_F2)); QAction* const resetIcon = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), i18n("Reset Tag Icon"), this); QAction* const invSel = new QAction(QIcon::fromTheme(QLatin1String("tag-reset")), i18n("Invert Selection"), this); QAction* const expandTree = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Tag Tree"), this); QAction* const expandSel = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Selected Nodes"), this); QAction* const delTagFromImg = new QAction(QIcon::fromTheme(QLatin1String("tag-delete")), i18n("Remove Tag from Images"), this); cmh.addAction(titleEdit, d->tagMngr, SLOT(slotEditTagTitle()), false); cmh.addAction(resetIcon, d->tagMngr, SLOT(slotResetTagIcon()), false); cmh.addAction(invSel, d->tagMngr, SLOT(slotInvertSel()), false); cmh.addAction(expandTree, this, SLOT(slotExpandTree()), false); cmh.addAction(expandSel, this , SLOT(slotExpandSelected()), false); cmh.addAction(delTagFromImg, d->tagMngr, SLOT(slotRemoveTagsFromImgs()), false); if (isRoot) { titleEdit->setEnabled(false); resetIcon->setEnabled(false); delTagFromImg->setEnabled(false); } if (albums.size() != 1) { titleEdit->setEnabled(false); } } void TagMngrTreeView::slotExpandSelected() { QModelIndexList list = selectionModel()->selectedIndexes(); - foreach(const QModelIndex& index, list) + foreach (const QModelIndex& index, list) { expand(index); } } void TagMngrTreeView::slotExpandTree() { QModelIndex root = model()->index(0, 0); QItemSelectionModel* const model = selectionModel(); QModelIndexList selected = model->selectedIndexes(); QQueue greyNodes; greyNodes.append(root); while (!greyNodes.isEmpty()) { QModelIndex current = greyNodes.dequeue(); if (!current.isValid()) { continue; } if (isExpanded(current)) { int it = 0; QModelIndex child = current.model()->index(it++, 0, current); while (child.isValid()) { if (isExpanded(child)) { greyNodes.enqueue(child); } else { expand(child); } child = current.model()->index(it++, 0, current); } } else { expand(current); } } } } // namespace Digikam diff --git a/core/libs/tags/manager/tagsmanager.cpp b/core/libs/tags/manager/tagsmanager.cpp index bad7d791c3..d738e52e54 100644 --- a/core/libs/tags/manager/tagsmanager.cpp +++ b/core/libs/tags/manager/tagsmanager.cpp @@ -1,1010 +1,1011 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-07-03 * Description : Tag Manager main class * * Copyright (C) 2013 by Veaceslav Munteanu * Copyright (C) 2014 by Michael G. Hansen * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "tagsmanager.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "digikam_config.h" #include "dmessagebox.h" #include "tagpropwidget.h" #include "tagmngrtreeview.h" #include "taglist.h" #include "tagfolderview.h" #include "ddragobjects.h" #include "searchtextbar.h" #include "tageditdlg.h" #include "coredb.h" #include "sidebar.h" #include "dlogoaction.h" #include "metadatasynchronizer.h" #include "fileactionmngr.h" #include "metaenginesettings.h" namespace Digikam { QPointer TagsManager::internalPtr = QPointer(); class Q_DECL_HIDDEN TagsManager::Private { public: explicit Private() { tagPixmap = nullptr; searchBar = nullptr; splitter = nullptr; treeWindow = nullptr; mainToolbar = nullptr; rightToolBar = nullptr; organizeAction = nullptr; syncexportAction = nullptr; tagProperties = nullptr; addAction = nullptr; delAction = nullptr; titleEdit = nullptr; listView = nullptr; tagPropWidget = nullptr; tagMngrView = nullptr; tagModel = nullptr; tagPropVisible = false; } TagMngrTreeView* tagMngrView; QLabel* tagPixmap; SearchTextBar* searchBar; QSplitter* splitter; KMainWindow* treeWindow; KToolBar* mainToolbar; DMultiTabBar* rightToolBar; QMenu* organizeAction; QMenu* syncexportAction; QAction* tagProperties; QAction* addAction; QAction* delAction; QAction* titleEdit; /** Options unavailable for root tag **/ QList rootDisabledOptions; TagList* listView; TagPropWidget* tagPropWidget; TagModel* tagModel; bool tagPropVisible; }; TagsManager::TagsManager() : KMainWindow(nullptr), StateSavingObject(this), d(new Private()) { setObjectName(QLatin1String("Tags Manager")); d->tagModel = new TagModel(AbstractAlbumModel::IncludeRootAlbum, this); d->tagModel->setCheckable(false); setupUi(this); /*----------------------------Connects---------------------------*/ connect(d->tagMngrView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged())); connect(d->addAction, SIGNAL(triggered()), this, SLOT(slotAddAction())); connect(d->delAction, SIGNAL(triggered()), this, SLOT(slotDeleteAction())); d->tagMngrView->setCurrentIndex(d->tagMngrView->model()->index(0, 0)); StateSavingObject::loadState(); /** Set KMainWindow in center of the screen **/ QScreen* screen = qApp->primaryScreen(); if (QWidget* const widget = qApp->activeWindow()) { if (QWindow* const window = widget->windowHandle()) screen = window->screen(); } const int screenIndex = qMax(qApp->screens().indexOf(screen), 0); move(qApp->screens().at(screenIndex)->geometry().center() - rect().center()); } TagsManager::~TagsManager() { StateSavingObject::saveState(); delete d; } TagsManager* TagsManager::instance() { if (TagsManager::internalPtr.isNull()) { TagsManager::internalPtr = new TagsManager(); } return TagsManager::internalPtr; } void TagsManager::setupUi(KMainWindow* const dialog) { dialog->resize(972, 722); dialog->setWindowTitle(i18n("Tags Manager")); QHBoxLayout* const mainLayout = new QHBoxLayout(); d->tagPixmap = new QLabel(); d->tagPixmap->setText(QLatin1String("Tag Pixmap")); d->tagPixmap->setMaximumWidth(40); d->tagPixmap->setPixmap(QIcon::fromTheme(QLatin1String("tag")).pixmap(30, 30)); d->tagMngrView = new TagMngrTreeView(this, d->tagModel); d->tagMngrView->setConfigGroup(getConfigGroup()); d->searchBar = new SearchTextBar(this, QLatin1String("ItemIconViewTagSearchBar")); d->searchBar->setHighlightOnResult(true); d->searchBar->setModel(d->tagMngrView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->searchBar->setMaximumWidth(200); d->searchBar->setFilterModel(d->tagMngrView->albumFilterModel()); /** Tree Widget & Actions + Tag Properties sidebar **/ d->treeWindow = new KMainWindow(this); setupActions(); d->tagPropWidget = new TagPropWidget(this); d->listView = new TagList(d->tagMngrView, this); d->splitter = new QSplitter(Qt::Horizontal, this); d->splitter->addWidget(d->listView); d->splitter->addWidget(d->tagMngrView); d->splitter->addWidget(d->tagPropWidget); d->tagPropWidget->hide(); connect(d->tagPropWidget, SIGNAL(signalTitleEditReady()), this, SLOT(slotTitleEditReady())); d->splitter->setStretchFactor(0, 0); d->splitter->setStretchFactor(1, 1); d->splitter->setStretchFactor(2, 0); d->treeWindow->setCentralWidget(d->splitter); mainLayout->addWidget(d->treeWindow); mainLayout->addWidget(d->rightToolBar); QWidget* const centraW = new QWidget(this); centraW->setLayout(mainLayout); setCentralWidget(centraW); } void TagsManager::slotOpenProperties() { DMultiTabBarTab* const sender = dynamic_cast(QObject::sender()); if (sender->isChecked()) { d->tagPropWidget->show(); } else { d->tagPropWidget->hide(); } d->tagPropVisible = d->tagPropWidget->isVisible(); } void TagsManager::slotSelectionChanged() { QList selectedTags = d->tagMngrView->selectedTags(); if (selectedTags.isEmpty() || (selectedTags.size() == 1 && selectedTags.at(0)->isRoot())) { enableRootTagActions(false); d->listView->enableAddButton(false); } else { enableRootTagActions(true); d->listView->enableAddButton(true); d->titleEdit->setEnabled((selectedTags.size() == 1)); } d->tagPropWidget->slotSelectionChanged(selectedTags); } void TagsManager::slotItemChanged() { } void TagsManager::slotAddAction() { TAlbum* parent = d->tagMngrView->currentAlbum(); QString title; QString icon; QKeySequence ks; if (!parent) { parent = static_cast(d->tagMngrView->albumForIndex(d->tagMngrView->model()->index(0, 0))); } if (!TagEditDlg::tagCreate(qApp->activeWindow(), parent, title, icon, ks)) { return; } QMap errMap; AlbumList tList = TagEditDlg::createTAlbum(parent, title, icon, ks, errMap); TagEditDlg::showtagsListCreationError(qApp->activeWindow(), errMap); } namespace { QString JoinTagNamesToList(const QStringList& stringList) { const QString joinedStringList = stringList.join(QLatin1String("', '")); return QLatin1Char('\'') + joinedStringList + QLatin1Char('\''); } } // namespace void TagsManager::slotDeleteAction() { const QModelIndexList selected = d->tagMngrView->selectionModel()->selectedIndexes(); QStringList tagNames; QStringList tagsWithChildren; QStringList tagsWithImages; QMultiMap sortedTags; foreach (const QModelIndex& index, selected) { if (!index.isValid()) { return; } TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(index)); if (!t || t->isRoot()) { return; } AlbumPointer tag(t); tagNames.append(tag->title()); // find number of subtags int children = 0; AlbumIterator iter(tag); while (iter.current()) { ++children; ++iter; } if (children) { tagsWithChildren.append(tag->title()); } QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); if (!assignedItems.isEmpty()) { tagsWithImages.append(tag->title()); } /** * Tags must be deleted from children to parents, if we don't want * to step on invalid index. Use QMultiMap to order them by distance * to root tag */ Album* parent = t; int depth = 0; while (!parent->isRoot()) { parent = parent->parent(); depth++; } sortedTags.insert(depth, tag); } // ask for deletion of children if (!tagsWithChildren.isEmpty()) { const int result = QMessageBox::warning(this, qApp->applicationName(), i18ncp("%2 is a comma separated list of tags to be deleted.", "Tag %2 has one or more subtags. " "Deleting it will also delete " "the subtags. " "Do you want to continue?", "Tags %2 have one or more subtags. " "Deleting them will also delete " "the subtags. " "Do you want to continue?", tagsWithChildren.count(), JoinTagNamesToList(tagsWithChildren)), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } } QString message; if (!tagsWithImages.isEmpty()) { message = i18ncp("%2 is a comma separated list of tags to be deleted.", "Tag %2 is assigned to one or more items. " "Do you want to delete it?", "Tags %2 are assigned to one or more items. " "Do you want to delete them?", tagsWithImages.count(), JoinTagNamesToList(tagsWithImages)); } else { message = i18ncp("%2 is a comma separated list of tags to be deleted.", "Delete tag %2?", "Delete tags %2?", tagNames.count(), JoinTagNamesToList(tagNames)); } const int result = QMessageBox::warning(this, i18np("Delete tag", "Delete tags", tagNames.count()), message, QMessageBox::Yes | QMessageBox::Cancel); if (result == QMessageBox::Yes) { QMultiMap::iterator it; /** * QMultimap doesn't provide reverse iterator, -1 is required * because end() points after the last element */ for (it = sortedTags.end()-1 ; it != sortedTags.begin()-1 ; --it) { QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(it.value(), errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } } void TagsManager::slotEditTagTitle() { QList selectedTags = d->tagMngrView->selectedTags(); if (selectedTags.size() == 1 && !selectedTags.at(0)->isRoot()) { d->tagPropWidget->show(); d->tagPropWidget->slotFocusTitleEdit(); d->rightToolBar->tab(0)->setChecked(true); } } void TagsManager::slotTitleEditReady() { if (!d->tagPropVisible) { d->tagPropWidget->hide(); d->rightToolBar->tab(0)->setChecked(false); } d->tagMngrView->setFocus(); } void TagsManager::slotResetTagIcon() { QString errMsg; const QList selected = d->tagMngrView->selectedTagAlbums(); const QString icon = QLatin1String("tag"); for (QList::const_iterator it = selected.constBegin() ; it != selected.constEnd() ; ++it) { TAlbum* const tag = *it; if (tag) { if (!AlbumManager::instance()->updateTAlbumIcon(tag, icon, 0, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } } void TagsManager::slotCreateTagAddr() { } void TagsManager::slotInvertSel() { QModelIndex root = d->tagMngrView->model()->index(0, 0); QItemSelectionModel* const model = d->tagMngrView->selectionModel(); QModelIndexList selected = model->selectedIndexes(); QQueue greyNodes; bool currentSet = false; greyNodes.append(root); model->clearSelection(); while (!greyNodes.isEmpty()) { QModelIndex current = greyNodes.dequeue(); if (!(current.isValid())) { continue; } int it = 0; QModelIndex child = current.model()->index(it++, 0, current); while (child.isValid()) { if (!selected.contains(child)) { if (!currentSet) { /** * Must set a new current item when inverting selection * it should be done only once */ d->tagMngrView->setCurrentIndex(child); currentSet = true; } model->select(child, model->Select); } if (d->tagMngrView->isExpanded(child)) { greyNodes.enqueue(child); } child = current.model()->index(it++, 0, current); } } } void TagsManager::slotWriteToImg() { int result = QMessageBox::warning(this, qApp->applicationName(), i18n("digiKam will clean up tag metadata before setting " "tags from database.
You may lose tags if you did not " "read tags before (by calling Read Tags from Image).
" "Do you want to continue?
"), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } result = QMessageBox::warning(this, qApp->applicationName(), i18n("This operation can take long time " "depending on collection size.\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } MetadataSynchronizer* const tool = new MetadataSynchronizer(AlbumList(), MetadataSynchronizer::WriteFromDatabaseToFile); tool->setTagsOnly(true); tool->start(); } void TagsManager::slotReadFromImg() { int result = QMessageBox::warning(this, qApp->applicationName(), i18n("This operation can take long time " "depending on collection size.\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } MetadataSynchronizer* const tool = new MetadataSynchronizer(AlbumList(), MetadataSynchronizer::ReadFromFileToDatabase); tool->setTagsOnly(true); tool->start(); } void TagsManager::slotWipeAll() { const int result = QMessageBox::warning(this, qApp->applicationName(), i18n("This operation will wipe all tags from database only.\n" "To apply changes to files, " "you must choose write metadata to file later.\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } /** Disable writing tags to images **/ MetaEngineSettings* const metaSettings = MetaEngineSettings::instance(); MetaEngineSettingsContainer backUpContainer = metaSettings->settings(); MetaEngineSettingsContainer newContainer = backUpContainer; bool settingsChanged = false; if (backUpContainer.saveTags == true || backUpContainer.saveFaceTags == true) { settingsChanged = true; newContainer.saveTags = false; newContainer.saveFaceTags = false; metaSettings->setSettings(newContainer); } AlbumPointerList tagList; const QModelIndex root = d->tagMngrView->model()->index(0, 0); int iter = 0; QModelIndex child = root.model()->index(iter++, 0, root); while (child.isValid()) { tagList << AlbumPointer(d->tagMngrView->albumForIndex(child)); child = root.model()->index(iter++, 0, root); } AlbumPointerList::iterator it; for (it = tagList.begin() ; it != tagList.end() ; ++it) { QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(*it, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } /** Restore settings after tag deletion **/ if (settingsChanged) { metaSettings->setSettings(backUpContainer); } } void TagsManager::slotRemoveTagsFromImgs() { const QModelIndexList selList = d->tagMngrView->selectionModel()->selectedIndexes(); const int result = QMessageBox::warning(this, qApp->applicationName(), i18np("Do you really want to remove the selected tag from all images?", "Do you really want to remove the selected tags from all images?", selList.count()), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } foreach (const QModelIndex& index, selList) { TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(index)); AlbumPointer tag(t); if (tag->isRoot()) { continue; } QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); ItemInfoList imgList(assignedItems); FileActionMngr::instance()->removeTag(imgList, tag->id()); } } void TagsManager::closeEvent(QCloseEvent* event) { d->listView->saveSettings(); KMainWindow::closeEvent(event); } void TagsManager::setupActions() { d->mainToolbar = new KToolBar(d->treeWindow, true); d->mainToolbar->layout()->setContentsMargins(QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin), QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin), QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin), QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin)); QWidgetAction* const pixMapAction = new QWidgetAction(this); pixMapAction->setDefaultWidget(d->tagPixmap); QWidgetAction* const searchAction = new QWidgetAction(this); searchAction->setDefaultWidget(d->searchBar); d->mainToolbar->addAction(pixMapAction); d->mainToolbar->addAction(searchAction); d->mainToolbar->addSeparator(); - d->addAction = new QAction(QIcon::fromTheme(QLatin1String("list-add")), QLatin1String(""), d->treeWindow); + d->addAction = new QAction(QIcon::fromTheme(QLatin1String("list-add")), + QLatin1String(""), d->treeWindow); - d->delAction = new QAction(QIcon::fromTheme(QLatin1String("list-remove")), QLatin1String(""), d->treeWindow); + d->delAction = new QAction(QIcon::fromTheme(QLatin1String("list-remove")), + QLatin1String(""), d->treeWindow); /** organize group **/ d->organizeAction = new QMenu(i18nc("@title:menu", "Organize"), this); d->organizeAction->setIcon(QIcon::fromTheme(QLatin1String("autocorrection"))); d->titleEdit = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Edit Tag Title"), this); d->titleEdit->setShortcut(QKeySequence(Qt::Key_F2)); QAction* const resetIcon = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), i18n("Reset Tag Icon"), this); QAction* const createTagAddr = new QAction(QIcon::fromTheme(QLatin1String("tag-addressbook")), i18n("Create Tag from Address Book"), this); QAction* const invSel = new QAction(QIcon::fromTheme(QLatin1String("tag-reset")), i18n("Invert Selection"), this); QAction* const expandTree = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Tag Tree"), this); QAction* const expandSel = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Selected Nodes"), this); QAction* const delTagFromImg = new QAction(QIcon::fromTheme(QLatin1String("tag-delete")), i18n("Remove Tag from Images"), this); QAction* const deleteUnused = new QAction(QIcon::fromTheme(QLatin1String("draw-eraser")), i18n("Delete Unassigned Tags"), this); /** Tool tips **/ setHelpText(d->addAction, i18n("Add new tag to current tag. " "Current tag is last clicked tag.")); setHelpText(d->delAction, i18n("Delete selected items. " "Also work with multiple items, " "but will not delete the root tag.")); setHelpText(d->titleEdit, i18n("Edit title from selected tag.")); setHelpText(resetIcon, i18n("Reset icon to selected tags. " "Works with multiple selection.")); setHelpText(invSel, i18n("Invert selection. " "Only visible items will be selected")); setHelpText(expandTree, i18n("Expand tag tree by one level")); setHelpText(expandSel, i18n("Selected items will be expanded")); setHelpText(delTagFromImg, i18n("Delete selected tag(s) from images. " "Works with multiple selection.")); setHelpText(deleteUnused, i18n("Delete all tags that are not assigned to images. " "Use with caution.")); connect(d->titleEdit, SIGNAL(triggered()), this, SLOT(slotEditTagTitle())); connect(resetIcon, SIGNAL(triggered()), this, SLOT(slotResetTagIcon())); connect(createTagAddr, SIGNAL(triggered()), this, SLOT(slotCreateTagAddr())); connect(invSel, SIGNAL(triggered()), this, SLOT(slotInvertSel())); connect(expandTree, SIGNAL(triggered()), d->tagMngrView, SLOT(slotExpandTree())); connect(expandSel, SIGNAL(triggered()), d->tagMngrView, SLOT(slotExpandSelected())); connect(delTagFromImg, SIGNAL(triggered()), this, SLOT(slotRemoveTagsFromImgs())); connect(deleteUnused, SIGNAL(triggered()), this, SLOT(slotRemoveNotAssignedTags())); d->organizeAction->addAction(d->titleEdit); d->organizeAction->addAction(resetIcon); d->organizeAction->addAction(createTagAddr); d->organizeAction->addAction(invSel); d->organizeAction->addAction(expandTree); d->organizeAction->addAction(expandSel); d->organizeAction->addAction(delTagFromImg); d->organizeAction->addAction(deleteUnused); /** Sync & Export Group **/ d->syncexportAction = new QMenu(i18n("Sync &Export"), this); d->syncexportAction->setIcon(QIcon::fromTheme(QLatin1String("network-server-database"))); QAction* const wrDbImg = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), - i18n("Write Tags from Database " - "to Image"), this); + i18n("Write Tags from Database to Image"), this); QAction* const readTags = new QAction(QIcon::fromTheme(QLatin1String("tag-new")), i18n("Read Tags from Image"), this); QAction* const wipeAll = new QAction(QIcon::fromTheme(QLatin1String("draw-eraser")), i18n("Wipe all tags from Database only"), this); setHelpText(wrDbImg, i18n("Write Tags Metadata to Image.")); setHelpText(readTags, i18n("Read tags from Images into Database. " "Existing tags will not be affected")); setHelpText(wipeAll, i18n("Delete all tags from database only. Will not sync with files. " "Proceed with caution.")); connect(wrDbImg, SIGNAL(triggered()), this, SLOT(slotWriteToImg())); connect(readTags, SIGNAL(triggered()), this, SLOT(slotReadFromImg())); connect(wipeAll, SIGNAL(triggered()), this, SLOT(slotWipeAll())); d->syncexportAction->addAction(wrDbImg); d->syncexportAction->addAction(readTags); d->syncexportAction->addAction(wipeAll); d->mainToolbar->addAction(d->addAction); d->mainToolbar->addAction(d->delAction); d->mainToolbar->addAction(d->organizeAction->menuAction()); d->mainToolbar->addAction(d->syncexportAction->menuAction()); d->mainToolbar->addAction(new DLogoAction(this)); addToolBar(d->mainToolbar); /** * Right Toolbar with vertical properties button */ d->rightToolBar = new DMultiTabBar(Qt::RightEdge); d->rightToolBar->appendTab(QIcon::fromTheme(QLatin1String("tag-properties")) .pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)), 0, i18n("Tag Properties")); d->rightToolBar->setStyle(DMultiTabBar::AllIconsText); connect(d->rightToolBar->tab(0), SIGNAL(clicked()), this, SLOT(slotOpenProperties())); d->rootDisabledOptions.append(d->delAction); d->rootDisabledOptions.append(d->titleEdit); d->rootDisabledOptions.append(resetIcon); d->rootDisabledOptions.append(delTagFromImg); } // helper based on KAction::setHelpText -void TagsManager::setHelpText(QAction *action, const QString& text) +void TagsManager::setHelpText(QAction* const action, const QString& text) { action->setStatusTip(text); action->setToolTip(text); if (action->whatsThis().isEmpty()) { action->setWhatsThis(text); } } void TagsManager::enableRootTagActions(bool value) { foreach (QAction* const action, d->rootDisabledOptions) { if (value) action->setEnabled(true); else action->setEnabled(false); } } void TagsManager::doLoadState() { KConfigGroup group = getConfigGroup(); d->tagMngrView->doLoadState(); group.sync(); } void TagsManager::doSaveState() { KConfigGroup group = getConfigGroup(); d->tagMngrView->doSaveState(); group.sync(); } void TagsManager::slotRemoveNotAssignedTags() { const int result = DMessageBox::showContinueCancel(QMessageBox::Warning, this, i18n("Warning"), i18n("This option will remove all tags which\n" "are not assigned to any image.\n " "Do you want to continue?")); if (result != QMessageBox::Yes) { return; } QModelIndex root = d->tagMngrView->model()->index(0, 0); QQueue greyNodes; QList redNodes; QSet greenNodes; int iter = 0; while (root.model()->hasIndex(iter, 0, root)) { greyNodes.append(root.model()->index(iter++, 0, root)); } while (!greyNodes.isEmpty()) { QModelIndex current = greyNodes.dequeue(); if (!(current.isValid())) { continue; } if (current.model()->hasIndex(0, 0, current)) { // Add in the list int iterator = 0; while (current.model()->hasIndex(iterator, 0, current)) { greyNodes.append(current.model()->index(iterator++, 0, current)); } } else { TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(current)); if (t && !t->isRoot() && !t->isInternalTag()) { QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(t->id()); if (assignedItems.isEmpty()) { redNodes.append(current); } else { QModelIndex tmp = current.parent(); while (tmp.isValid()) { greenNodes.insert(tmp); tmp = tmp.parent(); } } } } } QList toRemove; foreach (const QModelIndex& toDelete, redNodes) { QModelIndex current = toDelete; while (current.isValid() && !greenNodes.contains(current)) { TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(current)); if (t && !t->isRoot() && !t->isInternalTag()) { QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(t->id()); if (assignedItems.isEmpty() && !toRemove.contains(t->id())) { toRemove.append(t->id()); } else { break; } } current = current.parent(); } } foreach (int id, toRemove) { TAlbum* const talbum = AlbumManager::instance()->findTAlbum(id); if (!talbum) { continue; } qCDebug(DIGIKAM_GENERAL_LOG) << talbum->title(); QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(talbum, errMsg)) { QMessageBox::critical(this, qApp->applicationName(), errMsg); return; } } QMessageBox::information(this, qApp->applicationName(), i18np("%1 unused tag were removed.", "%1 unused tags were removed.", toRemove.count())); } } // namespace Digikam diff --git a/core/libs/tags/manager/tagsmanager.h b/core/libs/tags/manager/tagsmanager.h index 9d47d8bafd..990d5e791d 100644 --- a/core/libs/tags/manager/tagsmanager.h +++ b/core/libs/tags/manager/tagsmanager.h @@ -1,181 +1,181 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-07-03 * Description : Tag Manager main class * * Copyright (C) 2013 by Veaceslav Munteanu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_TAGS_MANAGER_H #define DIGIKAM_TAGS_MANAGER_H // Qt includes #include // KDE includes #include // Local includes #include "statesavingobject.h" namespace Digikam { class TagModel; class TAlbum; class TagsManager : public KMainWindow, public StateSavingObject { Q_OBJECT public: explicit TagsManager(); ~TagsManager(); /** * @brief setupUi setup all gui elements for Tag Manager * @param Dialog parent dialog */ void setupUi(KMainWindow* const dialog); static QPointer internalPtr; static TagsManager* instance(); static bool isCreated() { return !(internalPtr.isNull()); } Q_SIGNALS: void signalSelectionChanged(TAlbum* album); private Q_SLOTS: /** * @brief slotOpenProperties - open tag properties option when * activating Tag Properties from right sidebar */ void slotOpenProperties(); /** * @brief slotSelectionChanged - update tag properties in tagPropWidget when * different item is selected */ void slotSelectionChanged(); /** * Not used yet */ void slotItemChanged(); /** * @brief slotAddAction - add new tag when addAction(+) is triggered */ void slotAddAction(); /** * @brief slotDeleteAction - delete tag/tags when delAction is triggered */ void slotDeleteAction(); /** * @brief slotResetTagIcon - connected to resetTagIcon action and * will reset icon to all selected tags */ void slotResetTagIcon(); /** * @brief slotEditTagTitle - view Tag Properties and set focus to title edit */ void slotEditTagTitle(); /** * @brief slotTitleEditReady - title edit from Tag Properties was return button pressed */ void slotTitleEditReady(); /** * @brief slotCreateTagAddr - connected to createTagAddr action and * will create tags from Addressbook */ void slotCreateTagAddr(); /** * @brief slotInvertSel - connected to invSel action and will * invert selection of current items */ void slotInvertSel(); /** * @brief slotWriteToImg - connected to wrDbImg action and will * write all metadata from database to images */ void slotWriteToImg(); /** * @brief slotReadFromImg - coonected to readTags action and will * reread all images metadata into database */ void slotReadFromImg(); /** * @brief slotWipeAll - connected to wipeAll action and will * wipe all tag related data from database * and reread from image's metadata */ void slotWipeAll(); /** * @brief slotRemoveTagsFromImg - will remove selected tags from all * images that have them. */ void slotRemoveTagsFromImgs(); /** * @brief slotRemoveNotAssignedTags - remove all tags that are not assigned to images */ void slotRemoveNotAssignedTags(); protected: void closeEvent(QCloseEvent* event) override; virtual void doLoadState() override; virtual void doSaveState() override; private: void setupActions(); /** * @brief enableRootTagActions - enable or disable options when only root * tag is selected */ - void setHelpText(QAction* action, const QString& text); + void setHelpText(QAction* const action, const QString& text); void enableRootTagActions(bool value); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_TAGS_MANAGER_H