diff --git a/core/libs/database/models/itemmodel.cpp b/core/libs/database/models/itemmodel.cpp index d1685b083a..07fcc83498 100644 --- a/core/libs/database/models/itemmodel.cpp +++ b/core/libs/database/models/itemmodel.cpp @@ -1,1431 +1,1422 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-03-05 * Description : Qt item model for database entries * * Copyright (C) 2009-2011 by Marcel Wiesweg * Copyright (C) 2012-2019 by Gilles Caulier * * 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 "itemmodel.h" // Qt includes #include #include // Local includes #include "digikam_debug.h" #include "coredbchangesets.h" #include "coredbfields.h" #include "coredbwatch.h" #include "iteminfo.h" #include "iteminfolist.h" #include "abstractitemdragdrophandler.h" namespace Digikam { class Q_DECL_HIDDEN ItemModel::Private { public: explicit Private() { preprocessor = nullptr; keepFilePathCache = false; sendRemovalSignals = false; incrementalUpdater = nullptr; refreshing = false; reAdding = false; incrementalRefreshRequested = false; } public: ItemInfoList infos; ItemInfo itemInfo; QList extraValues; QHash idHash; bool keepFilePathCache; QHash filePathHash; bool sendRemovalSignals; QObject* preprocessor; bool refreshing; bool reAdding; bool incrementalRefreshRequested; DatabaseFields::Set watchFlags; class ItemModelIncrementalUpdater* incrementalUpdater; ItemInfoList pendingInfos; QList pendingExtraValues; public: inline bool isValid(const QModelIndex& index) { if (!index.isValid()) { return false; } if (index.row() < 0 || index.row() >= infos.size()) { qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index" << index; return false; } return true; } inline bool extraValueValid(const QModelIndex& index) { // we assume isValid() being called before, no duplicate checks if (index.row() >= extraValues.size()) { qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index for extraData" << index; return false; } return true; } }; // ------------------------------------------------------------------------------------- typedef QPair IntPair; // to make foreach macro happy typedef QList IntPairList; class Q_DECL_HIDDEN ItemModelIncrementalUpdater { public: explicit ItemModelIncrementalUpdater(ItemModel::Private* d); void appendInfos(const QList& infos, const QList& extraValues); void aboutToBeRemovedInModel(const IntPairList& aboutToBeRemoved); QList oldIndexes(); static QList toContiguousPairs(const QList& ids); public: QHash oldIds; QList oldExtraValues; QList newInfos; QList newExtraValues; QList modelRemovals; }; // ------------------------------------------------------------------------------------- ItemModel::ItemModel(QObject* const parent) : QAbstractListModel(parent), d(new Private) { -/* NOTE: old signal/slot syntax ported to new syntax fooloing paper https://wiki.qt.io/New_Signal_Slot_Syntax - - connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)), - this, SLOT(slotImageChange(ImageChangeset))); - - connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)), - this, SLOT(slotImageTagChange(ImageTagChangeset))); -*/ - - // --- NOTE: use dynamic binding as slotImageChenge() is a virtual slot which can be re-implemented in derived classes. + // --- NOTE: use dynamic binding as slotImageChange() is a virtual slot which can be re-implemented in derived classes. connect(CoreDbAccess::databaseWatch(), static_cast(&CoreDbWatch::imageChange), this, &ItemModel::slotImageChange); - // --- NOTE: use dynamic binding as slotImageTagChenge() is a virtual slot which can be re-implemented in derived classes. + // --- NOTE: use dynamic binding as slotImageTagChange() is a virtual slot which can be re-implemented in derived classes. connect(CoreDbAccess::databaseWatch(), static_cast(&CoreDbWatch::imageTagChange), this, &ItemModel::slotImageTagChange); } ItemModel::~ItemModel() { delete d->incrementalUpdater; delete d; } // ------------ Access methods ------------- void ItemModel::setKeepsFilePathCache(bool keepCache) { d->keepFilePathCache = keepCache; } bool ItemModel::keepsFilePathCache() const { return d->keepFilePathCache; } bool ItemModel::isEmpty() const { return d->infos.isEmpty(); } void ItemModel::setWatchFlags(const DatabaseFields::Set& set) { d->watchFlags = set; } ItemInfo ItemModel::imageInfo(const QModelIndex& index) const { if (!d->isValid(index)) { return ItemInfo(); } return d->infos.at(index.row()); } ItemInfo& ItemModel::imageInfoRef(const QModelIndex& index) const { if (!d->isValid(index)) { return d->itemInfo; } return d->infos[index.row()]; } qlonglong ItemModel::imageId(const QModelIndex& index) const { if (!d->isValid(index)) { return 0; } return d->infos.at(index.row()).id(); } QList ItemModel::imageInfos(const QList& indexes) const { QList infos; foreach (const QModelIndex& index, indexes) { infos << imageInfo(index); } return infos; } QList ItemModel::imageIds(const QList& indexes) const { QList ids; foreach (const QModelIndex& index, indexes) { ids << imageId(index); } return ids; } ItemInfo ItemModel::imageInfo(int row) const { if ((row < 0) || (row >= d->infos.size())) { return ItemInfo(); } return d->infos.at(row); } ItemInfo& ItemModel::imageInfoRef(int row) const { if ((row < 0) || (row >= d->infos.size())) { return d->itemInfo; } return d->infos[row]; } qlonglong ItemModel::imageId(int row) const { if ((row < 0) || (row >= d->infos.size())) { return -1; } return d->infos.at(row).id(); } QModelIndex ItemModel::indexForItemInfo(const ItemInfo& info) const { return indexForImageId(info.id()); } QModelIndex ItemModel::indexForItemInfo(const ItemInfo& info, const QVariant& extraValue) const { return indexForImageId(info.id(), extraValue); } QList ItemModel::indexesForItemInfo(const ItemInfo& info) const { return indexesForImageId(info.id()); } QModelIndex ItemModel::indexForImageId(qlonglong id) const { int index = d->idHash.value(id, -1); if (index != -1) { return createIndex(index, 0); } return QModelIndex(); } QModelIndex ItemModel::indexForImageId(qlonglong id, const QVariant& extraValue) const { if (d->extraValues.isEmpty()) { return indexForImageId(id); } QHash::const_iterator it; for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it) { if (d->extraValues.at(it.value()) == extraValue) { return createIndex(it.value(), 0); } } return QModelIndex(); } QList ItemModel::indexesForImageId(qlonglong id) const { QList indexes; QHash::const_iterator it; for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it) { indexes << createIndex(it.value(), 0); } return indexes; } int ItemModel::numberOfIndexesForItemInfo(const ItemInfo& info) const { return numberOfIndexesForImageId(info.id()); } int ItemModel::numberOfIndexesForImageId(qlonglong id) const { if (d->extraValues.isEmpty()) { return 0; } int count = 0; QHash::const_iterator it; for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it) { ++count; } return count; } // static method ItemInfo ItemModel::retrieveItemInfo(const QModelIndex& index) { if (!index.isValid()) { return ItemInfo(); } ItemModel* const model = index.data(ItemModelPointerRole).value(); int row = index.data(ItemModelInternalId).toInt(); if (!model) { return ItemInfo(); } return model->imageInfo(row); } // static method qlonglong ItemModel::retrieveImageId(const QModelIndex& index) { if (!index.isValid()) { return 0; } ItemModel* const model = index.data(ItemModelPointerRole).value(); int row = index.data(ItemModelInternalId).toInt(); if (!model) { return 0; } return model->imageId(row); } QModelIndex ItemModel::indexForPath(const QString& filePath) const { if (d->keepFilePathCache) { return indexForImageId(d->filePathHash.value(filePath)); } else { const int size = d->infos.size(); for (int i = 0 ; i < size ; ++i) { if (d->infos.at(i).filePath() == filePath) { return createIndex(i, 0); } } } return QModelIndex(); } QList ItemModel::indexesForPath(const QString& filePath) const { if (d->keepFilePathCache) { return indexesForImageId(d->filePathHash.value(filePath)); } else { QList indexes; const int size = d->infos.size(); for (int i = 0 ; i < size ; ++i) { if (d->infos.at(i).filePath() == filePath) { indexes << createIndex(i, 0); } } return indexes; } } ItemInfo ItemModel::imageInfo(const QString& filePath) const { if (d->keepFilePathCache) { qlonglong id = d->filePathHash.value(filePath); if (id) { int index = d->idHash.value(id, -1); if (index != -1) { return d->infos.at(index); } } } else { foreach (const ItemInfo& info, d->infos) { if (info.filePath() == filePath) { return info; } } } return ItemInfo(); } QList ItemModel::imageInfos(const QString& filePath) const { QList infos; if (d->keepFilePathCache) { qlonglong id = d->filePathHash.value(filePath); if (id) { foreach (int index, d->idHash.values(id)) { infos << d->infos.at(index); } } } else { foreach (const ItemInfo& info, d->infos) { if (info.filePath() == filePath) { infos << info; } } } return infos; } void ItemModel::addItemInfo(const ItemInfo& info) { addItemInfos(QList() << info, QList()); } void ItemModel::addItemInfos(const QList& infos) { addItemInfos(infos, QList()); } void ItemModel::addItemInfos(const QList& infos, const QList& extraValues) { if (infos.isEmpty()) { return; } if (d->incrementalUpdater) { d->incrementalUpdater->appendInfos(infos, extraValues); } else { appendInfos(infos, extraValues); } } void ItemModel::addItemInfoSynchronously(const ItemInfo& info) { addItemInfosSynchronously(QList() << info, QList()); } void ItemModel::addItemInfosSynchronously(const QList& infos) { addItemInfos(infos, QList()); } void ItemModel::addItemInfosSynchronously(const QList& infos, const QList& extraValues) { if (infos.isEmpty()) { return; } publiciseInfos(infos, extraValues); emit processAdded(infos, extraValues); } void ItemModel::ensureHasItemInfo(const ItemInfo& info) { ensureHasItemInfos(QList() << info, QList()); } void ItemModel::ensureHasItemInfos(const QList& infos) { ensureHasItemInfos(infos, QList()); } void ItemModel::ensureHasItemInfos(const QList& infos, const QList& extraValues) { if (extraValues.isEmpty()) { if (!d->pendingExtraValues.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos."; return; } } else { if (d->pendingInfos.size() != d->pendingExtraValues.size()) { qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos."; return; } } d->pendingInfos << infos; d->pendingExtraValues << extraValues; cleanSituationChecks(); } void ItemModel::clearItemInfos() { beginResetModel(); d->infos.clear(); d->extraValues.clear(); d->idHash.clear(); d->filePathHash.clear(); delete d->incrementalUpdater; d->incrementalUpdater = nullptr; d->pendingInfos.clear(); d->pendingExtraValues.clear(); d->refreshing = false; d->reAdding = false; d->incrementalRefreshRequested = false; imageInfosCleared(); endResetModel(); } void ItemModel::setItemInfos(const QList& infos) { clearItemInfos(); addItemInfos(infos); } QList ItemModel::imageInfos() const { return d->infos; } QList ItemModel::imageIds() const { return d->idHash.keys(); } bool ItemModel::hasImage(qlonglong id) const { return d->idHash.contains(id); } bool ItemModel::hasImage(const ItemInfo& info) const { return d->idHash.contains(info.id()); } bool ItemModel::hasImage(const ItemInfo& info, const QVariant& extraValue) const { return hasImage(info.id(), extraValue); } bool ItemModel::hasImage(qlonglong id, const QVariant& extraValue) const { if (d->extraValues.isEmpty()) { return hasImage(id); } QHash::const_iterator it; for (it = d->idHash.constFind(id) ; it != d->idHash.constEnd() && it.key() == id ; ++it) { if (d->extraValues.at(it.value()) == extraValue) { return true; } } return false;; } QList ItemModel::uniqueItemInfos() const { if (d->extraValues.isEmpty()) { return d->infos; } QList uniqueInfos; const int size = d->infos.size(); for (int i = 0 ; i < size ; ++i) { const ItemInfo& info = d->infos.at(i); if (d->idHash.value(info.id()) == i) { uniqueInfos << info; } } return uniqueInfos; } void ItemModel::emitDataChangedForAll() { if (d->infos.isEmpty()) { return; } QModelIndex first = createIndex(0, 0); QModelIndex last = createIndex(d->infos.size() - 1, 0); emit dataChanged(first, last); } void ItemModel::emitDataChangedForSelection(const QItemSelection& selection) { if (!selection.isEmpty()) { foreach (const QItemSelectionRange& range, selection) { emit dataChanged(range.topLeft(), range.bottomRight()); } } } void ItemModel::ensureHasGroupedImages(const ItemInfo& groupLeader) { ensureHasItemInfos(groupLeader.groupedImages()); } // ------------ Preprocessing ------------- void ItemModel::setPreprocessor(QObject* const preprocessor) { unsetPreprocessor(d->preprocessor); d->preprocessor = preprocessor; } void ItemModel::unsetPreprocessor(QObject* const preprocessor) { if (preprocessor && d->preprocessor == preprocessor) { disconnect(this, SIGNAL(preprocess(QList,QList)), nullptr, nullptr); disconnect(d->preprocessor, nullptr, this, SLOT(reAddItemInfos(QList,QList))); disconnect(d->preprocessor, nullptr, this, SLOT(reAddingFinished())); } } void ItemModel::appendInfos(const QList& infos, const QList& extraValues) { if (infos.isEmpty()) { return; } if (d->preprocessor) { d->reAdding = true; emit preprocess(infos, extraValues); } else { publiciseInfos(infos, extraValues); } } void ItemModel::appendInfosChecked(const QList& infos, const QList& extraValues) { // This method does deduplication. It is private because in context of readding or refreshing it is of no use. if (extraValues.isEmpty()) { QList checkedInfos; foreach (const ItemInfo& info, infos) { if (!hasImage(info)) { checkedInfos << info; } } appendInfos(checkedInfos, QList()); } else { QList checkedInfos; QList checkedExtraValues; const int size = infos.size(); for (int i = 0 ; i < size ; ++i) { if (!hasImage(infos[i], extraValues[i])) { checkedInfos << infos[i]; checkedExtraValues << extraValues[i]; } } appendInfos(checkedInfos, checkedExtraValues); } } void ItemModel::reAddItemInfos(const QList& infos, const QList& extraValues) { // addItemInfos -> appendInfos -> preprocessor -> reAddItemInfos publiciseInfos(infos, extraValues); } void ItemModel::reAddingFinished() { d->reAdding = false; cleanSituationChecks(); } void ItemModel::startRefresh() { d->refreshing = true; } void ItemModel::finishRefresh() { d->refreshing = false; cleanSituationChecks(); } bool ItemModel::isRefreshing() const { return d->refreshing; } void ItemModel::cleanSituationChecks() { // For starting an incremental refresh we want a clear situation: // Any remaining batches from non-incremental refreshing subclasses have been received in appendInfos(), // any batches sent to preprocessor for re-adding have been re-added. if (d->refreshing || d->reAdding) { return; } if (!d->pendingInfos.isEmpty()) { appendInfosChecked(d->pendingInfos, d->pendingExtraValues); d->pendingInfos.clear(); d->pendingExtraValues.clear(); cleanSituationChecks(); return; } if (d->incrementalRefreshRequested) { d->incrementalRefreshRequested = false; emit readyForIncrementalRefresh(); } else { emit allRefreshingFinished(); } } void ItemModel::publiciseInfos(const QList& infos, const QList& extraValues) { if (infos.isEmpty()) { return; } Q_ASSERT(infos.size() == extraValues.size() || (extraValues.isEmpty() && d->extraValues.isEmpty())); emit imageInfosAboutToBeAdded(infos); const int firstNewIndex = d->infos.size(); const int lastNewIndex = d->infos.size() + infos.size() - 1; beginInsertRows(QModelIndex(), firstNewIndex, lastNewIndex); d->infos << infos; d->extraValues << extraValues; for (int i = firstNewIndex ; i <= lastNewIndex ; ++i) { const ItemInfo& info = d->infos.at(i); qlonglong id = info.id(); d->idHash.insertMulti(id, i); if (d->keepFilePathCache) { d->filePathHash[info.filePath()] = id; } } endInsertRows(); emit imageInfosAdded(infos); } void ItemModel::requestIncrementalRefresh() { if (d->reAdding) { d->incrementalRefreshRequested = true; } else { emit readyForIncrementalRefresh(); } } bool ItemModel::hasIncrementalRefreshPending() const { return d->incrementalRefreshRequested; } void ItemModel::startIncrementalRefresh() { delete d->incrementalUpdater; d->incrementalUpdater = new ItemModelIncrementalUpdater(d); } void ItemModel::finishIncrementalRefresh() { if (!d->incrementalUpdater) { return; } // remove old entries QList > pairs = d->incrementalUpdater->oldIndexes(); removeRowPairs(pairs); // add new indexes appendInfos(d->incrementalUpdater->newInfos, d->incrementalUpdater->newExtraValues); delete d->incrementalUpdater; d->incrementalUpdater = nullptr; } void ItemModel::removeIndex(const QModelIndex& index) { removeIndexes(QList() << index); } void ItemModel::removeIndexes(const QList& indexes) { QList listIndexes; foreach (const QModelIndex& index, indexes) { if (d->isValid(index)) { listIndexes << index.row(); } } if (listIndexes.isEmpty()) { return; } removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes)); } void ItemModel::removeItemInfo(const ItemInfo& info) { removeItemInfos(QList() << info); } void ItemModel::removeItemInfos(const QList& infos) { QList listIndexes; foreach (const ItemInfo& info, infos) { QModelIndex index = indexForImageId(info.id()); if (index.isValid()) { listIndexes << index.row(); } } removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes)); } void ItemModel::removeItemInfos(const QList& infos, const QList& extraValues) { if (extraValues.isEmpty()) { removeItemInfos(infos); return; } QList listIndexes; for (int i = 0 ; i < infos.size() ; ++i) { QModelIndex index = indexForImageId(infos.at(i).id(), extraValues.at(i)); if (index.isValid()) { listIndexes << index.row(); } } removeRowPairsWithCheck(ItemModelIncrementalUpdater::toContiguousPairs(listIndexes)); } void ItemModel::setSendRemovalSignals(bool send) { d->sendRemovalSignals = send; } template static bool pairsContain(const List& list, T value) { typename List::const_iterator middle; typename List::const_iterator begin = list.begin(); typename List::const_iterator end = list.end(); int n = int(end - begin); int half; while (n > 0) { half = n >> 1; middle = begin + half; if ((middle->first <= value) && (middle->second >= value)) { return true; } else if (middle->second < value) { begin = middle + 1; n -= half + 1; } else { n = half; } } return false; } void ItemModel::removeRowPairsWithCheck(const QList >& toRemove) { if (d->incrementalUpdater) { d->incrementalUpdater->aboutToBeRemovedInModel(toRemove); } removeRowPairs(toRemove); } void ItemModel::removeRowPairs(const QList >& toRemove) { if (toRemove.isEmpty()) { return; } // Remove old indexes // Keep in mind that when calling beginRemoveRows all structures announced to be removed // must still be valid, and this includes our hashes as well, which limits what we can optimize int removedRows = 0, offset = 0; typedef QPair IntPair; // to make foreach macro happy foreach (const IntPair& pair, toRemove) { const int begin = pair.first - offset; const int end = pair.second - offset; // inclusive removedRows = end - begin + 1; // when removing from the list, all subsequent indexes are affected offset += removedRows; QList removedInfos; if (d->sendRemovalSignals) { std::copy(d->infos.begin() + begin, d->infos.begin() + end, removedInfos.begin()); emit imageInfosAboutToBeRemoved(removedInfos); } imageInfosAboutToBeRemoved(begin, end); beginRemoveRows(QModelIndex(), begin, end); // update idHash - which points to indexes of d->infos, and these change now! QHash::iterator it; for (it = d->idHash.begin() ; it != d->idHash.end() ; ) { if (it.value() >= begin) { if (it.value() > end) { // after the removed interval: adjust index it.value() -= removedRows; } else { // in the removed interval it = d->idHash.erase(it); continue; } } ++it; } // remove from list d->infos.erase(d->infos.begin() + begin, d->infos.begin() + (end + 1)); if (!d->extraValues.isEmpty()) { d->extraValues.erase(d->extraValues.begin() + begin, d->extraValues.begin() + (end + 1)); } endRemoveRows(); if (d->sendRemovalSignals) { emit imageInfosRemoved(removedInfos); } } // tidy up: remove old indexes from file path hash now if (d->keepFilePathCache) { QHash::iterator it; for (it = d->filePathHash.begin() ; it != d->filePathHash.end() ; ) { if (pairsContain(toRemove, it.value())) { it = d->filePathHash.erase(it); } else { ++it; } } } } ItemModelIncrementalUpdater::ItemModelIncrementalUpdater(ItemModel::Private* d) { oldIds = d->idHash; oldExtraValues = d->extraValues; } void ItemModelIncrementalUpdater::appendInfos(const QList& infos, const QList& extraValues) { if (extraValues.isEmpty()) { foreach (const ItemInfo& info, infos) { QHash::iterator it = oldIds.find(info.id()); if (it != oldIds.end()) { oldIds.erase(it); } else { newInfos << info; } } } else { for (int i = 0 ; i < infos.size() ; ++i) { const ItemInfo& info = infos.at(i); bool found = false; QHash::iterator it; for (it = oldIds.find(info.id()) ; it != oldIds.end() && it.key() == info.id() ; ++it) { // first check is for bug #262596. Not sure if needed. if (it.value() < oldExtraValues.size() && extraValues.at(i) == oldExtraValues.at(it.value())) { found = true; break; } } if (found) { oldIds.erase(it); // do not erase from oldExtraValues - oldIds is a hash id -> index. } else { newInfos << info; newExtraValues << extraValues.at(i); } } } } void ItemModelIncrementalUpdater::aboutToBeRemovedInModel(const IntPairList& toRemove) { modelRemovals << toRemove; } QList > ItemModelIncrementalUpdater::oldIndexes() { // first, apply all changes to indexes by direct removal in model // while the updater was active foreach (const IntPairList& list, modelRemovals) { int removedRows = 0; int offset = 0; foreach (const IntPair& pair, list) { const int begin = pair.first - offset; const int end = pair.second - offset; // inclusive removedRows = end - begin + 1; // when removing from the list, all subsequent indexes are affected offset += removedRows; // update idHash - which points to indexes of d->infos, and these change now! QHash::iterator it; for (it = oldIds.begin() ; it != oldIds.end() ; ) { if (it.value() >= begin) { if (it.value() > end) { // after the removed interval: adjust index it.value() -= removedRows; } else { // in the removed interval it = oldIds.erase(it); continue; } } ++it; } } } modelRemovals.clear(); return toContiguousPairs(oldIds.values()); } QList > ItemModelIncrementalUpdater::toContiguousPairs(const QList& unsorted) { // Take the given indices and return them as contiguous pairs [begin, end] QList > pairs; if (unsorted.isEmpty()) { return pairs; } QList indices(unsorted); std::sort(indices.begin(), indices.end()); QPair pair(indices.first(), indices.first()); for (int i = 1 ; i < indices.size() ; ++i) { const int &index = indices.at(i); if (index == pair.second + 1) { pair.second = index; continue; } pairs << pair; // insert last pair pair.first = index; pair.second = index; } pairs << pair; return pairs; } // ------------ QAbstractItemModel implementation ------------- QVariant ItemModel::data(const QModelIndex& index, int role) const { if (!d->isValid(index)) { return QVariant(); } switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: return d->infos.at(index.row()).name(); case ItemModelPointerRole: return QVariant::fromValue(const_cast(this)); case ItemModelInternalId: return index.row(); case CreationDateRole: return d->infos.at(index.row()).dateTime(); case ExtraDataRole: if (d->extraValueValid(index)) { return d->extraValues.at(index.row()); } else { return QVariant(); } case ExtraDataDuplicateCount: { qlonglong id = d->infos.at(index.row()).id(); return numberOfIndexesForImageId(id); } } return QVariant(); } QVariant ItemModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section) Q_UNUSED(orientation) Q_UNUSED(role) return QVariant(); } int ItemModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return d->infos.size(); } Qt::ItemFlags ItemModel::flags(const QModelIndex& index) const { if (!d->isValid(index)) { return nullptr; } Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled; f |= dragDropFlags(index); return f; } QModelIndex ItemModel::index(int row, int column, const QModelIndex& parent) const { if ((column != 0) || (row < 0) || parent.isValid() || (row >= d->infos.size())) { return QModelIndex(); } return createIndex(row, 0); } // ------------ Database watch ------------- void ItemModel::slotImageChange(const ImageChangeset& changeset) { if (d->infos.isEmpty()) { return; } if (d->watchFlags & changeset.changes()) { QItemSelection items; foreach (const qlonglong& id, changeset.ids()) { QModelIndex index = indexForImageId(id); if (index.isValid()) { items.select(index, index); } } if (!items.isEmpty()) { emitDataChangedForSelection(items); emit imageChange(changeset, items); } } } void ItemModel::slotImageTagChange(const ImageTagChangeset& changeset) { if (d->infos.isEmpty()) { return; } QItemSelection items; foreach (const qlonglong& id, changeset.ids()) { QModelIndex index = indexForImageId(id); if (index.isValid()) { items.select(index, index); } } if (!items.isEmpty()) { emitDataChangedForSelection(items); emit imageTagChange(changeset, items); } } } // namespace Digikam diff --git a/core/libs/widgets/mainview/sidebar.cpp b/core/libs/widgets/mainview/sidebar.cpp index fd1a568f32..e5c4d92d7f 100644 --- a/core/libs/widgets/mainview/sidebar.cpp +++ b/core/libs/widgets/mainview/sidebar.cpp @@ -1,1407 +1,1407 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-03-22 * Description : a widget to manage sidebar in GUI. * * Copyright (C) 2005-2006 by Joern Ahrens * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2008-2011 by Marcel Wiesweg * Copyright (C) 2001-2003 by Joseph Wenninger * * 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 "sidebar.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN DMultiTabBarFrame::Private { public: QBoxLayout* mainLayout; QList tabs; Qt::Edge position; DMultiTabBar::TextStyle style; }; DMultiTabBarFrame::DMultiTabBarFrame(QWidget* const parent, Qt::Edge pos) : QFrame(parent), d(new Private) { d->position = pos; if (pos == Qt::LeftEdge || pos == Qt::RightEdge) { d->mainLayout = new QVBoxLayout(this); } else { d->mainLayout = new QHBoxLayout(this); } d->mainLayout->setContentsMargins(QMargins()); d->mainLayout->setSpacing(0); d->mainLayout->addStretch(); setFrameStyle(NoFrame); setBackgroundRole(QPalette::Window); } DMultiTabBarFrame::~DMultiTabBarFrame() { qDeleteAll(d->tabs); d->tabs.clear(); delete d; } void DMultiTabBarFrame::setStyle(DMultiTabBar::TextStyle style) { d->style = style; for (int i = 0 ; i < d->tabs.count() ; ++i) { d->tabs.at(i)->setStyle(d->style); } updateGeometry(); } void DMultiTabBarFrame::contentsMousePressEvent(QMouseEvent* e) { e->ignore(); } void DMultiTabBarFrame::mousePressEvent(QMouseEvent* e) { e->ignore(); } DMultiTabBarTab* DMultiTabBarFrame::tab(int id) const { QListIterator it(d->tabs); while (it.hasNext()) { DMultiTabBarTab* const tab = it.next(); if (tab->id() == id) { return tab; } } return nullptr; } int DMultiTabBarFrame::appendTab(const QPixmap& pic, int id, const QString& text) { DMultiTabBarTab* const tab = new DMultiTabBarTab(pic, text, id, this, d->position, d->style); d->tabs.append(tab); // Insert before the stretch. d->mainLayout->insertWidget(d->tabs.size()-1, tab); tab->show(); return 0; } void DMultiTabBarFrame::removeTab(int id) { for (int pos = 0 ; pos < d->tabs.count() ; ++pos) { if (d->tabs.at(pos)->id() == id) { // remove & delete the tab delete d->tabs.takeAt(pos); break; } } } void DMultiTabBarFrame::setPosition(Qt::Edge pos) { d->position = pos; for (int i = 0 ; i < d->tabs.count() ; ++i) { d->tabs.at(i)->setPosition(d->position); } updateGeometry(); } QList* DMultiTabBarFrame::tabs() { return &d->tabs; } // ------------------------------------------------------------------------------------- DMultiTabBarButton::DMultiTabBarButton(const QPixmap& pic, const QString& text, int id, QWidget* const parent) : QPushButton(QIcon(pic), text, parent), m_id(id) { - // --- NOTE: use dynamic binding as slotChangedTab() is a virtual method which can be re-implemented in derived classes. + // --- NOTE: use dynamic binding as slotClicked() is a virtual method which can be re-implemented in derived classes. connect(this, &DMultiTabBarButton::clicked, this, &DMultiTabBarButton::slotClicked); // we can't see the focus, so don't take focus. #45557 // If keyboard navigation is wanted, then only the bar should take focus, // and arrows could change the focused button; but generally, tabbars don't take focus anyway. setFocusPolicy(Qt::NoFocus); // See RB #128005 setAttribute(Qt::WA_LayoutUsesWidgetRect); } DMultiTabBarButton::~DMultiTabBarButton() { } void DMultiTabBarButton::setText(const QString& text) { QPushButton::setText(text); } void DMultiTabBarButton::slotClicked() { updateGeometry(); emit clicked(m_id); } int DMultiTabBarButton::id() const { return m_id; } void DMultiTabBarButton::hideEvent(QHideEvent* e) { QPushButton::hideEvent(e); DMultiTabBar* const tb = dynamic_cast(parentWidget()); if (tb) { tb->updateSeparator(); } } void DMultiTabBarButton::showEvent(QShowEvent* e) { QPushButton::showEvent(e); DMultiTabBar* const tb = dynamic_cast(parentWidget()); if (tb) { tb->updateSeparator(); } } void DMultiTabBarButton::paintEvent(QPaintEvent*) { QStyleOptionButton opt; opt.initFrom(this); opt.icon = icon(); opt.iconSize = iconSize(); // removes the QStyleOptionButton::HasMenu ButtonFeature opt.features = QStyleOptionButton::Flat; QPainter painter(this); style()->drawControl(QStyle::CE_PushButton, &opt, &painter, this); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DMultiTabBarTab::Private { public: Qt::Edge position; DMultiTabBar::TextStyle style; }; DMultiTabBarTab::DMultiTabBarTab(const QPixmap& pic, const QString& text, int id, QWidget* const parent, Qt::Edge pos, DMultiTabBar::TextStyle style) : DMultiTabBarButton(pic, text, id, parent), d(new Private) { d->style = style; d->position = pos; setToolTip(text); setCheckable(true); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); // shrink down to icon only, but prefer to show text if it's there } DMultiTabBarTab::~DMultiTabBarTab() { delete d; } void DMultiTabBarTab::setPosition(Qt::Edge pos) { d->position = pos; updateGeometry(); } void DMultiTabBarTab::setStyle(DMultiTabBar::TextStyle style) { d->style = style; updateGeometry(); } QPixmap DMultiTabBarTab::iconPixmap() const { int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this); return icon().pixmap(iconSize); } void DMultiTabBarTab::initStyleOption(QStyleOptionToolButton* opt) const { opt->initFrom(this); // Setup icon.. if (!icon().isNull()) { opt->iconSize = iconPixmap().size(); opt->icon = icon(); } // Should we draw text? if (shouldDrawText()) { opt->text = text(); } if (underMouse()) { opt->state |= QStyle::State_AutoRaise | QStyle::State_MouseOver | QStyle::State_Raised; } if (isChecked()) { opt->state |= QStyle::State_Sunken | QStyle::State_On; } opt->font = font(); opt->toolButtonStyle = shouldDrawText() ? Qt::ToolButtonTextBesideIcon : Qt::ToolButtonIconOnly; opt->subControls = QStyle::SC_ToolButton; } QSize DMultiTabBarTab::sizeHint() const { return computeSizeHint(shouldDrawText()); } QSize DMultiTabBarTab::minimumSizeHint() const { return computeSizeHint(false); } void DMultiTabBarTab::computeMargins(int* hMargin, int* vMargin) const { // Unfortunately, QStyle does not give us enough information to figure out // where to place things, so we try to reverse-engineer it QStyleOptionToolButton opt; initStyleOption(&opt); QPixmap iconPix = iconPixmap(); QSize trialSize = iconPix.size(); QSize expandSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, trialSize, this); *hMargin = (expandSize.width() - trialSize.width())/2; *vMargin = (expandSize.height() - trialSize.height())/2; } QSize DMultiTabBarTab::computeSizeHint(bool withText) const { // Compute as horizontal first, then flip around if need be. QStyleOptionToolButton opt; initStyleOption(&opt); int hMargin, vMargin; computeMargins(&hMargin, &vMargin); // Compute interior size, starting from pixmap.. QPixmap iconPix = iconPixmap(); QSize size = iconPix.size(); // Always include text height in computation, to avoid resizing the minor direction // when expanding text.. QSize textSize = fontMetrics().size(0, text()); size.setHeight(qMax(size.height(), textSize.height())); // Pick margins for major/minor direction, depending on orientation int majorMargin = isVertical() ? vMargin : hMargin; int minorMargin = isVertical() ? hMargin : vMargin; size.setWidth (size.width() + 2*majorMargin); size.setHeight(size.height() + 2*minorMargin); if (withText) { // Add enough room for the text, and an extra major margin. size.setWidth(size.width() + textSize.width() + majorMargin); } if (isVertical()) { return QSize(size.height(), size.width()); } return size; } void DMultiTabBarTab::setState(bool newState) { setChecked(newState); updateGeometry(); } void DMultiTabBarTab::setIcon(const QString& icon) { const QIcon i = QIcon::fromTheme(icon); const int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this); setIcon(i.pixmap(iconSize)); } void DMultiTabBarTab::setIcon(const QPixmap& icon) { QPushButton::setIcon(icon); } bool DMultiTabBarTab::shouldDrawText() const { return (d->style == DMultiTabBar::AllIconsText) || isChecked(); } bool DMultiTabBarTab::isVertical() const { return (d->position == Qt::RightEdge || d->position == Qt::LeftEdge); } void DMultiTabBarTab::paintEvent(QPaintEvent*) { QPainter painter(this); QStyleOptionToolButton opt; initStyleOption(&opt); // Paint bevel.. if (underMouse() || isChecked()) { opt.text.clear(); opt.icon = QIcon(); style()->drawComplexControl(QStyle::CC_ToolButton, &opt, &painter, this); } int hMargin, vMargin; computeMargins(&hMargin, &vMargin); // We first figure out how much room we have for the text, based on // icon size and margin, try to fit in by eliding, and perhaps // give up on drawing the text entirely if we're too short on room QPixmap icon = iconPixmap(); int textRoom = 0; int iconRoom = 0; QString t; if (shouldDrawText()) { if (isVertical()) { iconRoom = icon.height() + 2*vMargin; textRoom = height() - iconRoom - vMargin; } else { iconRoom = icon.width() + 2*hMargin; textRoom = width() - iconRoom - hMargin; } t = painter.fontMetrics().elidedText(text(), Qt::ElideRight, textRoom); // See whether anything is left. Qt will return either // ... or the ellipsis unicode character, 0x2026 if (t == QLatin1String("...") || t == QChar(0x2026)) { t.clear(); } } // Label time.... Simple case: no text, so just plop down the icon right in the center // We only do this when the button never draws the text, to avoid jumps in icon position // when resizing if (!shouldDrawText()) { style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter | Qt::AlignVCenter, icon); return; } // Now where the icon/text goes depends on text direction and tab position QRect iconArea; QRect labelArea; bool bottomIcon = false; bool rtl = layoutDirection() == Qt::RightToLeft; if (isVertical()) { if (d->position == Qt::LeftEdge && !rtl) { bottomIcon = true; } if (d->position == Qt::RightEdge && rtl) { bottomIcon = true; } } if (isVertical()) { if (bottomIcon) { labelArea = QRect(0, vMargin, width(), textRoom); iconArea = QRect(0, vMargin + textRoom, width(), iconRoom); } else { labelArea = QRect(0, iconRoom, width(), textRoom); iconArea = QRect(0, 0, width(), iconRoom); } } else { // Pretty simple --- depends only on RTL/LTR if (rtl) { labelArea = QRect(hMargin, 0, textRoom, height()); iconArea = QRect(hMargin + textRoom, 0, iconRoom, height()); } else { labelArea = QRect(iconRoom, 0, textRoom, height()); iconArea = QRect(0, 0, iconRoom, height()); } } style()->drawItemPixmap(&painter, iconArea, Qt::AlignCenter | Qt::AlignVCenter, icon); if (t.isEmpty()) { return; } QRect labelPaintArea = labelArea; if (isVertical()) { // If we're vertical, we paint to a simple 0,0 origin rect, // and get the transformations to get us in the right place labelPaintArea = QRect(0, 0, labelArea.height(), labelArea.width()); QTransform tr; if (bottomIcon) { tr.translate(labelArea.x(), labelPaintArea.width() + labelArea.y()); tr.rotate(-90); } else { tr.translate(labelPaintArea.height() + labelArea.x(), labelArea.y()); tr.rotate(90); } painter.setTransform(tr); } style()->drawItemText(&painter, labelPaintArea, Qt::AlignLeading | Qt::AlignVCenter, palette(), true, t, QPalette::ButtonText); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DMultiTabBar::Private { public: DMultiTabBarFrame* internal; QBoxLayout* layout; QFrame* btnTabSep; QList buttons; Qt::Edge position; }; DMultiTabBar::DMultiTabBar(Qt::Edge pos, QWidget* const parent) : QWidget(parent), d(new Private) { if ((pos == Qt::LeftEdge) || (pos == Qt::RightEdge)) { d->layout = new QVBoxLayout(this); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); } else { d->layout = new QHBoxLayout(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); } d->layout->setContentsMargins(QMargins()); d->layout->setSpacing(0); d->internal = new DMultiTabBarFrame(this, pos); setPosition(pos); setStyle(ActiveIconText); d->layout->insertWidget(0, d->internal); d->layout->insertWidget(0, d->btnTabSep = new QFrame(this)); d->btnTabSep->setFixedHeight(4); d->btnTabSep->setFrameStyle(QFrame::Panel | QFrame::Sunken); d->btnTabSep->setLineWidth(2); d->btnTabSep->hide(); updateGeometry(); } DMultiTabBar::~DMultiTabBar() { qDeleteAll(d->buttons); d->buttons.clear(); delete d; } int DMultiTabBar::appendButton(const QPixmap &pic, int id, QMenu *popup, const QString&) { DMultiTabBarButton* const btn = new DMultiTabBarButton(pic, QString(), id, this); // a button with a QMenu can have another size. Make sure the button has always the same size. btn->setFixedWidth(btn->height()); btn->setMenu(popup); d->buttons.append(btn); d->layout->insertWidget(0,btn); btn->show(); d->btnTabSep->show(); return 0; } void DMultiTabBar::updateSeparator() { bool hideSep = true; QListIterator it(d->buttons); while (it.hasNext()) { if (it.next()->isVisibleTo(this)) { hideSep = false; break; } } if (hideSep) { d->btnTabSep->hide(); } else { d->btnTabSep->show(); } } int DMultiTabBar::appendTab(const QPixmap& pic, int id, const QString& text) { d->internal->appendTab(pic,id,text); return 0; } DMultiTabBarButton* DMultiTabBar::button(int id) const { QListIterator it(d->buttons); while (it.hasNext()) { DMultiTabBarButton* const button = it.next(); if (button->id() == id) return button; } return nullptr; } DMultiTabBarTab* DMultiTabBar::tab(int id) const { return d->internal->tab(id); } void DMultiTabBar::removeButton(int id) { for (int pos = 0 ; pos < d->buttons.count() ; ++pos) { if (d->buttons.at(pos)->id() == id) { d->buttons.takeAt(pos)->deleteLater(); break; } } if (d->buttons.count() == 0) { d->btnTabSep->hide(); } } void DMultiTabBar::removeTab(int id) { d->internal->removeTab(id); } void DMultiTabBar::setTab(int id,bool state) { DMultiTabBarTab* const ttab = tab(id); if (ttab) { ttab->setState(state); } } bool DMultiTabBar::isTabRaised(int id) const { DMultiTabBarTab* const ttab = tab(id); if (ttab) { return ttab->isChecked(); } return false; } void DMultiTabBar::setStyle(TextStyle style) { d->internal->setStyle(style); } DMultiTabBar::TextStyle DMultiTabBar::tabStyle() const { return d->internal->d->style; } void DMultiTabBar::setPosition(Qt::Edge pos) { d->position = pos; d->internal->setPosition(pos); } Qt::Edge DMultiTabBar::position() const { return d->position; } void DMultiTabBar::fontChange(const QFont&) { updateGeometry(); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN SidebarState { public: SidebarState() : activeWidget(nullptr), size(0) { } SidebarState(QWidget* const w, int size) : activeWidget(w), size(size) { } QWidget* activeWidget; int size; }; // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN Sidebar::Private { public: explicit Private() : minimizedDefault(false), minimized(false), isMinimized(false), tabs(0), activeTab(-1), dragSwitchId(-1), restoreSize(0), stack(nullptr), splitter(nullptr), dragSwitchTimer(nullptr), appendedTabsStateCache(), optionActiveTabEntry(QLatin1String("ActiveTab")), optionMinimizedEntry(QLatin1String("Minimized")), optionRestoreSizeEntry(QLatin1String("RestoreSize")) { } bool minimizedDefault; bool minimized; bool isMinimized; // Backup of shrinked status before backup(), restored by restore() // NOTE: when sidebar is hidden, only icon bar is affected. If sidebar view is // visible, this one must be shrink and restored accordingly. int tabs; int activeTab; int dragSwitchId; int restoreSize; QStackedWidget* stack; SidebarSplitter* splitter; QTimer* dragSwitchTimer; QHash appendedTabsStateCache; const QString optionActiveTabEntry; const QString optionMinimizedEntry; const QString optionRestoreSizeEntry; }; class Q_DECL_HIDDEN SidebarSplitter::Private { public: QList sidebars; }; // ------------------------------------------------------------------------------------- Sidebar::Sidebar(QWidget* const parent, SidebarSplitter* const sp, Qt::Edge side, bool minimizedDefault) : DMultiTabBar(side, parent), StateSavingObject(this), d(new Private) { d->splitter = sp; d->minimizedDefault = minimizedDefault; d->stack = new QStackedWidget(d->splitter); d->dragSwitchTimer = new QTimer(this); connect(d->dragSwitchTimer, SIGNAL(timeout()), this, SLOT(slotDragSwitchTimer())); d->splitter->d->sidebars << this; setStyle(DMultiTabBar::ActiveIconText); } Sidebar::~Sidebar() { saveState(); if (d->splitter) { d->splitter->d->sidebars.removeAll(this); } delete d; } SidebarSplitter* Sidebar::splitter() const { return d->splitter; } void Sidebar::doLoadState() { KConfigGroup group = getConfigGroup(); int tab = group.readEntry(entryName(d->optionActiveTabEntry), 0); bool minimized = group.readEntry(entryName(d->optionMinimizedEntry), d->minimizedDefault); d->restoreSize = group.readEntry(entryName(d->optionRestoreSizeEntry), -1); // validate if (tab >= d->tabs || tab < 0) { tab = 0; } if (minimized) { d->activeTab = tab; setTab(d->activeTab, false); d->stack->setCurrentIndex(d->activeTab); shrink(); emit signalChangedTab(d->stack->currentWidget()); return; } d->activeTab = -1; clicked(tab); } void Sidebar::doSaveState() { KConfigGroup group = getConfigGroup(); group.writeEntry(entryName(d->optionActiveTabEntry), d->activeTab); group.writeEntry(entryName(d->optionMinimizedEntry), d->minimized); group.writeEntry(entryName(d->optionRestoreSizeEntry), d->minimized ? d->restoreSize : -1); } void Sidebar::backup() { // backup preview state of sidebar view (shrink or not) d->isMinimized = d->minimized; // In all case, shrink sidebar view shrink(); DMultiTabBar::hide(); } void Sidebar::backup(const QList thirdWidgetsToBackup, QList* const sizes) { sizes->clear(); foreach (QWidget* const widget, thirdWidgetsToBackup) { *sizes << d->splitter->size(widget); } backup(); } void Sidebar::restore() { DMultiTabBar::show(); // restore preview state of sidebar view, stored in backup() if (!d->isMinimized) { expand(); } } void Sidebar::restore(const QList thirdWidgetsToRestore, const QList& sizes) { restore(); if (thirdWidgetsToRestore.size() == sizes.size()) { for (int i = 0 ; i < thirdWidgetsToRestore.size() ; ++i) { d->splitter->setSize(thirdWidgetsToRestore.at(i), sizes.at(i)); } } } void Sidebar::appendTab(QWidget* const w, const QIcon& pic, const QString& title) { // Store state (but not on initialization) if (isVisible()) { d->appendedTabsStateCache[w] = SidebarState(d->stack->currentWidget(), d->splitter->size(this)); } // Add tab w->setParent(d->stack); DMultiTabBar::appendTab(pic.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)), d->tabs, title); d->stack->insertWidget(d->tabs, w); tab(d->tabs)->setAcceptDrops(true); tab(d->tabs)->installEventFilter(this); connect(tab(d->tabs), SIGNAL(clicked(int)), this, SLOT(clicked(int))); d->tabs++; } void Sidebar::deleteTab(QWidget* const w) { int tab = d->stack->indexOf(w); if (tab < 0) { return; } bool removingActiveTab = (tab == d->activeTab); if (removingActiveTab) { d->activeTab = -1; } d->stack->removeWidget(d->stack->widget(tab)); // delete widget removeTab(tab); d->tabs--; // restore or reset active tab and width if (!d->minimized) { // restore to state before adding tab // using a hash is simple, but does not handle well multiple add/removal operations at a time SidebarState state = d->appendedTabsStateCache.take(w); if (state.activeWidget) { int tab = d->stack->indexOf(state.activeWidget); if (tab != -1) { switchTabAndStackToTab(tab); emit signalChangedTab(d->stack->currentWidget()); if (state.size == 0) { d->minimized = true; setTab(d->activeTab, false); } d->splitter->setSize(this, state.size); } } else { if (removingActiveTab) { clicked(d->tabs - 1); } d->splitter->setSize(this, -1); } } else { d->restoreSize = -1; } } void Sidebar::clicked(int tab) { if (tab >= d->tabs || tab < 0) { return; } if (tab == d->activeTab) { d->stack->isHidden() ? expand() : shrink(); } else { switchTabAndStackToTab(tab); if (d->minimized) { expand(); } emit signalChangedTab(d->stack->currentWidget()); } } void Sidebar::setActiveTab(QWidget* const w) { int tab = d->stack->indexOf(w); if (tab < 0) { return; } switchTabAndStackToTab(tab); if (d->minimized) { expand(); } emit signalChangedTab(d->stack->currentWidget()); } void Sidebar::activePreviousTab() { int tab = d->stack->indexOf(d->stack->currentWidget()); if (tab == 0) { tab = d->tabs-1; } else { tab--; } setActiveTab(d->stack->widget(tab)); } void Sidebar::activeNextTab() { int tab = d->stack->indexOf(d->stack->currentWidget()); if (tab == d->tabs-1) { tab = 0; } else { tab++; } setActiveTab(d->stack->widget(tab)); } void Sidebar::switchTabAndStackToTab(int tab) { if (d->activeTab >= 0) { setTab(d->activeTab, false); } d->activeTab = tab; setTab(d->activeTab, true); d->stack->setCurrentIndex(d->activeTab); } QWidget* Sidebar::getActiveTab() const { if (d->splitter) { return d->stack->currentWidget(); } else { return nullptr; } } void Sidebar::shrink() { d->minimized = true; // store the size that we had. We may later need it when we restore to visible. int currentSize = d->splitter->size(this); if (currentSize) { d->restoreSize = currentSize; } d->stack->hide(); emit signalViewChanged(); } void Sidebar::expand() { d->minimized = false; d->stack->show(); QTimer::singleShot(0, this, SLOT(slotExpandTimer())); } void Sidebar::slotExpandTimer() { // Do not expand to size 0 (only splitter handle visible) // but either to previous size, or the minimum size hint if (d->splitter->size(this) == 0) { setTab(d->activeTab, true); d->splitter->setSize(this, d->restoreSize ? d->restoreSize : -1); } emit signalViewChanged(); } bool Sidebar::isExpanded() const { return !d->minimized; } bool Sidebar::eventFilter(QObject* obj, QEvent* ev) { for (int i = 0 ; i < d->tabs ; ++i) { if (obj == tab(i)) { if (ev->type() == QEvent::DragEnter) { QDragEnterEvent* const e = static_cast(ev); enterEvent(e); e->accept(); return false; } else if (ev->type() == QEvent::DragMove) { if (!d->dragSwitchTimer->isActive()) { d->dragSwitchTimer->setSingleShot(true); d->dragSwitchTimer->start(800); d->dragSwitchId = i; } return false; } else if (ev->type() == QEvent::DragLeave) { d->dragSwitchTimer->stop(); QDragLeaveEvent* const e = static_cast(ev); leaveEvent(e); return false; } else if (ev->type() == QEvent::Drop) { d->dragSwitchTimer->stop(); QDropEvent* const e = static_cast(ev); leaveEvent(e); return false; } else { return false; } } } // Else, pass the event on to the parent class return DMultiTabBar::eventFilter(obj, ev); } void Sidebar::slotDragSwitchTimer() { clicked(d->dragSwitchId); } void Sidebar::slotSplitterBtnClicked() { clicked(d->activeTab); } // ----------------------------------------------------------------------------- const QString SidebarSplitter::DEFAULT_CONFIG_KEY = QLatin1String("SplitterState"); SidebarSplitter::SidebarSplitter(QWidget* const parent) : QSplitter(parent), d(new Private) { connect(this, SIGNAL(splitterMoved(int,int)), this, SLOT(slotSplitterMoved(int,int))); } SidebarSplitter::SidebarSplitter(Qt::Orientation orientation, QWidget* const parent) : QSplitter(orientation, parent), d(new Private) { connect(this, SIGNAL(splitterMoved(int,int)), this, SLOT(slotSplitterMoved(int,int))); } SidebarSplitter::~SidebarSplitter() { // retreat cautiously from sidebars that live longer foreach (Sidebar* const sidebar, d->sidebars) { sidebar->d->splitter = nullptr; } delete d; } void SidebarSplitter::restoreState(KConfigGroup& group) { restoreState(group, DEFAULT_CONFIG_KEY); } void SidebarSplitter::restoreState(KConfigGroup& group, const QString& key) { if (group.hasKey(key)) { QByteArray state; state = group.readEntry(key, state); QSplitter::restoreState(QByteArray::fromBase64(state)); } } void SidebarSplitter::saveState(KConfigGroup& group) { saveState(group, DEFAULT_CONFIG_KEY); } void SidebarSplitter::saveState(KConfigGroup& group, const QString& key) { group.writeEntry(key, QSplitter::saveState().toBase64()); } int SidebarSplitter::size(Sidebar* const bar) const { return size(bar->d->stack); } int SidebarSplitter::size(QWidget* const widget) const { int index = indexOf(widget); if (index == -1) { return -1; } return sizes().at(index); } void SidebarSplitter::setSize(Sidebar* const bar, int size) { setSize(bar->d->stack, size); } void SidebarSplitter::setSize(QWidget* const widget, int size) { int index = indexOf(widget); if (index == -1) { return; } // special case: Use minimum size hint if (size == -1) { if (orientation() == Qt::Horizontal) { size = widget->minimumSizeHint().width(); } if (orientation() == Qt::Vertical) { size = widget->minimumSizeHint().height(); } } QList sizeList = sizes(); sizeList[index] = size; setSizes(sizeList); } void SidebarSplitter::slotSplitterMoved(int pos, int index) { Q_UNUSED(pos); // When the user moves the splitter so that size is 0 (collapsed), // it may be difficult to restore the sidebar as clicking the buttons // has no effect (only hides/shows the splitter handle) // So we want to transform this size-0-sidebar // to a sidebar that is shrunk (d->stack hidden) // and can be restored by clicking a tab bar button // We need to look at the widget between index-1 and index // and the one between index and index+1 QList sizeList = sizes(); // Is there a sidebar with size 0 before index ? if (index > 0 && sizeList.at(index-1) == 0) { QWidget* const w = widget(index-1); foreach (Sidebar* const sidebar, d->sidebars) { if (w == sidebar->d->stack) { if (!sidebar->d->minimized) { sidebar->setTab(sidebar->d->activeTab, false); sidebar->shrink(); } break; } } } // Is there a sidebar with size 0 after index ? if (sizeList.at(index) == 0) { QWidget* const w = widget(index); foreach (Sidebar* const sidebar, d->sidebars) { if (w == sidebar->d->stack) { if (!sidebar->d->minimized) { sidebar->setTab(sidebar->d->activeTab, false); sidebar->shrink(); } break; } } } } } // namespace Digikam diff --git a/core/showfoto/thumbbar/showfotothumbnailbar.cpp b/core/showfoto/thumbbar/showfotothumbnailbar.cpp index c444ef3454..8be50248a4 100644 --- a/core/showfoto/thumbbar/showfotothumbnailbar.cpp +++ b/core/showfoto/thumbbar/showfotothumbnailbar.cpp @@ -1,213 +1,214 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 02-08-2013 * Description : Thumbnail bar for Showfoto * * Copyright (C) 2013 by Mohamed_Anwer * Copyright (C) 2013-2019 by Gilles Caulier * * 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 "showfotothumbnailbar.h" // Qt includes #include // Local includes #include "digikam_debug.h" #include "showfotosettings.h" #include "showfotodelegate.h" #include "showfotofiltermodel.h" #include "itemviewtooltip.h" #include "showfototooltipfiller.h" #include "showfotocategorizedview.h" #include "itemselectionoverlay.h" #include "showfotokineticscroller.h" #include "showfotocoordinatesoverlay.h" namespace ShowFoto { class Q_DECL_HIDDEN ShowfotoThumbnailBar::Private { public: explicit Private() { scrollPolicy = Qt::ScrollBarAlwaysOn; duplicatesFilter = nullptr; kScroller = nullptr; } Qt::ScrollBarPolicy scrollPolicy; NoDuplicatesShowfotoFilterModel* duplicatesFilter; ShowfotoKineticScroller* kScroller; }; ShowfotoThumbnailBar::ShowfotoThumbnailBar(QWidget* const parent) : ShowfotoCategorizedView(parent), d(new Private()) { setItemDelegate(new ShowfotoThumbnailDelegate(this)); setSpacing(3); setUsePointingHandCursor(false); setScrollStepGranularity(3); setScrollCurrentToCenter(ShowfotoSettings::instance()->getItemCenter()); setScrollBarPolicy(Qt::ScrollBarAlwaysOn); setDragEnabled(true); setAcceptDrops(true); setDropIndicatorShown(true); - slotSetupChanged(); + // NOTE: use dynamic binding as this virtual method can be re-implemented in derived classes. + this->slotSetupChanged(); d->kScroller = new ShowfotoKineticScroller(); d->kScroller->enableKineticScrollFor(this); } ShowfotoThumbnailBar::~ShowfotoThumbnailBar() { delete d; } void ShowfotoThumbnailBar::installOverlays() { addOverlay(new ShowfotoCoordinatesOverlay(this)); } void ShowfotoThumbnailBar::slotDockLocationChanged(Qt::DockWidgetArea area) { if (area == Qt::LeftDockWidgetArea || area == Qt::RightDockWidgetArea) { setFlow(TopToBottom); d->kScroller->setScrollFlow(TopToBottom); } else { setFlow(LeftToRight); d->kScroller->setScrollFlow(LeftToRight); } scrollTo(currentIndex()); } void ShowfotoThumbnailBar::setScrollBarPolicy(Qt::ScrollBarPolicy policy) { if (policy == Qt::ScrollBarAsNeeded) { // Delegate resizing will cause endless relayouting, see bug #228807 qCDebug(DIGIKAM_GENERAL_LOG) << "The Qt::ScrollBarAsNeeded policy is not supported by ShowfotoThumbnailBar"; } d->scrollPolicy = policy; if (flow() == TopToBottom) { setVerticalScrollBarPolicy(d->scrollPolicy); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } else { setHorizontalScrollBarPolicy(d->scrollPolicy); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } } void ShowfotoThumbnailBar::setFlow(QListView::Flow flow) { setWrapping(false); ShowfotoCategorizedView::setFlow(flow); ShowfotoThumbnailDelegate* const del = static_cast(delegate()); del->setFlow(flow); // Reset the minimum and maximum sizes. setMinimumSize(QSize(0, 0)); setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); // Adjust minimum and maximum width to thumbnail sizes. if (flow == TopToBottom) { int viewportFullWidgetOffset = size().width() - viewport()->size().width(); setMinimumWidth(del->minimumSize() + viewportFullWidgetOffset); setMaximumWidth(del->maximumSize() + viewportFullWidgetOffset); } else { int viewportFullWidgetOffset = size().height() - viewport()->size().height(); setMinimumHeight(del->minimumSize() + viewportFullWidgetOffset); setMaximumHeight(del->maximumSize() + viewportFullWidgetOffset); } setScrollBarPolicy(d->scrollPolicy); } void ShowfotoThumbnailBar::slotSetupChanged() { ShowfotoCategorizedView::slotSetupChanged(); } bool ShowfotoThumbnailBar::event(QEvent* e) { // reset widget max/min sizes if (e->type() == QEvent::StyleChange || e->type() == QEvent::Show) { setFlow(flow()); } return ShowfotoCategorizedView::event(e); } QModelIndex ShowfotoThumbnailBar::nextIndex(const QModelIndex& index) const { return showfotoFilterModel()->index(index.row() + 1, 0); } QModelIndex ShowfotoThumbnailBar::previousIndex(const QModelIndex& index) const { return showfotoFilterModel()->index(index.row() - 1, 0); } QModelIndex ShowfotoThumbnailBar::firstIndex() const { return showfotoFilterModel()->index(0, 0); } QModelIndex ShowfotoThumbnailBar::lastIndex() const { return showfotoFilterModel()->index(showfotoFilterModel()->rowCount() - 1, 0); } ShowfotoItemInfo ShowfotoThumbnailBar::findItemByUrl(const QUrl& url) { ShowfotoItemInfoList lst = showfotoItemInfos(); for (int i = 0 ; i < lst.size() ; ++i) { if (lst.at(i).url == url) { return lst.at(i); } } return ShowfotoItemInfo(); } } // namespace ShowFoto diff --git a/core/utilities/geolocation/geoiface/bookmark/bookmarksmenu.cpp b/core/utilities/geolocation/geoiface/bookmark/bookmarksmenu.cpp index 45840ce935..7e00c3fb12 100644 --- a/core/utilities/geolocation/geoiface/bookmark/bookmarksmenu.cpp +++ b/core/utilities/geolocation/geoiface/bookmark/bookmarksmenu.cpp @@ -1,352 +1,361 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-05-15 * Description : menu to manage GPS bookmarks * * Copyright (C) 2017-2019 by Gilles Caulier * * 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 "bookmarksmenu.h" // Qt includes #include #include #include // Local includes #include "bookmarknode.h" Q_DECLARE_METATYPE(QModelIndex) namespace Digikam { class Q_DECL_HIDDEN ModelMenu::Private { public: explicit Private() : maxRows(7), firstSeparator(-1), maxWidth(-1), hoverRole(0), separatorRole(0), model(nullptr) { } int maxRows; int firstSeparator; int maxWidth; int hoverRole; int separatorRole; QAbstractItemModel* model; QPersistentModelIndex root; }; ModelMenu::ModelMenu(QWidget* const parent) : QMenu(parent), d(new Private) { - connect(this, SIGNAL(aboutToShow()), - this, SLOT(slotAboutToShow())); + // --- NOTE: use dynamic binding as slotAboutToShow() is a virtual method which can be re-implemented in derived classes. + + connect(this, &ModelMenu::aboutToShow, + this, &ModelMenu::slotAboutToShow); } ModelMenu::~ModelMenu() { delete d; } bool ModelMenu::prePopulated() { return false; } void ModelMenu::postPopulated() { } void ModelMenu::setModel(QAbstractItemModel* model) { d->model = model; } QAbstractItemModel* ModelMenu::model() const { return d->model; } void ModelMenu::setMaxRows(int max) { d->maxRows = max; } int ModelMenu::maxRows() const { return d->maxRows; } void ModelMenu::setFirstSeparator(int offset) { d->firstSeparator = offset; } int ModelMenu::firstSeparator() const { return d->firstSeparator; } void ModelMenu::setRootIndex(const QModelIndex& index) { d->root = index; } QModelIndex ModelMenu::rootIndex() const { return d->root; } void ModelMenu::setHoverRole(int role) { d->hoverRole = role; } int ModelMenu::hoverRole() const { return d->hoverRole; } void ModelMenu::setSeparatorRole(int role) { d->separatorRole = role; } int ModelMenu::separatorRole() const { return d->separatorRole; } void ModelMenu::slotAboutToShow() { if (QMenu* const menu = qobject_cast(sender())) { QVariant v = menu->menuAction()->data(); if (v.canConvert()) { QModelIndex idx = qvariant_cast(v); createMenu(idx, -1, menu, menu); disconnect(menu, SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); return; } } clear(); - if (prePopulated()) + // NOTE: use dynamic binding as this virtual method can be re-implemented in derived classes. + if (this->prePopulated()) + { addSeparator(); + } int max = d->maxRows; if (max != -1) + { max += d->firstSeparator; + } createMenu(d->root, max, this, this); postPopulated(); } void ModelMenu::createMenu(const QModelIndex& parent, int max, QMenu* parentMenu, QMenu* menu) { if (!menu) { QString title = parent.data().toString(); menu = new QMenu(title, this); QIcon icon = qvariant_cast(parent.data(Qt::DecorationRole)); menu->setIcon(icon); parentMenu->addMenu(menu); QVariant v; v.setValue(parent); menu->menuAction()->setData(v); connect(menu, SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); return; } int end = d->model->rowCount(parent); if (max != -1) + { end = qMin(max, end); + } connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(triggered(QAction*))); connect(menu, SIGNAL(hovered(QAction*)), this, SLOT(hovered(QAction*))); for (int i = 0 ; i < end ; ++i) { QModelIndex idx = d->model->index(i, 0, parent); if (d->model->hasChildren(idx)) { createMenu(idx, -1, menu); } else { if (d->separatorRole != 0 && idx.data(d->separatorRole).toBool()) addSeparator(); else menu->addAction(makeAction(idx)); } if (menu == this && i == d->firstSeparator - 1) addSeparator(); } } QAction* ModelMenu::makeAction(const QModelIndex& index) { QIcon icon = qvariant_cast(index.data(Qt::DecorationRole)); QAction* const action = makeAction(icon, index.data().toString(), this); QVariant v; v.setValue(index); action->setData(v); return action; } QAction* ModelMenu::makeAction(const QIcon& icon, const QString& text, QObject* const parent) { QFontMetrics fm(font()); if (d->maxWidth == -1) #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) d->maxWidth = fm.horizontalAdvance(QLatin1Char('m')) * 30; #else d->maxWidth = fm.width(QLatin1Char('m')) * 30; #endif QString smallText = fm.elidedText(text, Qt::ElideMiddle, d->maxWidth); return (new QAction(icon, smallText, parent)); } void ModelMenu::triggered(QAction* action) { QVariant v = action->data(); if (v.canConvert()) { QModelIndex idx = qvariant_cast(v); emit activated(idx); } } void ModelMenu::hovered(QAction* action) { QVariant v = action->data(); if (v.canConvert()) { QModelIndex idx = qvariant_cast(v); QString hoveredString = idx.data(d->hoverRole).toString(); if (!hoveredString.isEmpty()) emit hovered(hoveredString); } } // ------------------------------------------------------------------------------ class Q_DECL_HIDDEN BookmarksMenu::Private { public: explicit Private() : manager(nullptr) { } BookmarksManager* manager; QList initActions; }; BookmarksMenu::BookmarksMenu(BookmarksManager* const mngr, QWidget* const parent) : ModelMenu(parent), d(new Private) { d->manager = mngr; connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(activated(QModelIndex))); setMaxRows(-1); setHoverRole(BookmarksModel::UrlStringRole); setSeparatorRole(BookmarksModel::SeparatorRole); } BookmarksMenu::~BookmarksMenu() { delete d; } void BookmarksMenu::activated(const QModelIndex& index) { emit openUrl(index.data(BookmarksModel::UrlRole).toUrl()); } bool BookmarksMenu::prePopulated() { setModel(d->manager->bookmarksModel()); setRootIndex(d->manager->bookmarksModel()->index(d->manager->bookmarks()->children().first())); // initial actions foreach (QAction* const ac, d->initActions) { if (ac) addAction(ac); } if (!d->initActions.isEmpty()) addSeparator(); createMenu(rootIndex(), 0, this, this); return true; } void BookmarksMenu::setInitialActions(const QList& actions) { d->initActions = actions; foreach (QAction* const ac, d->initActions) { if (ac) addAction(ac); } } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.cpp b/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.cpp index f4b29f57b2..744035be48 100644 --- a/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.cpp +++ b/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.cpp @@ -1,601 +1,605 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-12-01 * Description : An abstract base class for tiling of markers * * Copyright (C) 2009-2019 by Gilles Caulier * Copyright (C) 2009-2011 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 "abstractmarkertiler.h" // Qt includes #include // Local includes #include "digikam_debug.h" #include "geoifacecommon.h" namespace Digikam { class Q_DECL_HIDDEN AbstractMarkerTiler::Private { public: explicit Private() : rootTile(nullptr), isDirty(true) { } AbstractMarkerTiler::Tile* rootTile; bool isDirty; }; AbstractMarkerTiler::AbstractMarkerTiler(QObject* const parent) : QObject(parent), d(new Private()) { } AbstractMarkerTiler::~AbstractMarkerTiler() { // delete all tiles clear(); delete d; } AbstractMarkerTiler::Tile* AbstractMarkerTiler::rootTile() { if (isDirty()) { regenerateTiles(); } return d->rootTile; } bool AbstractMarkerTiler::isDirty() const { return d->isDirty; } void AbstractMarkerTiler::setDirty(const bool state) { if (state && !d->isDirty) { d->isDirty = true; emit signalTilesOrSelectionChanged(); } else { d->isDirty = state; } } AbstractMarkerTiler::Tile* AbstractMarkerTiler::resetRootTile() { tileDelete(d->rootTile); d->rootTile = tileNew(); return d->rootTile; } void AbstractMarkerTiler::onIndicesClicked(const ClickInfo& clickInfo) { Q_UNUSED(clickInfo) } void AbstractMarkerTiler::onIndicesMoved(const TileIndex::List& tileIndicesList, const GeoCoordinates& targetCoordinates, const QPersistentModelIndex& targetSnapIndex) { Q_UNUSED(tileIndicesList); Q_UNUSED(targetCoordinates); Q_UNUSED(targetSnapIndex); } AbstractMarkerTiler::Tile* AbstractMarkerTiler::tileNew() { return new Tile(); } void AbstractMarkerTiler::tileDelete(AbstractMarkerTiler::Tile* const tile) { tileDeleteChildren(tile); - tileDeleteInternal(tile); + + // NOTE: use dynamic binding as this virtual method can be re-implemented in derived classes. + this->tileDeleteInternal(tile); } void AbstractMarkerTiler::tileDeleteInternal(AbstractMarkerTiler::Tile* const tile) { delete tile; } void AbstractMarkerTiler::tileDeleteChildren(AbstractMarkerTiler::Tile* const tile) { if (!tile) + { return; + } QVector tileChildren = tile->takeChildren(); foreach (Tile* tilec, tileChildren) { tileDelete(tilec); } } void AbstractMarkerTiler::tileDeleteChild(AbstractMarkerTiler::Tile* const parentTile, AbstractMarkerTiler::Tile* const childTile, const int knownLinearIndex) { int tileIndex = knownLinearIndex; if (tileIndex < 0) { tileIndex = parentTile->indexOfChildTile(childTile); } parentTile->clearChild(tileIndex); tileDelete(childTile); } AbstractMarkerTiler::TilerFlags AbstractMarkerTiler::tilerFlags() const { return FlagNull; } void AbstractMarkerTiler::clear() { tileDelete(d->rootTile); d->rootTile = nullptr; } // ------------------------------------------------------------------------- class Q_DECL_HIDDEN AbstractMarkerTiler::NonEmptyIterator::Private { public: explicit Private() : model(nullptr), level(0), startIndex(), endIndex(), currentIndex(), atEnd(false), atStartOfLevel(true) { } AbstractMarkerTiler* model; int level; QList > boundsList; TileIndex startIndex; TileIndex endIndex; TileIndex currentIndex; bool atEnd; bool atStartOfLevel; }; AbstractMarkerTiler::NonEmptyIterator::~NonEmptyIterator() { delete d; } AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model, const int level) : d(new Private()) { d->model = model; GEOIFACE_ASSERT(level <= TileIndex::MaxLevel); d->level = level; TileIndex startIndex; TileIndex endIndex; for (int i = 0 ; i <= level ; ++i) { startIndex.appendLinearIndex(0); endIndex.appendLinearIndex(TileIndex::Tiling * TileIndex::Tiling - 1); } // qCDebug(DIGIKAM_GEOIFACE_LOG) << d->startIndexLinear << d->endIndexLinear; d->boundsList << QPair(startIndex, endIndex); initializeNextBounds(); } AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model, const int level, const TileIndex& startIndex, const TileIndex& endIndex) : d(new Private()) { d->model = model; GEOIFACE_ASSERT(level <= TileIndex::MaxLevel); d->level = level; GEOIFACE_ASSERT(startIndex.level() == level); GEOIFACE_ASSERT(endIndex.level() == level); d->boundsList << QPair(startIndex, endIndex); initializeNextBounds(); } AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model, const int level, const GeoCoordinates::PairList& normalizedMapBounds) : d(new Private()) { d->model = model; GEOIFACE_ASSERT(level <= TileIndex::MaxLevel); d->level = level; // store the coordinates of the bounds as indices: for (int i = 0 ; i < normalizedMapBounds.count() ; ++i) { GeoCoordinates::Pair currentBounds = normalizedMapBounds.at(i); GEOIFACE_ASSERT(currentBounds.first.lat() < currentBounds.second.lat()); GEOIFACE_ASSERT(currentBounds.first.lon() < currentBounds.second.lon()); const TileIndex startIndex = TileIndex::fromCoordinates(currentBounds.first, d->level); const TileIndex endIndex = TileIndex::fromCoordinates(currentBounds.second, d->level); /* qCDebug(DIGIKAM_GEOIFACE_LOG) << currentBounds.first.geoUrl() << startIndex << currentBounds.second.geoUrl() << endIndex; */ d->boundsList << QPair(startIndex, endIndex); } initializeNextBounds(); } bool AbstractMarkerTiler::NonEmptyIterator::initializeNextBounds() { if (d->boundsList.isEmpty()) { d->atEnd = true; return false; } QPair nextBounds = d->boundsList.takeFirst(); d->startIndex = nextBounds.first; d->endIndex = nextBounds.second; GEOIFACE_ASSERT(d->startIndex.level() == d->level); GEOIFACE_ASSERT(d->endIndex.level() == d->level); d->currentIndex = d->startIndex.mid(0, 1); d->atStartOfLevel = true; nextIndex(); return d->atEnd; } TileIndex AbstractMarkerTiler::NonEmptyIterator::nextIndex() { if (d->atEnd) { return d->currentIndex; } Q_FOREVER { const int currentLevel = d->currentIndex.level(); /* qCDebug(DIGIKAM_GEOIFACE_LOG) << d->level << currentLevel << d->atStartOfLevel << d->currentIndex; */ if (d->atStartOfLevel) { d->atStartOfLevel = false; } else { // go to the next tile at the current level, if that is possible: // determine the limits in the current tile: int limitLatBL = 0; int limitLonBL = 0; int limitLatTR = TileIndex::Tiling-1; int limitLonTR = TileIndex::Tiling-1; int compareLevel = currentLevel - 1; // check limit on the left side: bool onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLat(i) == d->startIndex.indexLat(i); } if (onLimit) { limitLatBL = d->startIndex.indexLat(currentLevel); } // check limit on the bottom side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLon(i) == d->startIndex.indexLon(i); } if (onLimit) { limitLonBL = d->startIndex.indexLon(currentLevel); } // check limit on the right side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLat(i) == d->endIndex.indexLat(i); } if (onLimit) { limitLatTR = d->endIndex.indexLat(currentLevel); } // check limit on the top side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLon(i) == d->endIndex.indexLon(i); } if (onLimit) { limitLonTR = d->endIndex.indexLon(currentLevel); } GEOIFACE_ASSERT(limitLatBL <= limitLatTR); GEOIFACE_ASSERT(limitLonBL <= limitLonTR); /* qCDebug(DIGIKAM_GEOIFACE_LOG) << limitLatBL << limitLonBL << limitLatTR << limitLonTR << compareLevel << currentLevel; */ int currentLat = d->currentIndex.indexLat(d->currentIndex.level()); int currentLon = d->currentIndex.indexLon(d->currentIndex.level()); currentLon++; if (currentLon>limitLonTR) { currentLon = limitLonBL; currentLat++; if (currentLat>limitLatTR) { if (currentLevel == 0) { // we are at the end! // are there other bounds to iterate over? initializeNextBounds(); // initializeNextBounds() call nextIndex which updates d->currentIndexLinear, if possible: return d->currentIndex; } // we need to go one level up, trim the indices: d->currentIndex.oneUp(); continue; } } // save the new position: d->currentIndex.oneUp(); d->currentIndex.appendLatLonIndex(currentLat, currentLon); } // is the tile empty? if (d->model->getTileMarkerCount(d->currentIndex)==0) { continue; } // are we at the target level? if (currentLevel == d->level) { // yes, return the current index: return d->currentIndex; } // go one level down: int compareLevel = currentLevel; // determine the limits for the next level: - int limitLatBL = 0; - int limitLonBL = 0; - int limitLatTR = TileIndex::Tiling-1; - int limitLonTR = TileIndex::Tiling-1; + int limitLatBL = 0; + int limitLonBL = 0; + int limitLatTR = TileIndex::Tiling-1; + int limitLonTR = TileIndex::Tiling-1; // check limit on the left side: - bool onLimit = true; + bool onLimit = true; for (int i = 0 ; onLimit&&(i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLat(i)==d->startIndex.indexLat(i); } if (onLimit) { limitLatBL = d->startIndex.indexLat(currentLevel+1); } // check limit on the bottom side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLon(i)==d->startIndex.indexLon(i); } if (onLimit) { limitLonBL = d->startIndex.indexLon(currentLevel+1); } // check limit on the right side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLat(i) == d->endIndex.indexLat(i); } if (onLimit) { limitLatTR = d->endIndex.indexLat(currentLevel+1); } // check limit on the top side: onLimit = true; for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) { onLimit = d->currentIndex.indexLon(i) == d->endIndex.indexLon(i); } if (onLimit) { limitLonTR = d->endIndex.indexLon(currentLevel+1); } GEOIFACE_ASSERT(limitLatBL <= limitLatTR); GEOIFACE_ASSERT(limitLonBL <= limitLonTR); // go one level down: d->currentIndex.appendLatLonIndex(limitLatBL, limitLonBL); d->atStartOfLevel = true; } } TileIndex AbstractMarkerTiler::NonEmptyIterator::currentIndex() const { return d->currentIndex; } bool AbstractMarkerTiler::NonEmptyIterator::atEnd() const { return d->atEnd; } AbstractMarkerTiler* AbstractMarkerTiler::NonEmptyIterator::model() const { return d->model; } // ------------------------------------------------------------------------- AbstractMarkerTiler::Tile::Tile() : children() { } AbstractMarkerTiler::Tile::~Tile() { } int AbstractMarkerTiler::Tile::maxChildCount() { return TileIndex::Tiling * TileIndex::Tiling; } AbstractMarkerTiler::Tile* AbstractMarkerTiler::Tile::getChild(const int linearIndex) { if (children.isEmpty()) { return nullptr; } return children.at(linearIndex); } void AbstractMarkerTiler::Tile::addChild(const int linearIndex, Tile* const tilePointer) { - if ((tilePointer==nullptr) && children.isEmpty()) + if ((tilePointer == nullptr) && children.isEmpty()) { return; } prepareForChildren(); children[linearIndex] = tilePointer; } void AbstractMarkerTiler::Tile::clearChild(const int linearIndex) { if (children.isEmpty()) { return; } children[linearIndex] = 0; } int AbstractMarkerTiler::Tile::indexOfChildTile(Tile* const tile) { return children.indexOf(tile); } bool AbstractMarkerTiler::Tile::childrenEmpty() const { return children.isEmpty(); } QVector AbstractMarkerTiler::Tile::takeChildren() { QVector childrenCopy = children; children.clear(); return childrenCopy; } void AbstractMarkerTiler::Tile::prepareForChildren() { if (!children.isEmpty()) { return; } children = QVector(maxChildCount(), 0); } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.h b/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.h index a15f1cd953..6a5dd57882 100644 --- a/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.h +++ b/core/utilities/geolocation/geoiface/tiles/abstractmarkertiler.h @@ -1,211 +1,211 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-12-01 * Description : An abstract base class for tiling of markers * * Copyright (C) 2009-2019 by Gilles Caulier * Copyright (C) 2009-2011 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. * * ============================================================ */ #ifndef DIGIKAM_ABSTRACT_MARKER_TILER_H #define DIGIKAM_ABSTRACT_MARKER_TILER_H // Qt includes #include #include #include // Local includes #include "tileindex.h" #include "geoifacetypes.h" #include "digikam_export.h" #include "geogroupstate.h" namespace Digikam { class DIGIKAM_EXPORT AbstractMarkerTiler : public QObject { Q_OBJECT public: enum TilerFlag { FlagNull = 0, FlagMovable = 1 }; Q_DECLARE_FLAGS(TilerFlags, TilerFlag) public: class ClickInfo { public: TileIndex::List tileIndicesList; QVariant representativeIndex; GeoGroupState groupSelectionState; GeoMouseModes currentMouseMode; }; public: class Tile { public: explicit Tile(); /** * NOTE: Tile is only deleted by AbstractMarkerTiler::tileDelete. * All subclasses of AbstractMarkerTiler have to reimplement tileDelete * to delete their Tile subclasses. * This was done in order not to have any virtual functions * in Tile and its subclasses in order to save memory, since there * can be a lot of tiles in a MarkerTiler. */ ~Tile(); Tile* getChild(const int linearIndex); void addChild(const int linearIndex, Tile* const tilePointer); /** * @brief Sets the pointer to a child tile to zero, but you have to delete the tile by yourself! */ void clearChild(const int linearIndex); int indexOfChildTile(Tile* const tile); bool childrenEmpty() const; /** * @brief Take away the list of children, only to be used for deleting them. * * @todo Make this function protected. * */ QVector takeChildren(); static int maxChildCount(); private: void prepareForChildren(); private: QVector children; }; public: class NonEmptyIterator { public: NonEmptyIterator(AbstractMarkerTiler* const model, const int level); NonEmptyIterator(AbstractMarkerTiler* const model, const int level, const TileIndex& startIndex, const TileIndex& endIndex); NonEmptyIterator(AbstractMarkerTiler* const model, const int level, const GeoCoordinates::PairList& normalizedMapBounds); ~NonEmptyIterator(); bool atEnd() const; TileIndex nextIndex(); TileIndex currentIndex() const; AbstractMarkerTiler* model() const; private: bool initializeNextBounds(); private: // Hidden copy constructor and assignment operator. NonEmptyIterator(const NonEmptyIterator&); NonEmptyIterator& operator=(const NonEmptyIterator&); class Private; Private* const d; }; public: explicit AbstractMarkerTiler(QObject* const parent = nullptr); virtual ~AbstractMarkerTiler(); void tileDeleteChildren(Tile* const tile); void tileDelete(Tile* const tile); void tileDeleteChild(Tile* const parentTile, Tile* const childTile, const int knownLinearIndex = -1); // these have to be implemented virtual TilerFlags tilerFlags() const; virtual Tile* tileNew(); virtual void tileDeleteInternal(Tile* const tile); virtual void prepareTiles(const GeoCoordinates& upperLeft, const GeoCoordinates& lowerRight, int level) = 0; - virtual void regenerateTiles() = 0; - virtual Tile* getTile(const TileIndex& tileIndex, const bool stopIfEmpty = false) = 0; - virtual int getTileMarkerCount(const TileIndex& tileIndex) = 0; - virtual int getTileSelectedCount(const TileIndex& tileIndex) = 0; + virtual void regenerateTiles() = 0; + virtual Tile* getTile(const TileIndex& tileIndex, const bool stopIfEmpty = false) = 0; + virtual int getTileMarkerCount(const TileIndex& tileIndex) = 0; + virtual int getTileSelectedCount(const TileIndex& tileIndex) = 0; // these should be implemented for thumbnail handling - virtual QVariant getTileRepresentativeMarker(const TileIndex& tileIndex, const int sortKey) = 0; - virtual QVariant bestRepresentativeIndexFromList(const QList& indices, const int sortKey) = 0; - virtual QPixmap pixmapFromRepresentativeIndex(const QVariant& index, const QSize& size) = 0; - virtual bool indicesEqual(const QVariant& a, const QVariant& b) const = 0; - virtual GeoGroupState getTileGroupState(const TileIndex& tileIndex) = 0; - virtual GeoGroupState getGlobalGroupState() = 0; + virtual QVariant getTileRepresentativeMarker(const TileIndex& tileIndex, const int sortKey) = 0; + virtual QVariant bestRepresentativeIndexFromList(const QList& indices, const int sortKey) = 0; + virtual QPixmap pixmapFromRepresentativeIndex(const QVariant& index, const QSize& size) = 0; + virtual bool indicesEqual(const QVariant& a, const QVariant& b) const = 0; + virtual GeoGroupState getTileGroupState(const TileIndex& tileIndex) = 0; + virtual GeoGroupState getGlobalGroupState() = 0; // these can be implemented if you want to react to actions in geolocation interface virtual void onIndicesClicked(const ClickInfo& clickInfo); virtual void onIndicesMoved(const TileIndex::List& tileIndicesList, const GeoCoordinates& targetCoordinates, const QPersistentModelIndex& targetSnapIndex); - virtual void setActive(const bool state) = 0; + virtual void setActive(const bool state) = 0; Tile* rootTile(); bool indicesEqual(const QIntList& a, const QIntList& b, const int upToLevel) const; bool isDirty() const; void setDirty(const bool state = true); Tile* resetRootTile(); Q_SIGNALS: void signalTilesOrSelectionChanged(); void signalThumbnailAvailableForIndex(const QVariant& index, const QPixmap& pixmap); protected: /** * @brief Only used to safely delete all tiles in the desctructor */ void clear(); private: class Private; Private* const d; }; } // namespace Digikam Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::AbstractMarkerTiler::TilerFlags) #endif // DIGIKAM_ABSTRACT_MARKER_TILER_H diff --git a/core/utilities/imageeditor/editor/editortool.cpp b/core/utilities/imageeditor/editor/editortool.cpp index ca6d0600b4..3176d263e6 100644 --- a/core/utilities/imageeditor/editor/editortool.cpp +++ b/core/utilities/imageeditor/editor/editortool.cpp @@ -1,720 +1,721 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-08-20 * Description : editor tool template class. * * Copyright (C) 2008-2019 by Gilles Caulier * * 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 "editortool.h" // Qt includes #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "dimgthreadedfilter.h" #include "dimgthreadedanalyser.h" #include "imageguidewidget.h" #include "imageregionwidget.h" #include "histogramwidget.h" #include "histogrambox.h" #include "editortoolsettings.h" #include "editortooliface.h" namespace Digikam { class Q_DECL_HIDDEN EditorTool::Private { public: explicit Private() : initPreview(false), version(0), view(nullptr), timer(nullptr), settings(nullptr), category(FilterAction::ReproducibleFilter), plugin(nullptr) { } static const QString configGroupName; static const QString configRestoreSettingsEntry; bool initPreview; QString helpAnchor; QString name; int version; QWidget* view; QIcon icon; QTimer* timer; EditorToolSettings* settings; FilterAction::Category category; DPluginEditor* plugin; }; const QString EditorTool::Private::configGroupName(QLatin1String("ImageViewer Settings")); const QString EditorTool::Private::configRestoreSettingsEntry(QLatin1String("RestoreToolSettings")); // -------------------------------------------------------- EditorTool::EditorTool(QObject* const parent) : QObject(parent), d(new Private) { d->timer = new QTimer(this); - // cppcheck-suppress virtualCallInConstructor - connect(d->timer, SIGNAL(timeout()), - this, SLOT(slotPreview())); + // --- NOTE: use dynamic binding as slotPreview() is a virtual method which can be re-implemented in derived classes. + + connect(d->timer, &QTimer::timeout, + this, &EditorTool::slotPreview); } EditorTool::~EditorTool() { delete d->settings; delete d->view; delete d; } void EditorTool::setPlugin(DPluginEditor* const plugin) { d->plugin = plugin; setToolName(d->plugin->name()); setToolIcon(d->plugin->icon()); d->settings->setTool(this); } DPluginEditor* EditorTool::plugin() const { return d->plugin; } void EditorTool::init() { QTimer::singleShot(0, this, SLOT(slotInit())); } void EditorTool::setInitPreview(bool b) { d->initPreview = b; } QIcon EditorTool::toolIcon() const { return d->icon; } void EditorTool::setToolIcon(const QIcon& icon) { d->icon = icon; } QString EditorTool::toolName() const { return d->name; } void EditorTool::setToolName(const QString& name) { d->name = name; } int EditorTool::toolVersion() const { return d->version; } void EditorTool::setToolVersion(const int version) { d->version = version; } FilterAction::Category EditorTool::toolCategory() const { return d->category; } void EditorTool::setToolCategory(const FilterAction::Category category) { d->category = category; } void EditorTool::setPreviewModeMask(int mask) { EditorToolIface::editorToolIface()->setPreviewModeMask(mask); } QWidget* EditorTool::toolView() const { return d->view; } void EditorTool::setToolView(QWidget* const view) { d->view = view; // Will be unblocked in slotInit() // This will prevent resize event signals emit during tool init. d->view->blockSignals(true); ImageGuideWidget* const wgt = dynamic_cast(d->view); if (wgt) { connect(d->view, SIGNAL(spotPositionChangedFromOriginal(Digikam::DColor,QPoint)), this, SLOT(slotUpdateSpotInfo(Digikam::DColor,QPoint))); connect(d->view, SIGNAL(spotPositionChangedFromTarget(Digikam::DColor,QPoint)), this, SLOT(slotUpdateSpotInfo(Digikam::DColor,QPoint))); } } EditorToolSettings* EditorTool::toolSettings() const { return d->settings; } void EditorTool::setToolSettings(EditorToolSettings* const settings) { d->settings = settings; d->settings->setTool(this); connect(d->settings, SIGNAL(signalOkClicked()), this, SLOT(slotOk())); connect(d->settings, SIGNAL(signalCancelClicked()), this, SLOT(slotCancel())); connect(d->settings, SIGNAL(signalDefaultClicked()), this, SLOT(slotResetSettings())); connect(d->settings, SIGNAL(signalSaveAsClicked()), this, SLOT(slotSaveAsSettings())); connect(d->settings, SIGNAL(signalLoadClicked()), this, SLOT(slotLoadSettings())); connect(d->settings, SIGNAL(signalTryClicked()), this, SLOT(slotPreview())); connect(d->settings, SIGNAL(signalChannelChanged()), this, SLOT(slotChannelChanged())); connect(d->settings, SIGNAL(signalScaleChanged()), this, SLOT(slotScaleChanged())); // Will be unblocked in slotInit() // This will prevent signals emit during tool init. d->settings->blockSignals(true); } void EditorTool::slotInit() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); // We always have to call readSettings(), some tools need it. if (group.readEntry(d->configRestoreSettingsEntry, true)) { readSettings(); } else { writeSettings(); readSettings(); } // Unlock signals from preview and settings widgets when init is done. d->view->blockSignals(false); d->settings->blockSignals(false); if (d->initPreview) slotTimer(); } void EditorTool::setToolHelp(const QString& anchor) { d->helpAnchor = anchor; // TODO: use this anchor with editor Help menu } QString EditorTool::toolHelp() const { if (d->helpAnchor.isEmpty()) { return (objectName() + QLatin1String(".anchor")); } return d->helpAnchor; } void EditorTool::setBusy(bool state) { d->settings->setBusy(state); } void EditorTool::readSettings() { d->settings->readSettings(); } void EditorTool::writeSettings() { d->settings->writeSettings(); } void EditorTool::slotResetSettings() { d->settings->resetSettings(); } void EditorTool::slotTimer() { d->timer->setSingleShot(true); d->timer->start(500); } void EditorTool::slotOk() { writeSettings(); finalRendering(); emit okClicked(); } void EditorTool::slotCancel() { writeSettings(); emit cancelClicked(); } void EditorTool::slotCloseTool() { slotCancel(); } void EditorTool::slotApplyTool() { slotOk(); } void EditorTool::slotPreviewModeChanged() { slotPreview(); } void EditorTool::setBackgroundColor(const QColor& bg) { ImageGuideWidget* const view = dynamic_cast(d->view); QPalette palette; if (view) { palette.setColor(view->backgroundRole(), bg); view->setPalette(palette); } ImageRegionWidget* const view2 = dynamic_cast(d->view); if (view2) { palette.setColor(view2->backgroundRole(), bg); view2->setPalette(palette); } } void EditorTool::ICCSettingsChanged() { ImageGuideWidget* const view = dynamic_cast(d->view); if (view) { view->ICCSettingsChanged(); } ImageRegionWidget* const view2 = dynamic_cast(d->view); if (view2) { view2->ICCSettingsChanged(); } } void EditorTool::exposureSettingsChanged() { ImageGuideWidget* const view = dynamic_cast(d->view); if (view) { view->exposureSettingsChanged(); } ImageRegionWidget* const view2 = dynamic_cast(d->view); if (view2) { view2->exposureSettingsChanged(); } } void EditorTool::setToolInfoMessage(const QString& txt) { EditorToolIface::editorToolIface()->setToolInfoMessage(txt); } void EditorTool::slotUpdateSpotInfo(const DColor& col, const QPoint& point) { DColor color = col; setToolInfoMessage(i18n("(%1,%2) RGBA:%3,%4,%5,%6", point.x(), point.y(), color.red(), color.green(), color.blue(), color.alpha())); } // ---------------------------------------------------------------- class Q_DECL_HIDDEN EditorToolThreaded::Private { public: explicit Private() : delFilter(true), currentRenderingMode(EditorToolThreaded::NoneRendering), threadedFilter(nullptr), threadedAnalyser(nullptr) { } bool delFilter; EditorToolThreaded::RenderingMode currentRenderingMode; QString progressMess; DImgThreadedFilter* threadedFilter; DImgThreadedAnalyser* threadedAnalyser; }; EditorToolThreaded::EditorToolThreaded(QObject* const parent) : EditorTool(parent), d(new Private) { } EditorToolThreaded::~EditorToolThreaded() { delete d->threadedFilter; delete d; } EditorToolThreaded::RenderingMode EditorToolThreaded::renderingMode() const { return d->currentRenderingMode; } void EditorToolThreaded::setProgressMessage(const QString& mess) { d->progressMess = mess; } DImgThreadedFilter* EditorToolThreaded::filter() const { return d->threadedFilter; } void EditorToolThreaded::slotInit() { EditorTool::slotInit(); QWidget* const view = toolView(); if (dynamic_cast(view)) { connect(view, SIGNAL(signalResized()), this, SLOT(slotResized())); } if (dynamic_cast(view)) { connect(view, SIGNAL(signalOriginalClipFocusChanged()), this, SLOT(slotTimer())); } } void EditorToolThreaded::setFilter(DImgThreadedFilter* const filter) { delete d->threadedFilter; d->threadedFilter = filter; connect(d->threadedFilter, SIGNAL(started()), this, SLOT(slotFilterStarted())); connect(d->threadedFilter, SIGNAL(finished(bool)), this, SLOT(slotFilterFinished(bool))); connect(d->threadedFilter, SIGNAL(progress(int)), this, SLOT(slotProgress(int))); d->threadedFilter->startFilter(); } DImgThreadedAnalyser* EditorToolThreaded::analyser() const { return d->threadedAnalyser; } void EditorToolThreaded::setAnalyser(DImgThreadedAnalyser* const analyser) { qCDebug(DIGIKAM_GENERAL_LOG) << "Analys " << toolName() << " started..."; toolSettings()->enableButton(EditorToolSettings::Ok, false); toolSettings()->enableButton(EditorToolSettings::SaveAs, false); toolSettings()->enableButton(EditorToolSettings::Load, false); toolSettings()->enableButton(EditorToolSettings::Default, false); toolSettings()->enableButton(EditorToolSettings::Try, false); toolView()->setEnabled(false); EditorToolIface::editorToolIface()->setToolStartProgress(d->progressMess.isEmpty() ? toolName() : d->progressMess); qApp->setOverrideCursor(Qt::WaitCursor); delete d->threadedAnalyser; d->threadedAnalyser = analyser; connect(d->threadedAnalyser, SIGNAL(started()), this, SLOT(slotAnalyserStarted())); connect(d->threadedAnalyser, SIGNAL(finished(bool)), this, SLOT(slotAnalyserFinished(bool))); connect(d->threadedAnalyser, SIGNAL(progress(int)), this, SLOT(slotProgress(int))); d->threadedAnalyser->startFilter(); } void EditorToolThreaded::slotResized() { if (d->currentRenderingMode == EditorToolThreaded::FinalRendering) { toolView()->update(); return; } else if (d->currentRenderingMode == EditorToolThreaded::PreviewRendering) { if (filter()) { filter()->cancelFilter(); } } QTimer::singleShot(0, this, SLOT(slotPreview())); } void EditorToolThreaded::slotAbort() { d->currentRenderingMode = EditorToolThreaded::NoneRendering; if (analyser()) { analyser()->cancelFilter(); } if (filter()) { filter()->cancelFilter(); } EditorToolIface::editorToolIface()->setToolStopProgress(); toolSettings()->enableButton(EditorToolSettings::Ok, true); toolSettings()->enableButton(EditorToolSettings::Load, true); toolSettings()->enableButton(EditorToolSettings::SaveAs, true); toolSettings()->enableButton(EditorToolSettings::Try, true); toolSettings()->enableButton(EditorToolSettings::Default, true); toolView()->setEnabled(true); qApp->restoreOverrideCursor(); renderingFinished(); } void EditorToolThreaded::slotFilterStarted() { } void EditorToolThreaded::slotFilterFinished(bool success) { if (success) // Computation Completed ! { switch (d->currentRenderingMode) { case EditorToolThreaded::PreviewRendering: { qCDebug(DIGIKAM_GENERAL_LOG) << "Preview " << toolName() << " completed..."; setPreviewImage(); slotAbort(); break; } case EditorToolThreaded::FinalRendering: { qCDebug(DIGIKAM_GENERAL_LOG) << "Final" << toolName() << " completed..."; setFinalImage(); EditorToolIface::editorToolIface()->setToolStopProgress(); qApp->restoreOverrideCursor(); emit okClicked(); break; } default: break; } } else // Computation Failed ! { switch (d->currentRenderingMode) { case EditorToolThreaded::PreviewRendering: { qCDebug(DIGIKAM_GENERAL_LOG) << "Preview " << toolName() << " failed..."; slotAbort(); break; } case EditorToolThreaded::FinalRendering: default: break; } } } void EditorToolThreaded::slotProgress(int progress) { EditorToolIface::editorToolIface()->setToolProgress(progress); } void EditorToolThreaded::slotAnalyserStarted() { } void EditorToolThreaded::slotAnalyserFinished(bool success) { if (success) { qCDebug(DIGIKAM_GENERAL_LOG) << "Analys " << toolName() << " completed..."; analyserCompleted(); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "Analys " << toolName() << " failed..."; slotAbort(); } } void EditorToolThreaded::slotOk() { // Computation already in process. if (d->currentRenderingMode != EditorToolThreaded::PreviewRendering) { // See bug #305916 : cancel preview before. slotAbort(); } writeSettings(); d->currentRenderingMode = EditorToolThreaded::FinalRendering; qCDebug(DIGIKAM_GENERAL_LOG) << "Final " << toolName() << " started..."; toolSettings()->enableButton(EditorToolSettings::Ok, false); toolSettings()->enableButton(EditorToolSettings::SaveAs, false); toolSettings()->enableButton(EditorToolSettings::Load, false); toolSettings()->enableButton(EditorToolSettings::Default, false); toolSettings()->enableButton(EditorToolSettings::Try, false); toolView()->setEnabled(false); EditorToolIface::editorToolIface()->setToolStartProgress(d->progressMess.isEmpty() ? toolName() : d->progressMess); qApp->setOverrideCursor(Qt::WaitCursor); if (d->delFilter && d->threadedFilter) { delete d->threadedFilter; d->threadedFilter = nullptr; } prepareFinal(); } void EditorToolThreaded::slotPreview() { // Computation already in process. if (d->currentRenderingMode != EditorToolThreaded::NoneRendering) { return; } d->currentRenderingMode = EditorToolThreaded::PreviewRendering; qCDebug(DIGIKAM_GENERAL_LOG) << "Preview " << toolName() << " started..."; toolSettings()->enableButton(EditorToolSettings::Ok, false); toolSettings()->enableButton(EditorToolSettings::SaveAs, false); toolSettings()->enableButton(EditorToolSettings::Load, false); toolSettings()->enableButton(EditorToolSettings::Default, false); toolSettings()->enableButton(EditorToolSettings::Try, false); toolView()->setEnabled(false); EditorToolIface::editorToolIface()->setToolStartProgress(d->progressMess.isEmpty() ? toolName() : d->progressMess); qApp->setOverrideCursor(Qt::WaitCursor); if (d->delFilter && d->threadedFilter) { delete d->threadedFilter; d->threadedFilter = nullptr; } preparePreview(); } void EditorToolThreaded::slotCancel() { writeSettings(); slotAbort(); emit cancelClicked(); } void EditorToolThreaded::deleteFilterInstance(bool b) { d->delFilter = b; } } // namespace Digikam diff --git a/core/utilities/maintenance/maintenancetool.cpp b/core/utilities/maintenance/maintenancetool.cpp index 7743e1309a..875bf3c7cf 100644 --- a/core/utilities/maintenance/maintenancetool.cpp +++ b/core/utilities/maintenance/maintenancetool.cpp @@ -1,110 +1,111 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-02-02 * Description : maintenance tool * * Copyright (C) 2012-2019 by Gilles Caulier * Copyright (C) 2012 by Andi Clemens * * 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 "maintenancetool.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "dnotificationwrapper.h" namespace Digikam { class Q_DECL_HIDDEN MaintenanceTool::Private { public: explicit Private() { notification = true; } bool notification; QTime duration; }; MaintenanceTool::MaintenanceTool(const QString& id, ProgressItem* const parent) : ProgressItem(parent, id, QString(), QString(), true, true), d(new Private) { - // cppcheck-suppress virtualCallInConstructor - connect(this, SIGNAL(progressItemCanceled(QString)), - this, SLOT(slotCancel())); + // --- NOTE: use dynamic binding as slotCancel() is a virtual method which can be re-implemented in derived classes. + + connect(this, static_cast(&ProgressItem::progressItemCanceled), + this, &MaintenanceTool::slotCancel); } MaintenanceTool::~MaintenanceTool() { delete d; } void MaintenanceTool::setNotificationEnabled(bool b) { d->notification = b; } void MaintenanceTool::start() { // We delay start to be sure that eventloop connect signals and slots in top level. QTimer::singleShot(0, this, SLOT(slotStart())); } void MaintenanceTool::slotStart() { d->duration.start(); } void MaintenanceTool::slotDone() { QTime t = QTime::fromMSecsSinceStartOfDay(d->duration.elapsed()); if (d->notification) { // Pop-up a message to bring user when all is done. DNotificationWrapper(id(), i18n("Process is done.\nDuration: %1", t.toString()), qApp->activeWindow(), label()); } emit signalComplete(); setComplete(); } void MaintenanceTool::slotCancel() { setComplete(); } } // namespace Digikam diff --git a/core/utilities/setup/metadata/namespaceeditdlg.cpp b/core/utilities/setup/metadata/namespaceeditdlg.cpp index 5edd1dc3e2..dcef6cb84a 100644 --- a/core/utilities/setup/metadata/namespaceeditdlg.cpp +++ b/core/utilities/setup/metadata/namespaceeditdlg.cpp @@ -1,688 +1,688 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2015-07-03 * Description : dialog to edit and create digiKam xmp namespaces * * Copyright (C) 2015 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 "namespaceeditdlg.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dxmlguiwindow.h" #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN NamespaceEditDlg::Private { public: explicit Private() { buttons = nullptr; create = 0; topLabel = nullptr; logo = nullptr; gridLayout = nullptr; page = nullptr; // NamespaceEntry variables subspaceCombo = nullptr; specialOptsCombo = nullptr; altSpecialOptsCombo = nullptr; namespaceName = nullptr; alternativeName = nullptr; nameSpaceSeparator = nullptr; isPath = nullptr; ratingMappings = nullptr; zeroStars = nullptr; oneStar = nullptr; twoStars = nullptr; threeStars = nullptr; fourStars = nullptr; fiveStars = nullptr; // Labels tagTipLabel = nullptr; ratingTipLabel = nullptr; commentTipLabel = nullptr; subspaceLabel = nullptr; titleLabel = nullptr; specialOptsLabel = nullptr; alternativeNameLabel = nullptr; altspecialOptsLabel = nullptr; isTagLabel = nullptr; separatorLabel = nullptr; tipLabel2 = nullptr; nsType = NamespaceEntry::TAGS; } QDialogButtonBox* buttons; bool create; QLabel* topLabel; QLabel* logo; QGridLayout* gridLayout; QWidget* page; // NamespaceEntry variables QComboBox* subspaceCombo; QComboBox* specialOptsCombo; QComboBox* altSpecialOptsCombo; QLineEdit* namespaceName; QLineEdit* alternativeName; QLineEdit* nameSpaceSeparator; QCheckBox* isPath; QGroupBox* ratingMappings; QSpinBox* zeroStars; QSpinBox* oneStar; QSpinBox* twoStars; QSpinBox* threeStars; QSpinBox* fourStars; QSpinBox* fiveStars; // Labels QLabel* tagTipLabel; QLabel* ratingTipLabel; QLabel* commentTipLabel; QLabel* subspaceLabel; QLabel* titleLabel; QLabel* specialOptsLabel; QLabel* alternativeNameLabel; QLabel* altspecialOptsLabel; QLabel* isTagLabel; QLabel* separatorLabel; QLabel* tipLabel2; NamespaceEntry::NamespaceType nsType; }; NamespaceEditDlg::NamespaceEditDlg(bool create, NamespaceEntry& entry, QWidget* const parent) : QDialog(parent), d(new Private()) { setModal(true); d->buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); d->buttons->button(QDialogButtonBox::Ok)->setDefault(true); if (create) { setWindowTitle(i18n("New Xmp Namespace")); } else { setWindowTitle(i18n("Edit Xmp Namespace")); } d->create = create; d->nsType = entry.nsType; setupTagGui(entry); - // -------------------------------------------------------- + // --- NOTE: use dynamic binding as slots below are virtual method which can be re-implemented in derived classes. - connect(d->buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), - this, SLOT(accept())); + connect(d->buttons->button(QDialogButtonBox::Ok), &QPushButton::clicked, + this, &NamespaceEditDlg::accept); - connect(d->buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), - this, SLOT(reject())); + connect(d->buttons->button(QDialogButtonBox::Cancel), &QPushButton::clicked, + this, &NamespaceEditDlg::reject); - connect(d->buttons->button(QDialogButtonBox::Help), SIGNAL(clicked()), - this, SLOT(slotHelp())); + connect(d->buttons->button(QDialogButtonBox::Help), &QPushButton::clicked, + this, &NamespaceEditDlg::slotHelp); // -------------------------------------------------------- if (!d->create) { populateFields(entry); } setType(entry.nsType); if (entry.isDefault) { makeReadOnly(); } qCDebug(DIGIKAM_GENERAL_LOG) << "Entry type" << entry.nsType << "subspace" << entry.subspace << entry.isDefault; adjustSize(); } NamespaceEditDlg::~NamespaceEditDlg() { delete d; } bool NamespaceEditDlg::create(QWidget* const parent, NamespaceEntry& entry) { QPointer dlg = new NamespaceEditDlg(true,entry,parent); qCDebug(DIGIKAM_GENERAL_LOG) << "Name before save: " << entry.namespaceName; bool valRet = dlg->exec(); if (valRet == QDialog::Accepted) { dlg->saveData(entry); } qCDebug(DIGIKAM_GENERAL_LOG) << "Name after save: " << entry.namespaceName; delete dlg; return valRet; } bool NamespaceEditDlg::edit(QWidget* const parent, NamespaceEntry& entry) { QPointer dlg = new NamespaceEditDlg(false, entry, parent); qCDebug(DIGIKAM_GENERAL_LOG) << "Name before save: " << entry.namespaceName; bool valRet = dlg->exec(); if (valRet == QDialog::Accepted && !entry.isDefault) { dlg->saveData(entry); } qCDebug(DIGIKAM_GENERAL_LOG) << "Name before save: " << entry.namespaceName; delete dlg; return valRet; } void NamespaceEditDlg::setupTagGui(NamespaceEntry& entry) { d->page = new QWidget(this); d->gridLayout = new QGridLayout(d->page); d->logo = new QLabel(d->page); d->logo->setPixmap(QIcon::fromTheme(QLatin1String("digikam")).pixmap(QSize(48,48))); d->topLabel = new QLabel(d->page); d->topLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); d->topLabel->setWordWrap(false); d->topLabel->setText(i18n("Add metadata namespace")); d->subspaceCombo = new QComboBox(this); d->subspaceLabel = new QLabel(d->page); d->subspaceLabel->setText(i18n("Metadata Subspace")); d->subspaceCombo->addItem(QLatin1String("EXIF"), (int)NamespaceEntry::EXIF); d->subspaceCombo->addItem(QLatin1String("IPTC"), (int)NamespaceEntry::IPTC); d->subspaceCombo->addItem(QLatin1String("XMP"), (int)NamespaceEntry::XMP); d->subspaceCombo->setCurrentIndex((int)entry.subspace); qCDebug(DIGIKAM_GENERAL_LOG) << "Entry subspace" << (int)entry.subspace; // -------------------Tag Elements--------------------------------- d->titleLabel = new QLabel(d->page); d->titleLabel->setText(i18n("Name:")); d->namespaceName = new QLineEdit(this); //----------------- Tip Labels -------------------------------------- d->tagTipLabel = new QLabel(d->page); d->tagTipLabel->setTextFormat(Qt::RichText); d->tagTipLabel->setWordWrap(true); d->tagTipLabel->setText(i18n("

To create new namespaces, you need to specify parameters:

" "

  • Namespace name with dots.
    " "Ex.: \"Xmp.digiKam.TagsList\"
  • " "
  • Separator parameter, used by tag paths
    " "Ex.: \"City/Paris\" or \"City|Paris\"
  • " "
  • Specify if only keyword or the whole path must be written.

" )); d->ratingTipLabel = new QLabel(d->page); d->ratingTipLabel->setTextFormat(Qt::RichText); d->ratingTipLabel->setWordWrap(true); d->ratingTipLabel->setText(i18n("

To create new rating namespaces, you need to specify parameters:

" "

  • Namespace name with dots.
    " "Ex.: \"Xmp.xmp.Rating\"
  • " "
  • Rating mappings, if namespace need other values than 0-5
    " "Ex.: Microsoft uses 0 1 25 50 75 99
  • " "
  • Select the correct namespace option from list.

" )); d->commentTipLabel = new QLabel(d->page); d->commentTipLabel->setTextFormat(Qt::RichText); d->commentTipLabel->setWordWrap(true); d->commentTipLabel->setText(i18n("

To create new comment namespaces, you need to specify parameters:

" "

  • Namespace name with dots.
    " "Ex.: \"Xmp.xmp.Rating\"
  • " "
  • Select the correct namespace option from list.

" )); // ------------------------------------------------------- d->specialOptsLabel = new QLabel(d->page); d->specialOptsLabel->setText(i18n("Special Options")); d->specialOptsCombo = new QComboBox(d->page); d->specialOptsCombo->addItem(QLatin1String("NO_OPTS"), (int)NamespaceEntry::NO_OPTS); if (entry.nsType == NamespaceEntry::COMMENT) { d->specialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANG"), NamespaceEntry::COMMENT_ALTLANG); d->specialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANGLIST"), NamespaceEntry::COMMENT_ATLLANGLIST); d->specialOptsCombo->addItem(QLatin1String("COMMENT_XMP"), NamespaceEntry::COMMENT_XMP); d->specialOptsCombo->addItem(QLatin1String("COMMENT_JPEG"), NamespaceEntry::COMMENT_JPEG); } if (entry.nsType == NamespaceEntry::TAGS) { d->specialOptsCombo->addItem(QLatin1String("TAG_XMPBAG"), NamespaceEntry::TAG_XMPBAG); d->specialOptsCombo->addItem(QLatin1String("TAG_XMPSEQ"), NamespaceEntry::TAG_XMPSEQ); d->specialOptsCombo->addItem(QLatin1String("TAG_ACDSEE"), NamespaceEntry::TAG_ACDSEE); } d->alternativeNameLabel = new QLabel(d->page); d->alternativeNameLabel->setText(i18n("Alternative name")); d->alternativeName = new QLineEdit(d->page); d->altspecialOptsLabel = new QLabel(d->page); d->altspecialOptsLabel->setText(i18n("Alternative special options")); d->altSpecialOptsCombo = new QComboBox(d->page); d->altSpecialOptsCombo->addItem(QLatin1String("NO_OPTS"), (int)NamespaceEntry::NO_OPTS); if (entry.nsType == NamespaceEntry::COMMENT) { d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANG"), NamespaceEntry::COMMENT_ALTLANG); d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANGLIST"), NamespaceEntry::COMMENT_ATLLANGLIST); d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_XMP"), NamespaceEntry::COMMENT_XMP); d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_JPEG"), NamespaceEntry::COMMENT_JPEG); } if (entry.nsType == NamespaceEntry::TAGS) { d->altSpecialOptsCombo->addItem(QLatin1String("TAG_XMPBAG"), NamespaceEntry::TAG_XMPBAG); d->altSpecialOptsCombo->addItem(QLatin1String("TAG_XMPSEQ"), NamespaceEntry::TAG_XMPSEQ); d->altSpecialOptsCombo->addItem(QLatin1String("TAG_ACDSEE"), NamespaceEntry::TAG_ACDSEE); } // -------------------------------------------------------- d->separatorLabel = new QLabel(d->page); d->separatorLabel->setText(i18n("Separator:")); d->nameSpaceSeparator = new QLineEdit(this); // -------------------------------------------------------- d->isTagLabel = new QLabel(d->page); d->isTagLabel->setText(i18n("Set Tags Path:")); d->isPath = new QCheckBox(this); d->tipLabel2 = new QLabel(d->page); d->tipLabel2->setTextFormat(Qt::RichText); d->tipLabel2->setWordWrap(true); QPalette sample_palette; sample_palette.setColor(QPalette::Window, QColor(255, 51, 51, 150)); sample_palette.setColor(QPalette::WindowText, Qt::black); d->tipLabel2->setAutoFillBackground(true); d->tipLabel2->setPalette(sample_palette); d->tipLabel2->hide(); // ----------------------Rating Elements---------------------------------- d->ratingMappings = new QGroupBox(this); d->ratingMappings->setFlat(true); QGridLayout* const ratingMappingsLayout = new QGridLayout(d->ratingMappings); QLabel* const ratingLabel = new QLabel(d->page); ratingLabel->setText(i18n("Rating Mapping:")); d->zeroStars = new QSpinBox(this); d->zeroStars->setValue(0); d->oneStar = new QSpinBox(this); d->oneStar->setValue(1); d->twoStars = new QSpinBox(this); d->twoStars->setValue(2); d->threeStars = new QSpinBox(this); d->threeStars->setValue(3); d->fourStars = new QSpinBox(this); d->fourStars->setValue(4); d->fiveStars = new QSpinBox(this); d->fiveStars->setValue(5); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); const int cmargin = QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin); ratingMappingsLayout->addWidget(ratingLabel, 0, 0, 1, 2); ratingMappingsLayout->addWidget(d->zeroStars, 1, 0, 1, 1); ratingMappingsLayout->addWidget(d->oneStar, 1, 1, 1, 1); ratingMappingsLayout->addWidget(d->twoStars, 1, 2, 1, 1); ratingMappingsLayout->addWidget(d->threeStars, 1, 3, 1, 1); ratingMappingsLayout->addWidget(d->fourStars, 1, 4, 1, 1); ratingMappingsLayout->addWidget(d->fiveStars, 1, 5, 1, 1); d->gridLayout->addWidget(d->logo, 0, 0, 1, 2); d->gridLayout->addWidget(d->topLabel, 0, 1, 1, 4); d->gridLayout->addWidget(d->tagTipLabel, 1, 0, 1, 6); d->gridLayout->addWidget(d->ratingTipLabel, 2, 0, 1, 6); d->gridLayout->addWidget(d->commentTipLabel, 3, 0, 1, 6); d->gridLayout->addWidget(d->subspaceLabel, 5, 0, 1, 2); d->gridLayout->addWidget(d->subspaceCombo, 5, 2, 1, 4); d->gridLayout->addWidget(d->titleLabel, 6, 0, 1, 2); d->gridLayout->addWidget(d->namespaceName, 6, 2, 1, 4); d->gridLayout->addWidget(d->specialOptsLabel, 7, 0, 1, 2); d->gridLayout->addWidget(d->specialOptsCombo, 7, 2, 1, 4); d->gridLayout->addWidget(d->alternativeNameLabel, 8, 0, 1, 2); d->gridLayout->addWidget(d->alternativeName, 8, 2, 1, 4); d->gridLayout->addWidget(d->altspecialOptsLabel, 9, 0, 1, 2); d->gridLayout->addWidget(d->altSpecialOptsCombo, 9, 2, 1, 4); d->gridLayout->addWidget(d->separatorLabel, 10, 0, 1, 2); d->gridLayout->addWidget(d->nameSpaceSeparator, 10, 2, 1, 4); d->gridLayout->addWidget(d->isTagLabel, 11, 0, 1, 2); d->gridLayout->addWidget(d->isPath, 11, 2, 1, 3); d->gridLayout->addWidget(d->ratingMappings, 12, 0, 2, 6); d->gridLayout->addWidget(d->tipLabel2, 14, 0, 1, 6); d->gridLayout->setContentsMargins(cmargin, cmargin, cmargin, cmargin); d->gridLayout->setSpacing(spacing); QVBoxLayout* const vbx = new QVBoxLayout(this); vbx->addWidget(d->page); vbx->addWidget(d->buttons); } void NamespaceEditDlg::populateFields(NamespaceEntry& entry) { d->namespaceName->setText(entry.namespaceName); d->nameSpaceSeparator->setText(entry.separator); if (entry.tagPaths == NamespaceEntry::TAGPATH) { d->isPath->setChecked(true); } else { d->isPath->setChecked(false); } d->alternativeName->setText(entry.alternativeName); d->specialOptsCombo->setCurrentIndex(d->specialOptsCombo->findData(entry.specialOpts)); d->altSpecialOptsCombo->setCurrentIndex(d->altSpecialOptsCombo->findData(entry.secondNameOpts)); if (entry.convertRatio.size() == 6) { d->zeroStars->setValue(entry.convertRatio.at(0)); d->oneStar->setValue(entry.convertRatio.at(1)); d->twoStars->setValue(entry.convertRatio.at(2)); d->threeStars->setValue(entry.convertRatio.at(3)); d->fourStars->setValue(entry.convertRatio.at(4)); d->fiveStars->setValue(entry.convertRatio.at(5)); } } void NamespaceEditDlg::setType(NamespaceEntry::NamespaceType type) { switch(type) { case NamespaceEntry::TAGS: qCDebug(DIGIKAM_GENERAL_LOG) << "Setting up tags"; d->ratingTipLabel->hide(); d->commentTipLabel->hide(); d->ratingMappings->hide(); // disable IPTC and EXIV for tags d->subspaceCombo->setItemData(0, 0, Qt::UserRole -1); d->subspaceCombo->setItemData(1, 0, Qt::UserRole -1); break; case NamespaceEntry::RATING: d->tagTipLabel->hide(); d->commentTipLabel->hide(); d->isPath->hide(); d->isTagLabel->hide(); d->separatorLabel->hide(); d->nameSpaceSeparator->hide(); break; case NamespaceEntry::COMMENT: d->tagTipLabel->hide(); d->ratingTipLabel->hide(); d->isPath->hide(); d->isTagLabel->hide(); d->separatorLabel->hide(); d->nameSpaceSeparator->hide(); d->ratingMappings->hide(); break; default: break; } } void NamespaceEditDlg::makeReadOnly() { QString txt = i18n("This is a default namespace. Default namespaces can only be disabled"); d->tipLabel2->setText(txt); d->tipLabel2->show(); d->subspaceCombo->setDisabled(true); d->specialOptsCombo->setDisabled(true); d->altSpecialOptsCombo->setDisabled(true); d->namespaceName->setDisabled(true); d->alternativeName->setDisabled(true); d->nameSpaceSeparator->setDisabled(true); d->isPath->setDisabled(true); d->ratingMappings->setDisabled(true); d->zeroStars->setDisabled(true); d->oneStar->setDisabled(true); d->twoStars->setDisabled(true); d->threeStars->setDisabled(true); d->fourStars->setDisabled(true); d->fiveStars->setDisabled(true); } bool NamespaceEditDlg::validifyCheck(QString& errMsg) { // bool result = true; NOT USED if (d->namespaceName->text().isEmpty()) { errMsg = i18n("The namespace name is required"); return false; } switch (d->subspaceCombo->currentData().toInt()) { case NamespaceEntry::EXIF: if (d->namespaceName->text().split(QLatin1Char('.')).first() != QLatin1String("Exif")) { errMsg = i18n("EXIF namespace name must start with \"Exif\"."); return false; } if (!d->alternativeName->text().isEmpty() && d->alternativeName->text().split(QLatin1Char('.')).first() != QLatin1String("Exif")) { errMsg = i18n("EXIF alternative namespace name must start with \"Exif\"."); return false; } break; case NamespaceEntry::IPTC: if (d->namespaceName->text().split(QLatin1Char('.')).first() != QLatin1String("Iptc")) { errMsg = i18n("IPTC namespace name must start with \"Iptc\"."); return false; } if (!d->alternativeName->text().isEmpty() && d->alternativeName->text().split(QLatin1Char('.')).first() != QLatin1String("Iptc")) { errMsg = i18n("IPTC alternative namespace name must start with \"Iptc\"."); return false; } break; case NamespaceEntry::XMP: if (d->namespaceName->text().split(QLatin1Char('.')).first() != QLatin1String("Xmp")) { errMsg = i18n("XMP namespace name must start with \"Xmp\"."); return false; } if (!d->alternativeName->text().isEmpty() && d->alternativeName->text().split(QLatin1Char('.')).first() != QLatin1String("Xmp")) { errMsg = i18n("XMP alternative namespace name must start with \"Xmp\"."); return false; } break; default: break; } switch (d->nsType) { case NamespaceEntry::TAGS: if (d->nameSpaceSeparator->text().isEmpty()) { errMsg = i18n("Tag Path separator is required"); return false; } if (d->nameSpaceSeparator->text().size() > 1) { errMsg = i18n("Only one character is now supported as tag path separator"); return false; } break; case NamespaceEntry::RATING: break; case NamespaceEntry::COMMENT: break; default: break; } return true; } void NamespaceEditDlg::saveData(NamespaceEntry& entry) { entry.namespaceName = d->namespaceName->text(); entry.separator = d->nameSpaceSeparator->text(); if (d->isPath->isChecked()) { entry.tagPaths = NamespaceEntry::TAGPATH; } else { entry.tagPaths = NamespaceEntry::TAG; } entry.alternativeName = d->alternativeName->text(); entry.specialOpts = (NamespaceEntry::SpecialOptions)d->specialOptsCombo->currentData().toInt(); entry.secondNameOpts = (NamespaceEntry::SpecialOptions)d->altSpecialOptsCombo->currentData().toInt(); entry.subspace = (NamespaceEntry::NsSubspace)d->subspaceCombo->currentData().toInt(); entry.convertRatio.clear(); entry.convertRatio.append(d->zeroStars->value()); entry.convertRatio.append(d->oneStar->value()); entry.convertRatio.append(d->twoStars->value()); entry.convertRatio.append(d->threeStars->value()); entry.convertRatio.append(d->fourStars->value()); entry.convertRatio.append(d->fiveStars->value()); } void NamespaceEditDlg::accept() { QString errMsg; if (validifyCheck(errMsg)) { QDialog::accept(); } else { d->tipLabel2->setText(errMsg); d->tipLabel2->show(); } } void NamespaceEditDlg::slotHelp() { DXmlGuiWindow::openHandbook(); } } // namespace Digikam