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