From 7e00441c257e7e9e5dc5ab983fc06046fb72b0c5 Mon Sep 17 00:00:00 2001 From: Gilles Caulier Date: Sat, 22 Jul 2017 15:46:08 +0200 Subject: fix broken linking stage under MacOS with macports. move database models into libdigikamdatabase. Let's others model in place to be included into libdigikamcore --- libs/database/CMakeLists.txt | 16 +- libs/database/models/imagefiltermodel.cpp | 1116 ++++++++++++++++++ libs/database/models/imagefiltermodel.h | 299 +++++ libs/database/models/imagefiltermodelpriv.cpp | 258 ++++ libs/database/models/imagefiltermodelpriv.h | 159 +++ libs/database/models/imagefiltermodelthreads.cpp | 40 + libs/database/models/imagefiltermodelthreads.h | 100 ++ libs/database/models/imagefiltersettings.cpp | 952 +++++++++++++++ libs/database/models/imagefiltersettings.h | 349 ++++++ libs/database/models/imagelistmodel.cpp | 70 ++ libs/database/models/imagelistmodel.h | 63 + libs/database/models/imagemodel.cpp | 1368 ++++++++++++++++++++++ libs/database/models/imagemodel.h | 364 ++++++ libs/database/models/imagesortsettings.cpp | 400 +++++++ libs/database/models/imagesortsettings.h | 225 ++++ libs/database/models/imagethumbnailmodel.cpp | 323 +++++ libs/database/models/imagethumbnailmodel.h | 140 +++ libs/database/models/imageversionsmodel.cpp | 183 +++ libs/database/models/imageversionsmodel.h | 75 ++ libs/models/CMakeLists.txt | 15 +- libs/models/imagefiltermodel.cpp | 1116 ------------------ libs/models/imagefiltermodel.h | 299 ----- libs/models/imagefiltermodelpriv.cpp | 258 ---- libs/models/imagefiltermodelpriv.h | 159 --- libs/models/imagefiltermodelthreads.cpp | 40 - libs/models/imagefiltermodelthreads.h | 100 -- libs/models/imagefiltersettings.cpp | 952 --------------- libs/models/imagefiltersettings.h | 349 ------ libs/models/imagelistmodel.cpp | 70 -- libs/models/imagelistmodel.h | 63 - libs/models/imagemodel.cpp | 1368 ---------------------- libs/models/imagemodel.h | 364 ------ libs/models/imagesortsettings.cpp | 400 ------- libs/models/imagesortsettings.h | 225 ---- libs/models/imagethumbnailmodel.cpp | 323 ----- libs/models/imagethumbnailmodel.h | 140 --- libs/models/imageversionsmodel.cpp | 183 --- libs/models/imageversionsmodel.h | 75 -- 38 files changed, 6499 insertions(+), 6500 deletions(-) create mode 100644 libs/database/models/imagefiltermodel.cpp create mode 100644 libs/database/models/imagefiltermodel.h create mode 100644 libs/database/models/imagefiltermodelpriv.cpp create mode 100644 libs/database/models/imagefiltermodelpriv.h create mode 100644 libs/database/models/imagefiltermodelthreads.cpp create mode 100644 libs/database/models/imagefiltermodelthreads.h create mode 100644 libs/database/models/imagefiltersettings.cpp create mode 100644 libs/database/models/imagefiltersettings.h create mode 100644 libs/database/models/imagelistmodel.cpp create mode 100644 libs/database/models/imagelistmodel.h create mode 100644 libs/database/models/imagemodel.cpp create mode 100644 libs/database/models/imagemodel.h create mode 100644 libs/database/models/imagesortsettings.cpp create mode 100644 libs/database/models/imagesortsettings.h create mode 100644 libs/database/models/imagethumbnailmodel.cpp create mode 100644 libs/database/models/imagethumbnailmodel.h create mode 100644 libs/database/models/imageversionsmodel.cpp create mode 100644 libs/database/models/imageversionsmodel.h delete mode 100644 libs/models/imagefiltermodel.cpp delete mode 100644 libs/models/imagefiltermodel.h delete mode 100644 libs/models/imagefiltermodelpriv.cpp delete mode 100644 libs/models/imagefiltermodelpriv.h delete mode 100644 libs/models/imagefiltermodelthreads.cpp delete mode 100644 libs/models/imagefiltermodelthreads.h delete mode 100644 libs/models/imagefiltersettings.cpp delete mode 100644 libs/models/imagefiltersettings.h delete mode 100644 libs/models/imagelistmodel.cpp delete mode 100644 libs/models/imagelistmodel.h delete mode 100644 libs/models/imagemodel.cpp delete mode 100644 libs/models/imagemodel.h delete mode 100644 libs/models/imagesortsettings.cpp delete mode 100644 libs/models/imagesortsettings.h delete mode 100644 libs/models/imagethumbnailmodel.cpp delete mode 100644 libs/models/imagethumbnailmodel.h delete mode 100644 libs/models/imageversionsmodel.cpp delete mode 100644 libs/models/imageversionsmodel.h diff --git a/libs/database/CMakeLists.txt b/libs/database/CMakeLists.txt index 7d05536..a431a36 100644 --- a/libs/database/CMakeLists.txt +++ b/libs/database/CMakeLists.txt @@ -13,6 +13,18 @@ endif (POLICY CMP0063) # Boost uses operator names (and, not, ...) string(REPLACE "-fno-operator-names" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +set(libdatabasemodels_SRCS + models/imagemodel.cpp + models/imagefiltermodel.cpp + models/imagefiltermodelpriv.cpp + models/imagefiltermodelthreads.cpp + models/imagefiltersettings.cpp + models/imagelistmodel.cpp + models/imagesortsettings.cpp + models/imagethumbnailmodel.cpp + models/imageversionsmodel.cpp +) + set(libdatabasecore_SRCS server/databaseserverstarter.cpp server/databaseservererror.cpp @@ -152,10 +164,10 @@ if(ENABLE_DBUS) include_directories($) endif() -add_library(digikamdatabase_src OBJECT ${digikamdatabase_LIB_SRCS}) +add_library(digikamdatabase_src OBJECT ${digikamdatabase_LIB_SRCS} ${libdatabasemodels_SRCS}) add_library(digikamdatabasemain_src OBJECT ${libdatabaseutils_SRCS} ${libimgqsort_SRCS}) add_library(digikamdatabasecore_src OBJECT ${libdatabasecore_SRCS}) -add_library(digikamdatabase SHARED $ $) +add_library(digikamdatabase $) generate_export_header(digikamdatabase BASE_NAME digikam_database diff --git a/libs/database/models/imagefiltermodel.cpp b/libs/database/models/imagefiltermodel.cpp new file mode 100644 index 0000000..3d57e05 --- /dev/null +++ b/libs/database/models/imagefiltermodel.cpp @@ -0,0 +1,1116 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-05 + * Description : Qt item model for database entries + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * Copyright (C) 2011-2017 by Gilles Caulier + * Copyright (C) 2010 by Andi Clemens + * Copyright (C) 2011 by Michael G. Hansen + * Copyright (C) 2014 by Mohamed Anwer + * + * 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 "imagefiltermodel.h" +#include "imagefiltermodelpriv.h" +#include "imagefiltermodelthreads.h" + +// Local includes + +#include "digikam_debug.h" +#include "coredbaccess.h" +#include "coredbchangesets.h" +#include "coredbwatch.h" +#include "imageinfolist.h" +#include "imagemodel.h" + +namespace Digikam +{ + +ImageSortFilterModel::ImageSortFilterModel(QObject* parent) + : DCategorizedSortFilterProxyModel(parent), m_chainedModel(0) +{ +} + +void ImageSortFilterModel::setSourceImageModel(ImageModel* source) +{ + if (m_chainedModel) + { + m_chainedModel->setSourceImageModel(source); + } + else + { + setDirectSourceImageModel(source); + } +} + +void ImageSortFilterModel::setSourceFilterModel(ImageSortFilterModel* source) +{ + if (source) + { + ImageModel* const model = sourceImageModel(); + + if (model) + { + source->setSourceImageModel(model); + } + } + + m_chainedModel = source; + setSourceModel(source); +} + +void ImageSortFilterModel::setDirectSourceImageModel(ImageModel* model) +{ + setSourceModel(model); +} + +void ImageSortFilterModel::setSourceModel(QAbstractItemModel* model) +{ + // made it protected, only setSourceImageModel is public + DCategorizedSortFilterProxyModel::setSourceModel(model); +} + +ImageModel* ImageSortFilterModel::sourceImageModel() const +{ + if (m_chainedModel) + { + return m_chainedModel->sourceImageModel(); + } + + return static_cast(sourceModel()); +} + +ImageSortFilterModel* ImageSortFilterModel::sourceFilterModel() const +{ + return m_chainedModel; +} + +ImageFilterModel* ImageSortFilterModel::imageFilterModel() const +{ + // reimplemented in ImageFilterModel + if (m_chainedModel) + { + return m_chainedModel->imageFilterModel(); + } + + return 0; +} + +QModelIndex ImageSortFilterModel::mapToSourceImageModel(const QModelIndex& index) const +{ + if (m_chainedModel) + { + return m_chainedModel->mapToSourceImageModel(mapToSource(index)); + } + + return mapToSource(index); +} + +QModelIndex ImageSortFilterModel::mapFromSourceImageModel(const QModelIndex& albummodel_index) const +{ + if (m_chainedModel) + { + return mapFromSource(m_chainedModel->mapFromSourceImageModel(albummodel_index)); + } + + return mapFromSource(albummodel_index); +} + + +QModelIndex ImageSortFilterModel::mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const +{ + if (m_chainedModel) + { + return m_chainedModel->mapToSourceImageModel(sourceModel_index); + } + return sourceModel_index; +} + +// -------------- Convenience mappers ------------------------------------------------------------------- + +QList ImageSortFilterModel::mapListToSource(const QList& indexes) const +{ + QList sourceIndexes; + foreach(const QModelIndex& index, indexes) + { + sourceIndexes << mapToSourceImageModel(index); + } + return sourceIndexes; +} + +QList ImageSortFilterModel::mapListFromSource(const QList& sourceIndexes) const +{ + QList indexes; + foreach(const QModelIndex& index, sourceIndexes) + { + indexes << mapFromSourceImageModel(index); + } + return indexes; +} + +ImageInfo ImageSortFilterModel::imageInfo(const QModelIndex& index) const +{ + return sourceImageModel()->imageInfo(mapToSourceImageModel(index)); +} + +qlonglong ImageSortFilterModel::imageId(const QModelIndex& index) const +{ + return sourceImageModel()->imageId(mapToSourceImageModel(index)); +} + +QList ImageSortFilterModel::imageInfos(const QList& indexes) const +{ + QList infos; + ImageModel* const model = sourceImageModel(); + + foreach(const QModelIndex& index, indexes) + { + infos << model->imageInfo(mapToSourceImageModel(index)); + } + + return infos; +} + +QList ImageSortFilterModel::imageIds(const QList& indexes) const +{ + QList ids; + ImageModel* const model = sourceImageModel(); + + foreach(const QModelIndex& index, indexes) + { + ids << model->imageId(mapToSourceImageModel(index)); + } + + return ids; +} + +QModelIndex ImageSortFilterModel::indexForPath(const QString& filePath) const +{ + return mapFromSourceImageModel(sourceImageModel()->indexForPath(filePath)); +} + +QModelIndex ImageSortFilterModel::indexForImageInfo(const ImageInfo& info) const +{ + return mapFromSourceImageModel(sourceImageModel()->indexForImageInfo(info)); +} + +QModelIndex ImageSortFilterModel::indexForImageId(qlonglong id) const +{ + return mapFromSourceImageModel(sourceImageModel()->indexForImageId(id)); +} + +QList ImageSortFilterModel::imageInfosSorted() const +{ + QList infos; + const int size = rowCount(); + ImageModel* const model = sourceImageModel(); + + for (int i=0; iimageInfo(mapToSourceImageModel(index(i, 0))); + } + + return infos; +} + +// -------------------------------------------------------------------------------------------- + +ImageFilterModel::ImageFilterModel(QObject* parent) + : ImageSortFilterModel(parent), + d_ptr(new ImageFilterModelPrivate) +{ + d_ptr->init(this); +} + +ImageFilterModel::ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent) + : ImageSortFilterModel(parent), + d_ptr(&dd) +{ + d_ptr->init(this); +} + +ImageFilterModel::~ImageFilterModel() +{ + Q_D(ImageFilterModel); + delete d; +} + +void ImageFilterModel::setDirectSourceImageModel(ImageModel* sourceModel) +{ + Q_D(ImageFilterModel); + + if (d->imageModel) + { + d->imageModel->unsetPreprocessor(d); + disconnect(d->imageModel, SIGNAL(modelReset()), + this, SLOT(slotModelReset())); + slotModelReset(); + } + + d->imageModel = sourceModel; + + if (d->imageModel) + { + d->imageModel->setPreprocessor(d); + + connect(d->imageModel, SIGNAL(preprocess(QList,QList)), + d, SLOT(preprocessInfos(QList,QList))); + + connect(d->imageModel, SIGNAL(processAdded(QList,QList)), + d, SLOT(processAddedInfos(QList,QList))); + + connect(d, SIGNAL(reAddImageInfos(QList,QList)), + d->imageModel, SLOT(reAddImageInfos(QList,QList))); + + connect(d, SIGNAL(reAddingFinished()), + d->imageModel, SLOT(reAddingFinished())); + + connect(d->imageModel, SIGNAL(modelReset()), + this, SLOT(slotModelReset())); + + connect(d->imageModel, SIGNAL(imageChange(ImageChangeset,QItemSelection)), + this, SLOT(slotImageChange(ImageChangeset))); + + connect(d->imageModel, SIGNAL(imageTagChange(ImageTagChangeset,QItemSelection)), + this, SLOT(slotImageTagChange(ImageTagChangeset))); + } + + setSourceModel(d->imageModel); +} + +QVariant ImageFilterModel::data(const QModelIndex& index, int role) const +{ + Q_D(const ImageFilterModel); + + if (!index.isValid()) + { + return QVariant(); + } + + switch (role) + { + // Attention: This breaks should there ever be another filter model between this and the ImageModel + + case DCategorizedSortFilterProxyModel::CategoryDisplayRole: + return categoryIdentifier(d->imageModel->imageInfoRef(mapToSource(index))); + case CategorizationModeRole: + return d->sorter.categorizationMode; + case SortOrderRole: + return d->sorter.sortRole; + //case CategoryCountRole: + // return categoryCount(d->imageModel->imageInfoRef(mapToSource(index))); + case CategoryAlbumIdRole: + return d->imageModel->imageInfoRef(mapToSource(index)).albumId(); + case CategoryFormatRole: + return d->imageModel->imageInfoRef(mapToSource(index)).format(); + case GroupIsOpenRole: + return d->groupFilter.isAllOpen() || + d->groupFilter.isOpen(d->imageModel->imageInfoRef(mapToSource(index)).id()); + case ImageFilterModelPointerRole: + return QVariant::fromValue(const_cast(this)); + } + + return DCategorizedSortFilterProxyModel::data(index, role); +} + +ImageFilterModel* ImageFilterModel::imageFilterModel() const +{ + return const_cast(this); +} + +DatabaseFields::Set ImageFilterModel::suggestedWatchFlags() const +{ + DatabaseFields::Set watchFlags; + watchFlags |= DatabaseFields::Name | DatabaseFields::FileSize | DatabaseFields::ModificationDate; + watchFlags |= DatabaseFields::Rating | DatabaseFields::CreationDate | DatabaseFields::Orientation | + DatabaseFields::Width | DatabaseFields::Height; + watchFlags |= DatabaseFields::Comment; + watchFlags |= DatabaseFields::ImageRelations; + return watchFlags; +} + +// -------------- Filter settings -------------- + +void ImageFilterModel::setDayFilter(const QList& days) +{ + Q_D(ImageFilterModel); + d->filter.setDayFilter(days); + setImageFilterSettings(d->filter); +} + +void ImageFilterModel::setTagFilter(const QList& includedTags, const QList& excludedTags, + ImageFilterSettings::MatchingCondition matchingCond, + bool showUnTagged, const QList& clTagIds, const QList& plTagIds) +{ + Q_D(ImageFilterModel); + d->filter.setTagFilter(includedTags, excludedTags, matchingCond, showUnTagged, clTagIds, plTagIds); + setImageFilterSettings(d->filter); +} + +void ImageFilterModel::setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded) +{ + Q_D(ImageFilterModel); + d->filter.setRatingFilter(rating, ratingCond, isUnratedExcluded); + setImageFilterSettings(d->filter); +} + +void ImageFilterModel::setUrlWhitelist(const QList urlList, const QString& id) +{ + Q_D(ImageFilterModel); + d->filter.setUrlWhitelist(urlList, id); + setImageFilterSettings(d->filter); +} + +void ImageFilterModel::setIdWhitelist(const QList& idList, const QString& id) +{ + Q_D(ImageFilterModel); + d->filter.setIdWhitelist(idList, id); + setImageFilterSettings(d->filter); +} + +void ImageFilterModel::setMimeTypeFilter(int mimeTypeFilter) +{ + Q_D(ImageFilterModel); + d->filter.setMimeTypeFilter(mimeTypeFilter); + setImageFilterSettings(d->filter); +} + +void ImageFilterModel::setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition) +{ + Q_D(ImageFilterModel); + d->filter.setGeolocationFilter(condition); + setImageFilterSettings(d->filter); +} + +void ImageFilterModel::setTextFilter(const SearchTextFilterSettings& settings) +{ + Q_D(ImageFilterModel); + d->filter.setTextFilter(settings); + setImageFilterSettings(d->filter); +} + +void ImageFilterModel::setImageFilterSettings(const ImageFilterSettings& settings) +{ + Q_D(ImageFilterModel); + + { + QMutexLocker lock(&d->mutex); + d->version++; + d->filter = settings; + d->filterCopy = settings; + d->versionFilterCopy = d->versionFilter; + d->groupFilterCopy = d->groupFilter; + + d->needPrepareComments = settings.isFilteringByText(); + d->needPrepareTags = settings.isFilteringByTags(); + d->needPrepareGroups = true; + d->needPrepare = d->needPrepareComments || d->needPrepareTags || d->needPrepareGroups; + + d->hasOneMatch = false; + d->hasOneMatchForText = false; + } + + d->filterResults.clear(); + + //d->categoryCountHashInt.clear(); + //d->categoryCountHashString.clear(); + if (d->imageModel) + { + d->infosToProcess(d->imageModel->imageInfos()); + } + + emit filterSettingsChanged(settings); +} + +void ImageFilterModel::setVersionManagerSettings(const VersionManagerSettings& settings) +{ + Q_D(ImageFilterModel); + d->versionFilter.setVersionManagerSettings(settings); + setVersionImageFilterSettings(d->versionFilter); +} + +void ImageFilterModel::setExceptionList(const QList& idList, const QString& id) +{ + Q_D(ImageFilterModel); + d->versionFilter.setExceptionList(idList, id); + setVersionImageFilterSettings(d->versionFilter); +} + +void ImageFilterModel::setVersionImageFilterSettings(const VersionImageFilterSettings& settings) +{ + Q_D(ImageFilterModel); + d->versionFilter = settings; + slotUpdateFilter(); +} + +bool ImageFilterModel::isGroupOpen(qlonglong group) const +{ + Q_D(const ImageFilterModel); + return d->groupFilter.isOpen(group); +} + +bool ImageFilterModel::isAllGroupsOpen() const +{ + Q_D(const ImageFilterModel); + return d->groupFilter.isAllOpen(); +} + +void ImageFilterModel::setGroupOpen(qlonglong group, bool open) +{ + Q_D(ImageFilterModel); + d->groupFilter.setOpen(group, open); + setGroupImageFilterSettings(d->groupFilter); +} + +void ImageFilterModel::toggleGroupOpen(qlonglong group) +{ + setGroupOpen(group, !isGroupOpen(group)); +} + +void ImageFilterModel::setAllGroupsOpen(bool open) +{ + Q_D(ImageFilterModel); + d->groupFilter.setAllOpen(open); + setGroupImageFilterSettings(d->groupFilter); +} + +void ImageFilterModel::setGroupImageFilterSettings(const GroupImageFilterSettings& settings) +{ + Q_D(ImageFilterModel); + d->groupFilter = settings; + slotUpdateFilter(); +} + +void ImageFilterModel::slotUpdateFilter() +{ + Q_D(ImageFilterModel); + setImageFilterSettings(d->filter); +} + +ImageFilterSettings ImageFilterModel::imageFilterSettings() const +{ + Q_D(const ImageFilterModel); + return d->filter; +} + +ImageSortSettings ImageFilterModel::imageSortSettings() const +{ + Q_D(const ImageFilterModel); + return d->sorter; +} + +VersionImageFilterSettings ImageFilterModel::versionImageFilterSettings() const +{ + Q_D(const ImageFilterModel); + return d->versionFilter; +} + +GroupImageFilterSettings ImageFilterModel::groupImageFilterSettings() const +{ + Q_D(const ImageFilterModel); + return d->groupFilter; +} + +void ImageFilterModel::slotModelReset() +{ + Q_D(ImageFilterModel); + { + QMutexLocker lock(&d->mutex); + // discard all packages on the way that are marked as send out for re-add + d->lastDiscardVersion = d->version; + d->sentOutForReAdd = 0; + // discard all packages on the way + d->version++; + d->sentOut = 0; + + d->hasOneMatch = false; + d->hasOneMatchForText = false; + } + d->filterResults.clear(); +} + +bool ImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + Q_D(const ImageFilterModel); + + if (source_parent.isValid()) + { + return false; + } + + qlonglong id = d->imageModel->imageId(source_row); + QHash::const_iterator it = d->filterResults.constFind(id); + + if (it != d->filterResults.constEnd()) + { + return it.value(); + } + + // usually done in thread and cache, unless source model changed + ImageInfo info = d->imageModel->imageInfo(source_row); + bool match = d->filter.matches(info); + match = match ? d->versionFilter.matches(info) : false; + + return match ? d->groupFilter.matches(info) : false; +} + +void ImageFilterModel::setSendImageInfoSignals(bool sendSignals) +{ + if (sendSignals) + { + connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(slotRowsInserted(QModelIndex,int,int))); + + connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); + } + else + { + disconnect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(slotRowsInserted(QModelIndex,int,int))); + + disconnect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); + } +} + +void ImageFilterModel::slotRowsInserted(const QModelIndex& /*parent*/, int start, int end) +{ + QList infos; + + for (int i=start; i<=end; ++i) + { + infos << imageInfo(index(i, 0)); + } + + emit imageInfosAdded(infos); +} + +void ImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end) +{ + QList infos; + + for (int i=start; i<=end; ++i) + { + infos << imageInfo(index(i, 0)); + } + + emit imageInfosAboutToBeRemoved(infos); +} + +// -------------- Threaded preparation & filtering -------------- + +void ImageFilterModel::addPrepareHook(ImageFilterModelPrepareHook* hook) +{ + Q_D(ImageFilterModel); + QMutexLocker lock(&d->mutex); + d->prepareHooks << hook; +} + +void ImageFilterModel::removePrepareHook(ImageFilterModelPrepareHook* hook) +{ + Q_D(ImageFilterModel); + QMutexLocker lock(&d->mutex); + d->prepareHooks.removeAll(hook); +} + +void ImageFilterModelPreparer::process(ImageFilterModelTodoPackage package) +{ + if (!checkVersion(package)) + { + emit discarded(package); + return; + } + + // get thread-local copy + bool needPrepareTags, needPrepareComments, needPrepareGroups; + QList prepareHooks; + { + QMutexLocker lock(&d->mutex); + needPrepareTags = d->needPrepareTags; + needPrepareComments = d->needPrepareComments; + needPrepareGroups = d->needPrepareGroups; + prepareHooks = d->prepareHooks; + } + + //TODO: Make efficient!! + if (needPrepareComments) + { + foreach(const ImageInfo& info, package.infos) + { + info.comment(); + } + } + + if (!checkVersion(package)) + { + emit discarded(package); + return; + } + + // The downside of QVector: At some point, we may need a QList for an API. + // Nonetheless, QList and ImageInfo is fast. We could as well + // reimplement ImageInfoList to ImageInfoVector (internally with templates?) + ImageInfoList infoList; + + if (needPrepareTags || needPrepareGroups) + { + infoList = package.infos.toList(); + } + + if (needPrepareTags) + { + infoList.loadTagIds(); + } + + if (needPrepareGroups) + { + infoList.loadGroupImageIds(); + } + + foreach(ImageFilterModelPrepareHook* hook, prepareHooks) + { + hook->prepare(package.infos); + } + + emit processed(package); +} + +void ImageFilterModelFilterer::process(ImageFilterModelTodoPackage package) +{ + if (!checkVersion(package)) + { + emit discarded(package); + return; + } + + // get thread-local copy + ImageFilterSettings localFilter; + VersionImageFilterSettings localVersionFilter; + GroupImageFilterSettings localGroupFilter; + bool hasOneMatch; + bool hasOneMatchForText; + { + QMutexLocker lock(&d->mutex); + localFilter = d->filterCopy; + localVersionFilter = d->versionFilterCopy; + localGroupFilter = d->groupFilterCopy; + hasOneMatch = d->hasOneMatch; + hasOneMatchForText = d->hasOneMatchForText; + } + + // Actual filtering. The variants to spare checking hasOneMatch over and over again. + if (hasOneMatch && hasOneMatchForText) + { + foreach(const ImageInfo& info, package.infos) + { + package.filterResults[info.id()] = localFilter.matches(info) && + localVersionFilter.matches(info) && + localGroupFilter.matches(info); + } + } + else if (hasOneMatch) + { + bool matchForText; + + foreach(const ImageInfo& info, package.infos) + { + package.filterResults[info.id()] = localFilter.matches(info, &matchForText) && + localVersionFilter.matches(info) && + localGroupFilter.matches(info); + + if (matchForText) + { + hasOneMatchForText = true; + } + } + } + else + { + bool result, matchForText; + + foreach(const ImageInfo& info, package.infos) + { + result = localFilter.matches(info, &matchForText) && + localVersionFilter.matches(info) && + localGroupFilter.matches(info); + package.filterResults[info.id()] = result; + + if (result) + { + hasOneMatch = true; + } + + if (matchForText) + { + hasOneMatchForText = true; + } + } + } + + if (checkVersion(package)) + { + QMutexLocker lock(&d->mutex); + d->hasOneMatch = hasOneMatch; + d->hasOneMatchForText = hasOneMatchForText; + } + + emit processed(package); +} + +// -------------- Sorting and Categorization ------------------------------------------------------- + +void ImageFilterModel::setImageSortSettings(const ImageSortSettings& sorter) +{ + Q_D(ImageFilterModel); + d->sorter = sorter; + setCategorizedModel(d->sorter.categorizationMode != ImageSortSettings::NoCategories); + invalidate(); +} + +void ImageFilterModel::setCategorizationMode(ImageSortSettings::CategorizationMode mode) +{ + Q_D(ImageFilterModel); + d->sorter.setCategorizationMode(mode); + setImageSortSettings(d->sorter); +} + +void ImageFilterModel::setCategorizationSortOrder(ImageSortSettings::SortOrder order) +{ + Q_D(ImageFilterModel); + d->sorter.setCategorizationSortOrder(order); + setImageSortSettings(d->sorter); +} + +void ImageFilterModel::setSortRole(ImageSortSettings::SortRole role) +{ + Q_D(ImageFilterModel); + d->sorter.setSortRole(role); + setImageSortSettings(d->sorter); +} + +void ImageFilterModel::setSortOrder(ImageSortSettings::SortOrder order) +{ + Q_D(ImageFilterModel); + d->sorter.setSortOrder(order); + setImageSortSettings(d->sorter); +} + +void ImageFilterModel::setStringTypeNatural(bool natural) +{ + Q_D(ImageFilterModel); + d->sorter.setStringTypeNatural(natural); + setImageSortSettings(d->sorter); +} + +int ImageFilterModel::compareCategories(const QModelIndex& left, const QModelIndex& right) const +{ + // source indexes + Q_D(const ImageFilterModel); + + if (!d->sorter.isCategorized()) + { + return 0; + } + + if (!left.isValid() || !right.isValid()) + { + return -1; + } + + const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left); + const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right); + + // Check grouping + qlonglong leftGroupImageId = leftInfo.groupImageId(); + qlonglong rightGroupImageId = rightInfo.groupImageId(); + + return compareInfosCategories(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId), + rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId)); +} + +bool ImageFilterModel::subSortLessThan(const QModelIndex& left, const QModelIndex& right) const +{ + // source indexes + Q_D(const ImageFilterModel); + + if (!left.isValid() || !right.isValid()) + { + return true; + } + + if (left == right) + { + return false; + } + + const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left); + const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right); + + if (leftInfo == rightInfo) + { + return d->sorter.lessThan(left.data(ImageModel::ExtraDataRole), right.data(ImageModel::ExtraDataRole)); + } + + // Check grouping + qlonglong leftGroupImageId = leftInfo.groupImageId(); + qlonglong rightGroupImageId = rightInfo.groupImageId(); + + // Either no grouping (-1), or same group image, or same image + if (leftGroupImageId == rightGroupImageId) + { + return infosLessThan(leftInfo, rightInfo); + } + + // We have grouping to handle + + // Is one grouped on the other? Sort behind leader. + if (leftGroupImageId == rightInfo.id()) + { + return false; + } + if (rightGroupImageId == leftInfo.id()) + { + return true; + } + + // Use the group leader for sorting + return infosLessThan(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId), + rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId)); +} + +int ImageFilterModel::compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const +{ + // Note: reimplemented in ImageAlbumFilterModel + Q_D(const ImageFilterModel); + return d->sorter.compareCategories(left, right); +} + +// Feel free to optimize. QString::number is 3x slower. +static inline QString fastNumberToString(int id) +{ + const int size = sizeof(int) * 2; + char c[size+1]; + c[size] = '\0'; + char* p = c; + int number = id; + + for (int i=0; i>= 4; + ++p; + } + + return QString::fromLatin1(c); +} + +QString ImageFilterModel::categoryIdentifier(const ImageInfo& i) const +{ + Q_D(const ImageFilterModel); + + if (!d->sorter.isCategorized()) + { + return QString(); + } + + qlonglong groupedImageId = i.groupImageId(); + ImageInfo info = groupedImageId == -1 ? i : ImageInfo(groupedImageId); + + switch (d->sorter.categorizationMode) + { + case ImageSortSettings::NoCategories: + return QString(); + case ImageSortSettings::OneCategory: + return QString(); + case ImageSortSettings::CategoryByAlbum: + return fastNumberToString(info.albumId()); + case ImageSortSettings::CategoryByFormat: + return info.format(); + default: + return QString(); + } +} + +bool ImageFilterModel::infosLessThan(const ImageInfo& left, const ImageInfo& right) const +{ + Q_D(const ImageFilterModel); + return d->sorter.lessThan(left, right); +} + +// -------------- Watching changes ----------------------------------------------------------------- + +void ImageFilterModel::slotImageTagChange(const ImageTagChangeset& changeset) +{ + Q_D(ImageFilterModel); + + if (!d->imageModel || d->imageModel->isEmpty()) + { + return; + } + + // already scheduled to re-filter? + if (d->updateFilterTimer->isActive()) + { + return; + } + + // do we filter at all? + if (!d->versionFilter.isFilteringByTags() && + !d->filter.isFilteringByTags() && + !d->filter.isFilteringByText()) + { + return; + } + + // is one of our images affected? + foreach(const qlonglong& id, changeset.ids()) + { + // if one matching image id is found, trigger a refresh + if (d->imageModel->hasImage(id)) + { + d->updateFilterTimer->start(); + return; + } + } +} + +void ImageFilterModel::slotImageChange(const ImageChangeset& changeset) +{ + Q_D(ImageFilterModel); + + if (!d->imageModel || d->imageModel->isEmpty()) + { + return; + } + + // already scheduled to re-filter? + if (d->updateFilterTimer->isActive()) + { + return; + } + + // is one of the values affected that we filter or sort by? + DatabaseFields::Set set = changeset.changes(); + bool sortAffected = (set & d->sorter.watchFlags()); + bool filterAffected = (set & d->filter.watchFlags()) || (set & d->groupFilter.watchFlags()); + + if (!sortAffected && !filterAffected) + { + return; + } + + // is one of our images affected? + bool imageAffected = false; + + foreach(const qlonglong& id, changeset.ids()) + { + // if one matching image id is found, trigger a refresh + if (d->imageModel->hasImage(id)) + { + imageAffected = true; + break; + } + } + + if (!imageAffected) + { + return; + } + + if (filterAffected) + { + d->updateFilterTimer->start(); + } + else + { + invalidate(); // just resort, reuse filter results + } +} + +// ------------------------------------------------------------------------------------------------------- + +NoDuplicatesImageFilterModel::NoDuplicatesImageFilterModel(QObject* parent) + : ImageSortFilterModel(parent) +{ +} + +bool NoDuplicatesImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + QModelIndex index = sourceModel()->index(source_row, 0, source_parent); + + if (index.data(ImageModel::ExtraDataDuplicateCount).toInt() <= 1) + { + return true; + } + + QModelIndex previousIndex = sourceModel()->index(source_row - 1, 0, source_parent); + + if (!previousIndex.isValid()) + { + return true; + } + + if (sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(index)) == sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(previousIndex))) + { + return false; + } + return true; +} + +/* +void NoDuplicatesImageFilterModel::setSourceModel(QAbstractItemModel* model) +{ + if (sourceModel()) + { + } + + ImageSortFilterModel::setSourceModel(model); + + if (sourceModel()) + { + connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); + } +} + +void NoDuplicatesImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& parent, int begin, int end) +{ + bool needInvalidate = false; + + for (int i = begin; i<=end; ++i) + { + QModelIndex index = sourceModel()->index(i, 0, parent); + + // filtered out by us? + if (!mapFromSource(index).isValid()) + { + continue; + } + + QModelIndex sourceIndex = mapFromDirectSourceToSourceImageModel(index); + qlonglong id = sourceImageModel()->imageId(sourceIndex); + + if (sourceImageModel()->numberOfIndexesForImageId(id) > 1) + { + needInvalidate = true; + } + } +}*/ + +} // namespace Digikam diff --git a/libs/database/models/imagefiltermodel.h b/libs/database/models/imagefiltermodel.h new file mode 100644 index 0000000..d131b3e --- /dev/null +++ b/libs/database/models/imagefiltermodel.h @@ -0,0 +1,299 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-05 + * Description : Qt item model for database entries + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * Copyright (C) 2011 by Gilles Caulier + * Copyright (C) 2010 by Andi Clemens + * Copyright (C) 2011 by Michael G. Hansen + * Copyright (C) 2014 by Mohamed Anwer + * + * 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 IMAGEFILTERMODEL_H +#define IMAGEFILTERMODEL_H + +// Local includes + +#include "dcategorizedsortfilterproxymodel.h" +#include "textfilter.h" +#include "imagefiltersettings.h" +#include "imagemodel.h" +#include "imagesortsettings.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class ImageChangeset; +class ImageFilterModel; +class ImageTagChangeset; + +class DIGIKAM_DATABASE_EXPORT ImageFilterModelPrepareHook +{ +public: + + virtual ~ImageFilterModelPrepareHook() {}; + virtual void prepare(const QVector& infos) = 0; +}; + +// ----------------------------------------------------------------------------------------------- + +class DIGIKAM_DATABASE_EXPORT ImageSortFilterModel : public DCategorizedSortFilterProxyModel +{ + Q_OBJECT + +public: + + explicit ImageSortFilterModel(QObject* parent = 0); + + void setSourceImageModel(ImageModel* model); + ImageModel* sourceImageModel() const; + + void setSourceFilterModel(ImageSortFilterModel* model); + ImageSortFilterModel* sourceFilterModel() const; + + QModelIndex mapToSourceImageModel(const QModelIndex& index) const; + QModelIndex mapFromSourceImageModel(const QModelIndex& imagemodel_index) const; + QModelIndex mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const; + + /// Convenience methods mapped to ImageModel. + /// Mentioned indexes returned come from the source image model. + QList mapListToSource(const QList& indexes) const; + QList mapListFromSource(const QList& sourceIndexes) const; + + ImageInfo imageInfo(const QModelIndex& index) const; + qlonglong imageId(const QModelIndex& index) const; + QList imageInfos(const QList& indexes) const; + QList imageIds(const QList& indexes) const; + + QModelIndex indexForPath(const QString& filePath) const; + QModelIndex indexForImageInfo(const ImageInfo& info) const; + QModelIndex indexForImageId(qlonglong id) const; + + /** Returns a list of all image infos, sorted according to this model. + * If you do not need a sorted list, use ImageModel's imageInfos() method. + */ + QList imageInfosSorted() const; + + /// Returns this, any chained ImageFilterModel, or 0. + virtual ImageFilterModel* imageFilterModel() const; + +protected: + + /// Reimplement if needed. Called only when model shall be set as (direct) sourceModel. + virtual void setDirectSourceImageModel(ImageModel* model); + + // made protected + virtual void setSourceModel(QAbstractItemModel* model); + +protected: + + ImageSortFilterModel* m_chainedModel; +}; + +// ----------------------------------------------------------------------------------------------- + +class DIGIKAM_DATABASE_EXPORT ImageFilterModel : public ImageSortFilterModel +{ + Q_OBJECT + +public: + + enum ImageFilterModelRoles + { + /// Returns the current categorization mode + CategorizationModeRole = ImageModel::FilterModelRoles + 1, + /// Returns the current sort order + SortOrderRole = ImageModel::FilterModelRoles + 2, + // / Returns the number of items in the index' category + //CategoryCountRole = ImageModel::FilterModelRoles + 3, + /// Returns the id of the PAlbum of the index which is used for category + CategoryAlbumIdRole = ImageModel::FilterModelRoles + 3, + /// Returns the format of the index which is used for category + CategoryFormatRole = ImageModel::FilterModelRoles + 4, + /// Returns true if the given image is a group leader, and the group is opened + GroupIsOpenRole = ImageModel::FilterModelRoles + 5, + ImageFilterModelPointerRole = ImageModel::FilterModelRoles + 50 + }; + +public: + + explicit ImageFilterModel(QObject* parent = 0); + ~ImageFilterModel(); + + /** Add a hook to get added images for preparation tasks before they are added in the model */ + void addPrepareHook(ImageFilterModelPrepareHook* hook); + void removePrepareHook(ImageFilterModelPrepareHook* hook); + + /** Returns a set of DatabaseFields suggested to set as watch flags on the source ImageModel. + * The contained flags will be those that this model can sort or filter by. */ + DatabaseFields::Set suggestedWatchFlags() const; + + ImageFilterSettings imageFilterSettings() const; + VersionImageFilterSettings versionImageFilterSettings() const; + GroupImageFilterSettings groupImageFilterSettings() const; + ImageSortSettings imageSortSettings() const; + + // group is identified by the id of its group leader + bool isGroupOpen(qlonglong group) const; + bool isAllGroupsOpen() const; + + /// Enables sending imageInfosAdded and imageInfosAboutToBeRemoved + void setSendImageInfoSignals(bool sendSignals); + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + virtual ImageFilterModel* imageFilterModel() const; + +public Q_SLOTS: + + /** Changes the current version image filter settings and refilters. */ + void setVersionImageFilterSettings(const VersionImageFilterSettings& settings); + + /** Changes the current version image filter settings and refilters. */ + void setGroupImageFilterSettings(const GroupImageFilterSettings& settings); + + /** Adjust the current ImageFilterSettings. + * Equivalent to retrieving the current filter settings, adjusting the parameter + * and calling setImageFilterSettings. + * Provided for convenience. + * It is encouraged to use setImageFilterSettings if you change more than one + * parameter at a time. + */ + void setDayFilter(const QList& days); + void setTagFilter(const QList& includedTags, const QList& excludedTags, + ImageFilterSettings::MatchingCondition matchingCond, bool showUnTagged, + const QList& clTagIds, const QList& plTagIds); + void setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded); + void setMimeTypeFilter(int mimeTypeFilter); + void setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition); + void setTextFilter(const SearchTextFilterSettings& settings); + + void setCategorizationMode(ImageSortSettings::CategorizationMode mode); + void setCategorizationSortOrder(ImageSortSettings::SortOrder order); + void setSortRole(ImageSortSettings::SortRole role); + void setSortOrder(ImageSortSettings::SortOrder order); + void setStringTypeNatural(bool natural); + void setUrlWhitelist(const QList urlList, const QString& id); + void setIdWhitelist(const QList& idList, const QString& id); + + void setVersionManagerSettings(const VersionManagerSettings& settings); + void setExceptionList(const QList& idlist, const QString& id); + + void setGroupOpen(qlonglong group, bool open); + void toggleGroupOpen(qlonglong group); + void setAllGroupsOpen(bool open); + + /** Changes the current image filter settings and refilters. */ + virtual void setImageFilterSettings(const ImageFilterSettings& settings); + + /** Changes the current image sort settings and resorts. */ + virtual void setImageSortSettings(const ImageSortSettings& settings); + +Q_SIGNALS: + + /// Signals that the set filter matches at least one index + void filterMatches(bool matches); + + /** Signals that the set text filter matches at least one entry. + If no text filter is set, this signal is emitted + with 'false' when filterMatches() is emitted. + */ + void filterMatchesForText(bool matchesByText); + + /** Emitted when the filter settings have been changed + (the model may not yet have been updated) + */ + void filterSettingsChanged(const ImageFilterSettings& settings); + + /** These signals need to be explicitly enabled with setSendImageInfoSignals() + */ + void imageInfosAdded(const QList& infos); + void imageInfosAboutToBeRemoved(const QList& infos); + +public: + + // Declared as public because of use in sub-classes. + class ImageFilterModelPrivate; + +protected: + + ImageFilterModelPrivate* const d_ptr; + +protected: + + ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent); + + virtual void setDirectSourceImageModel(ImageModel* model); + + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; + + virtual int compareCategories(const QModelIndex& left, const QModelIndex& right) const; + virtual bool subSortLessThan(const QModelIndex& left, const QModelIndex& right) const; + //virtual int categoryCount(const ImageInfo& info) const; + + /** Reimplement to customize category sorting, + * Return negative if category of left < category right, + * Return 0 if left and right are in the same category, else return positive. + */ + virtual int compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const; + + /** Reimplement to customize sorting. Do not take categories into account here. + */ + virtual bool infosLessThan(const ImageInfo& left, const ImageInfo& right) const; + + /** Returns a unique identifier for the category if info. The string need not be for user display. + */ + virtual QString categoryIdentifier(const ImageInfo& info) const; + +protected Q_SLOTS: + + void slotModelReset(); + void slotUpdateFilter(); + + void slotImageTagChange(const ImageTagChangeset& changeset); + void slotImageChange(const ImageChangeset& changeset); + + void slotRowsInserted(const QModelIndex& parent, int start, int end); + void slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); + +private: + + Q_DECLARE_PRIVATE(ImageFilterModel) +}; + +// ----------------------------------------------------------------------------------------------------- + +class DIGIKAM_DATABASE_EXPORT NoDuplicatesImageFilterModel : public ImageSortFilterModel +{ + Q_OBJECT + +public: + + explicit NoDuplicatesImageFilterModel(QObject* parent = 0); + +protected: + + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; +}; + +} // namespace Digikam + +Q_DECLARE_METATYPE(Digikam::ImageFilterModel*) + +#endif // IMAGEMODEL_H diff --git a/libs/database/models/imagefiltermodelpriv.cpp b/libs/database/models/imagefiltermodelpriv.cpp new file mode 100644 index 0000000..07d9e79 --- /dev/null +++ b/libs/database/models/imagefiltermodelpriv.cpp @@ -0,0 +1,258 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-05 + * Description : Qt item model for database entries + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * Copyright (C) 2011-2017 by Gilles Caulier + * Copyright (C) 2010 by Andi Clemens + * Copyright (C) 2011 by Michael G. Hansen + * Copyright (C) 2014 by Mohamed Anwer + * + * 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 "imagefiltermodelpriv.h" + +// Local includes + +#include "digikam_debug.h" +#include "imagefiltermodelthreads.h" + +namespace Digikam +{ + +ImageFilterModel::ImageFilterModelPrivate::ImageFilterModelPrivate() +{ + imageModel = 0; + version = 0; + lastDiscardVersion = 0; + sentOut = 0; + sentOutForReAdd = 0; + updateFilterTimer = 0; + needPrepare = false; + needPrepareComments = false; + needPrepareTags = false; + needPrepareGroups = false; + preparer = 0; + filterer = 0; + hasOneMatch = false; + hasOneMatchForText = false; + + setupWorkers(); +} + +ImageFilterModel::ImageFilterModelPrivate::~ImageFilterModelPrivate() +{ + // facilitate thread stopping + ++version; + preparer->deactivate(); + filterer->deactivate(); + delete preparer; + delete filterer; +} + +void ImageFilterModel::ImageFilterModelPrivate::init(ImageFilterModel* _q) +{ + q = _q; + + updateFilterTimer = new QTimer(this); + updateFilterTimer->setSingleShot(true); + updateFilterTimer->setInterval(250); + + connect(updateFilterTimer, SIGNAL(timeout()), + q, SLOT(slotUpdateFilter())); + + // inter-thread redirection + qRegisterMetaType("ImageFilterModelTodoPackage"); +} + +void ImageFilterModel::ImageFilterModelPrivate::preprocessInfos(const QList& infos, const QList& extraValues) +{ + infosToProcess(infos, extraValues, true); +} + +void ImageFilterModel::ImageFilterModelPrivate::processAddedInfos(const QList& infos, const QList& extraValues) +{ + // These have already been added, we just process them afterwards + infosToProcess(infos, extraValues, false); +} + +void ImageFilterModel::ImageFilterModelPrivate::setupWorkers() +{ + preparer = new ImageFilterModelPreparer(this); + filterer = new ImageFilterModelFilterer(this); + + // A package in constructed in infosToProcess. + // Normal flow is infosToProcess -> preparer::process -> filterer::process -> packageFinished. + // If no preparation is needed, the first step is skipped. + // If filter version changes, both will discard old package and send them to packageDiscarded. + + connect(this, SIGNAL(packageToPrepare(ImageFilterModelTodoPackage)), + preparer, SLOT(process(ImageFilterModelTodoPackage))); + + connect(this, SIGNAL(packageToFilter(ImageFilterModelTodoPackage)), + filterer, SLOT(process(ImageFilterModelTodoPackage))); + + connect(preparer, SIGNAL(processed(ImageFilterModelTodoPackage)), + filterer, SLOT(process(ImageFilterModelTodoPackage))); + + connect(filterer, SIGNAL(processed(ImageFilterModelTodoPackage)), + this, SLOT(packageFinished(ImageFilterModelTodoPackage))); + + connect(preparer, SIGNAL(discarded(ImageFilterModelTodoPackage)), + this, SLOT(packageDiscarded(ImageFilterModelTodoPackage))); + + connect(filterer, SIGNAL(discarded(ImageFilterModelTodoPackage)), + this, SLOT(packageDiscarded(ImageFilterModelTodoPackage))); +} + +void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList& infos) +{ + infosToProcess(infos, QList(), false); +} + +void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList& infos, const QList& extraValues, bool forReAdd) +{ + if (infos.isEmpty()) + { + return; + } + + filterer->schedule(); + + if (needPrepare) + { + preparer->schedule(); + } + + Q_ASSERT(extraValues.isEmpty() || infos.size() == extraValues.size()); + + // prepare and filter in chunks + const int size = infos.size(); + const int maxChunkSize = needPrepare ? PrepareChunkSize : FilterChunkSize; + const bool hasExtraValues = !extraValues.isEmpty(); + QList::const_iterator it = infos.constBegin(), end; + QList::const_iterator xit = extraValues.constBegin(), xend; + int index = 0; + QVector infoVector; + QVector extraValueVector; + + while (it != infos.constEnd()) + { + const int chunkSize = qMin(maxChunkSize, size - index); + infoVector.resize(chunkSize); + end = it + chunkSize; + qCopy(it, end, infoVector.begin()); + + if (hasExtraValues) + { + extraValueVector.resize(chunkSize); + xend = xit + chunkSize; + qCopy(xit, xend, extraValueVector.begin()); + xit = xend; + } + + it = end; + index += chunkSize; + + ++sentOut; + + if (forReAdd) + { + ++sentOutForReAdd; + } + + if (needPrepare) + { + emit packageToPrepare(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd)); + } + else + { + emit packageToFilter(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd)); + } + } +} + +void ImageFilterModel::ImageFilterModelPrivate::packageFinished(const ImageFilterModelTodoPackage& package) +{ + // check if it got discarded on the journey + if (package.version != version) + { + packageDiscarded(package); + return; + } + + // incorporate result + QHash::const_iterator it = package.filterResults.constBegin(); + + for (; it != package.filterResults.constEnd(); ++it) + { + filterResults.insert(it.key(), it.value()); + } + + // re-add if necessary + if (package.isForReAdd) + { + emit reAddImageInfos(package.infos.toList(), package.extraValues.toList()); + + if (sentOutForReAdd == 1) // last package + { + emit reAddingFinished(); + } + } + + // decrement counters + --sentOut; + + if (package.isForReAdd) + { + --sentOutForReAdd; + } + + // If all packages have returned, filtered and readded, and no more are expected, + // and there is need to tell the filter result to the view, do that + if (sentOut == 0 && sentOutForReAdd == 0 && !imageModel->isRefreshing()) + { + q->invalidate(); // use invalidate, not invalidateFilter only. Sorting may have changed as well. + emit (q->filterMatches(hasOneMatch)); + emit (q->filterMatchesForText(hasOneMatchForText)); + filterer->deactivate(); + preparer->deactivate(); + } +} + +void ImageFilterModel::ImageFilterModelPrivate::packageDiscarded(const ImageFilterModelTodoPackage& package) +{ + // Either, the model was reset, or the filter changed + // In the former case throw all away, in the latter case, recycle + if (package.version > lastDiscardVersion) + { + // Recycle packages: Send again with current version + // Do not increment sentOut or sentOutForReAdd here: it was not decremented! + + if (needPrepare) + { + emit packageToPrepare(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd)); + } + else + { + emit packageToFilter(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd)); + } + } +} + +} // namespace Digikam diff --git a/libs/database/models/imagefiltermodelpriv.h b/libs/database/models/imagefiltermodelpriv.h new file mode 100644 index 0000000..a9e3f22 --- /dev/null +++ b/libs/database/models/imagefiltermodelpriv.h @@ -0,0 +1,159 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-11 + * Description : Qt item model for database entries - private shared header + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * + * 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 IMAGEFILTERMODELPRIV_H +#define IMAGEFILTERMODELPRIV_H + +// Qt includes + +#include +#include +#include +#include +#include +#include +#include + +// Local includes + +#include "imageinfo.h" +#include "imagefiltermodel.h" + +#include "digikam_export.h" +// Yes, we need the EXPORT macro in a private header because +// this private header is shared across binary objects. +// This does NOT make this classes here any more public! + +namespace Digikam +{ + +const int PrepareChunkSize = 101; +const int FilterChunkSize = 2001; + +class ImageFilterModelTodoPackage +{ +public: + + ImageFilterModelTodoPackage() + : version(0), isForReAdd(false) + { + } + + ImageFilterModelTodoPackage(const QVector& infos, const QVector& extraValues, int version, bool isForReAdd) + : infos(infos), extraValues(extraValues), version(version), isForReAdd(isForReAdd) + { + } + + QVector infos; + QVector extraValues; + unsigned int version; + bool isForReAdd; + QHash filterResults; +}; + +// ------------------------------------------------------------------------------------------------ + +class ImageFilterModelPreparer; +class ImageFilterModelFilterer; + +class DIGIKAM_DATABASE_EXPORT ImageFilterModel::ImageFilterModelPrivate : public QObject +{ + Q_OBJECT + +public: + + ImageFilterModelPrivate(); + ~ImageFilterModelPrivate(); + + void init(ImageFilterModel* q); + void setupWorkers(); + void infosToProcess(const QList& infos); + void infosToProcess(const QList& infos, const QList& extraValues, bool forReAdd = true); + +public: + + ImageFilterModel* q; + + ImageModel* imageModel; + + ImageFilterSettings filter; + ImageSortSettings sorter; + VersionImageFilterSettings versionFilter; + GroupImageFilterSettings groupFilter; + + volatile unsigned int version; + unsigned int lastDiscardVersion; + unsigned int lastFilteredVersion; + int sentOut; + int sentOutForReAdd; + + QTimer* updateFilterTimer; + + bool needPrepare; + bool needPrepareComments; + bool needPrepareTags; + bool needPrepareGroups; + + QMutex mutex; + ImageFilterSettings filterCopy; + VersionImageFilterSettings versionFilterCopy; + GroupImageFilterSettings groupFilterCopy; + ImageFilterModelPreparer* preparer; + ImageFilterModelFilterer* filterer; + + QHash filterResults; + bool hasOneMatch; + bool hasOneMatchForText; + + QList prepareHooks; + +/* + QHash > categoryCountHashInt; + QHash > categoryCountHashString; + +public: + + void cacheCategoryCount(int id, qlonglong imageid) const + { const_cast(this)->categoryCountHashInt[id].insert(imageid); } + void cacheCategoryCount(const QString& id, qlonglong imageid) const + { const_cast(this)->categoryCountHashString[id].insert(imageid); } +*/ + +public Q_SLOTS: + + void preprocessInfos(const QList& infos, const QList& extraValues); + void processAddedInfos(const QList& infos, const QList& extraValues); + void packageFinished(const ImageFilterModelTodoPackage& package); + void packageDiscarded(const ImageFilterModelTodoPackage& package); + +Q_SIGNALS: + + void packageToPrepare(const ImageFilterModelTodoPackage& package); + void packageToFilter(const ImageFilterModelTodoPackage& package); + void reAddImageInfos(const QList& infos, const QList& extraValues); + void reAddingFinished(); +}; + +} // namespace Digikam + +#endif // IMAGEFILTERMODELPRIV_H diff --git a/libs/database/models/imagefiltermodelthreads.cpp b/libs/database/models/imagefiltermodelthreads.cpp new file mode 100644 index 0000000..aa5c462 --- /dev/null +++ b/libs/database/models/imagefiltermodelthreads.cpp @@ -0,0 +1,40 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-05 + * Description : Qt item model for database entries + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * Copyright (C) 2011-2017 by Gilles Caulier + * Copyright (C) 2010 by Andi Clemens + * Copyright (C) 2011 by Michael G. Hansen + * Copyright (C) 2014 by Mohamed Anwer + * + * 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 "imagefiltermodel.h" +#include "imagefiltermodelpriv.h" +#include "imagefiltermodelthreads.h" + +namespace Digikam +{ + +ImageFilterModelWorker::ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d) + : d(d) +{ +} + +} // namespace Digikam diff --git a/libs/database/models/imagefiltermodelthreads.h b/libs/database/models/imagefiltermodelthreads.h new file mode 100644 index 0000000..83fa987 --- /dev/null +++ b/libs/database/models/imagefiltermodelthreads.h @@ -0,0 +1,100 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-11 + * Description : Qt item model for database entries - private header + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * + * 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 IMAGEFILTERMODELTHREADS_H +#define IMAGEFILTERMODELTHREADS_H + +// Qt includes + +#include + +// Local includes + +#include "digikam_export.h" +#include "workerobject.h" + +namespace Digikam +{ + +class DIGIKAM_DATABASE_EXPORT ImageFilterModelWorker : public WorkerObject +{ + Q_OBJECT + +public: + + explicit ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d); + + bool checkVersion(const ImageFilterModelTodoPackage& package) + { + return d->version == package.version; + } + +public Q_SLOTS: + + virtual void process(ImageFilterModelTodoPackage package) = 0; + +Q_SIGNALS: + + void processed(const ImageFilterModelTodoPackage& package); + void discarded(const ImageFilterModelTodoPackage& package); + +protected: + + ImageFilterModel::ImageFilterModelPrivate* d; +}; + +// ----------------------------------------------------------------------------------------- + +class DIGIKAM_DATABASE_EXPORT ImageFilterModelPreparer : public ImageFilterModelWorker +{ + Q_OBJECT + +public: + + explicit ImageFilterModelPreparer(ImageFilterModel::ImageFilterModelPrivate* const d) + : ImageFilterModelWorker(d) + { + } + + void process(ImageFilterModelTodoPackage package); +}; + +// ---------------------------------------------------------------------------------------- + +class DIGIKAM_DATABASE_EXPORT ImageFilterModelFilterer : public ImageFilterModelWorker +{ + Q_OBJECT + +public: + + explicit ImageFilterModelFilterer(ImageFilterModel::ImageFilterModelPrivate* const d) + : ImageFilterModelWorker(d) + { + } + + void process(ImageFilterModelTodoPackage package); +}; + +} // namespace Digikam + +#endif // IMAGEFILTERMODELTHREADS_H diff --git a/libs/database/models/imagefiltersettings.cpp b/libs/database/models/imagefiltersettings.cpp new file mode 100644 index 0000000..b61e7f9 --- /dev/null +++ b/libs/database/models/imagefiltersettings.cpp @@ -0,0 +1,952 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-05 + * Description : Filter values for use with ImageFilterModel + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * Copyright (C) 2011-2017 by Gilles Caulier + * Copyright (C) 2010 by Andi Clemens + * Copyright (C) 2011 by Michael G. Hansen + * Copyright (C) 2014 by Mohamed Anwer + * + * 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 "imagefiltersettings.h" + +// C++ includes + +#include + +// Qt includes + +#include + +// Local includes + +#include "digikam_debug.h" +#include "coredbfields.h" +#include "digikam_globals.h" +#include "imageinfo.h" +#include "tagscache.h" +#include "versionmanagersettings.h" + +namespace Digikam +{ + +ImageFilterSettings::ImageFilterSettings() +{ + m_untaggedFilter = false; + m_isUnratedExcluded = false; + m_ratingFilter = 0; + m_mimeTypeFilter = MimeFilter::AllFiles; + m_ratingCond = GreaterEqualCondition; + m_matchingCond = OrCondition; + m_geolocationCondition = GeolocationNoFilter; +} + +DatabaseFields::Set ImageFilterSettings::watchFlags() const +{ + DatabaseFields::Set set; + + if (isFilteringByDay()) + { + set |= DatabaseFields::CreationDate; + } + + if (isFilteringByText()) + { + set |= DatabaseFields::Name; + set |= DatabaseFields::Comment; + } + + if (isFilteringByRating()) + { + set |= DatabaseFields::Rating; + } + + if (isFilteringByTypeMime()) + { + set |= DatabaseFields::Category; + set |= DatabaseFields::Format; + } + + if (isFilteringByGeolocation()) + { + set |= DatabaseFields::ImagePositionsAll; + } + + if (isFilteringByColorLabels()) + { + set |= DatabaseFields::ColorLabel; + } + + if (isFilteringByPickLabels()) + { + set |= DatabaseFields::PickLabel; + } + + return set; +} + +bool ImageFilterSettings::isFilteringByDay() const +{ + if (!m_dayFilter.isEmpty()) + { + return true; + } + + return false; +} + +bool ImageFilterSettings::isFilteringByTags() const +{ + if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty() || m_untaggedFilter) + { + return true; + } + + return false; +} + +bool ImageFilterSettings::isFilteringByColorLabels() const +{ + if (!m_colorLabelTagFilter.isEmpty()) + { + return true; + } + + return false; +} + +bool ImageFilterSettings::isFilteringByPickLabels() const +{ + if (!m_pickLabelTagFilter.isEmpty()) + { + return true; + } + + return false; +} + +bool ImageFilterSettings::isFilteringByText() const +{ + if (!m_textFilterSettings.text.isEmpty()) + { + return true; + } + + return false; +} + +bool ImageFilterSettings::isFilteringByTypeMime() const +{ + if (m_mimeTypeFilter != MimeFilter::AllFiles) + { + return true; + } + + return false; +} + +bool ImageFilterSettings::isFilteringByGeolocation() const +{ + return (m_geolocationCondition != GeolocationNoFilter); +} + +bool ImageFilterSettings::isFilteringByRating() const +{ + if (m_ratingFilter != 0 || m_ratingCond != GreaterEqualCondition || m_isUnratedExcluded) + { + return true; + } + + return false; +} + +bool ImageFilterSettings::isFilteringInternally() const +{ + return (isFiltering() || !m_urlWhitelists.isEmpty() || !m_idWhitelists.isEmpty()); +} + +bool ImageFilterSettings::isFiltering() const +{ + return isFilteringByDay() || + isFilteringByTags() || + isFilteringByText() || + isFilteringByRating() || + isFilteringByTypeMime() || + isFilteringByColorLabels() || + isFilteringByPickLabels() || + isFilteringByGeolocation(); +} + +void ImageFilterSettings::setDayFilter(const QList& days) +{ + m_dayFilter.clear(); + + for (QList::const_iterator it = days.constBegin(); it != days.constEnd(); ++it) + { + m_dayFilter.insert(*it, true); + } +} + +void ImageFilterSettings::setTagFilter(const QList& includedTags, + const QList& excludedTags, + MatchingCondition matchingCondition, + bool showUnTagged, + const QList& clTagIds, + const QList& plTagIds) +{ + m_includeTagFilter = includedTags; + m_excludeTagFilter = excludedTags; + m_matchingCond = matchingCondition; + m_untaggedFilter = showUnTagged; + m_colorLabelTagFilter = clTagIds; + m_pickLabelTagFilter = plTagIds; +} + +void ImageFilterSettings::setRatingFilter(int rating, RatingCondition ratingCondition, bool isUnratedExcluded) +{ + m_ratingFilter = rating; + m_ratingCond = ratingCondition; + m_isUnratedExcluded = isUnratedExcluded; +} + +void ImageFilterSettings::setMimeTypeFilter(int mime) +{ + m_mimeTypeFilter = (MimeFilter::TypeMimeFilter)mime; +} + +void ImageFilterSettings::setGeolocationFilter(const GeolocationCondition& condition) +{ + m_geolocationCondition = condition; +} + +void ImageFilterSettings::setTextFilter(const SearchTextFilterSettings& settings) +{ + m_textFilterSettings = settings; +} + +void ImageFilterSettings::setTagNames(const QHash& hash) +{ + m_tagNameHash = hash; +} + +void ImageFilterSettings::setAlbumNames(const QHash& hash) +{ + m_albumNameHash = hash; +} + +void ImageFilterSettings::setUrlWhitelist(const QList& urlList, const QString& id) +{ + if (urlList.isEmpty()) + { + m_urlWhitelists.remove(id); + } + else + { + m_urlWhitelists.insert(id, urlList); + } +} + +void ImageFilterSettings::setIdWhitelist(const QList& idList, const QString& id) +{ + if (idList.isEmpty()) + { + m_idWhitelists.remove(id); + } + else + { + m_idWhitelists.insert(id, idList); + } +} + +template +bool containsAnyOf(const ContainerA& listA, const ContainerB& listB) +{ + foreach (const typename ContainerA::value_type& a, listA) + { + if (listB.contains(a)) + { + return true; + } + } + return false; +} + +template +bool containsNoneOfExcept(const ContainerA& list, const ContainerB& noneOfList, const Value& exception) +{ + foreach (const typename ContainerB::value_type& n, noneOfList) + { + if (n != exception && list.contains(n)) + { + return false; + } + } + return true; +} + +bool ImageFilterSettings::matches(const ImageInfo& info, bool* const foundText) const +{ + if (foundText) + { + *foundText = false; + } + + if (!isFilteringInternally()) + { + return true; + } + + bool match = false; + + if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty()) + { + QList tagIds = info.tagIds(); + QList::const_iterator it; + + match = m_includeTagFilter.isEmpty(); + + if (m_matchingCond == OrCondition) + { + for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it) + { + if (tagIds.contains(*it)) + { + match = true; + break; + } + } + + match |= (m_untaggedFilter && tagIds.isEmpty()); + } + else // AND matching condition... + { + // m_untaggedFilter and non-empty tag filter, combined with AND, is logically no match + if (!m_untaggedFilter) + { + for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it) + { + if (!tagIds.contains(*it)) + { + break; + } + } + + if (it == m_includeTagFilter.end()) + { + match = true; + } + } + } + + for (it = m_excludeTagFilter.begin(); it != m_excludeTagFilter.end(); ++it) + { + if (tagIds.contains(*it)) + { + match = false; + break; + } + } + } + else if (m_untaggedFilter) + { + match = !TagsCache::instance()->containsPublicTags(info.tagIds()); + } + else + { + match = true; + } + + //-- Filter by pick labels ------------------------------------------------ + + if (!m_pickLabelTagFilter.isEmpty()) + { + QList tagIds = info.tagIds(); + bool matchPL = false; + + if (containsAnyOf(m_pickLabelTagFilter, tagIds)) + { + matchPL = true; + } + else if (!matchPL) + { + int noPickLabelTagId = TagsCache::instance()->tagForPickLabel(NoPickLabel); + + if (m_pickLabelTagFilter.contains(noPickLabelTagId)) + { + // Searching for "has no ColorLabel" requires special handling: + // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag + matchPL = containsNoneOfExcept(tagIds, TagsCache::instance()->pickLabelTags(), noPickLabelTagId); + } + } + + match &= matchPL; + } + + //-- Filter by color labels ------------------------------------------------ + + if (!m_colorLabelTagFilter.isEmpty()) + { + QList tagIds = info.tagIds(); + bool matchCL = false; + + if (containsAnyOf(m_colorLabelTagFilter, tagIds)) + { + matchCL = true; + } + else if (!matchCL) + { + int noColorLabelTagId = TagsCache::instance()->tagForColorLabel(NoColorLabel); + + if (m_colorLabelTagFilter.contains(noColorLabelTagId)) + { + // Searching for "has no ColorLabel" requires special handling: + // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag + matchCL = containsNoneOfExcept(tagIds, TagsCache::instance()->colorLabelTags(), noColorLabelTagId); + } + } + + match &= matchCL; + } + + //-- Filter by date ----------------------------------------------------------- + + if (!m_dayFilter.isEmpty()) + { + match &= m_dayFilter.contains(QDateTime(info.dateTime().date(), QTime())); + } + + //-- Filter by rating --------------------------------------------------------- + + if (m_ratingFilter >= 0) + { + // for now we treat -1 (no rating) just like a rating of 0. + int rating = info.rating(); + + if (rating == -1) + { + rating = 0; + } + + if(m_isUnratedExcluded && rating == 0) + { + match = false; + } + else + { + if (m_ratingCond == GreaterEqualCondition) + { + // If the rating is not >=, i.e it is <, then it does not match. + if (rating < m_ratingFilter) + { + match = false; + } + } + else if (m_ratingCond == EqualCondition) + { + // If the rating is not =, i.e it is !=, then it does not match. + if (rating != m_ratingFilter) + { + match = false; + } + } + else + { + // If the rating is not <=, i.e it is >, then it does not match. + if (rating > m_ratingFilter) + { + match = false; + } + } + } + } + + // -- Filter by mime type ----------------------------------------------------- + + switch (m_mimeTypeFilter) + { + // info.format is a standardized string: Only one possibility per mime type + case MimeFilter::ImageFiles: + { + if (info.category() != DatabaseItem::Image) + { + match = false; + } + + break; + } + case MimeFilter::JPGFiles: + { + if (info.format() != QLatin1String("JPG")) + { + match = false; + } + + break; + } + case MimeFilter::PNGFiles: + { + if (info.format() != QLatin1String("PNG")) + { + match = false; + } + + break; + } + case MimeFilter::TIFFiles: + { + if (info.format() != QLatin1String("TIFF")) + { + match = false; + } + + break; + } + case MimeFilter::DNGFiles: + { + if (info.format() != QLatin1String("RAW-DNG")) + { + match = false; + } + + break; + } + case MimeFilter::NoRAWFiles: + { + if (info.format().startsWith(QLatin1String("RAW"))) + { + match = false; + } + + break; + } + case MimeFilter::RAWFiles: + { + if (!info.format().startsWith(QLatin1String("RAW"))) + { + match = false; + } + + break; + } + case MimeFilter::MoviesFiles: + { + if (info.category() != DatabaseItem::Video) + { + match = false; + } + + break; + } + case MimeFilter::AudioFiles: + { + if (info.category() != DatabaseItem::Audio) + { + match = false; + } + + break; + } + case MimeFilter::RasterFiles: + { + if (info.format() != QLatin1String("PSD") && // Adobe Photoshop Document + info.format() != QLatin1String("PSB") && // Adobe Photoshop Big + info.format() != QLatin1String("XCF") && // Gimp + info.format() != QLatin1String("KRA") && // Krita + info.format() != QLatin1String("ORA") // Open Raster + ) + { + match = false; + } + + break; + } + default: + { + // All Files: do nothing... + break; + } + } + + //-- Filter by geolocation ---------------------------------------------------- + + if (m_geolocationCondition!=GeolocationNoFilter) + { + if (m_geolocationCondition==GeolocationNoCoordinates) + { + if (info.hasCoordinates()) + { + match = false; + } + } + else if (m_geolocationCondition==GeolocationHasCoordinates) + { + if (!info.hasCoordinates()) + { + match = false; + } + } + } + + //-- Filter by text ----------------------------------------------------------- + + if (!m_textFilterSettings.text.isEmpty()) + { + bool textMatch = false; + + // Image name + if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageName && + info.name().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) + { + textMatch = true; + } + + // Image title + if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageTitle && + info.title().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) + { + textMatch = true; + } + + // Image comment + if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageComment && + info.comment().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) + { + textMatch = true; + } + + // Tag names + foreach(int id, info.tagIds()) + { + if (m_textFilterSettings.textFields & SearchTextFilterSettings::TagName && + m_tagNameHash.value(id).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) + { + textMatch = true; + } + } + + // Album names + if (m_textFilterSettings.textFields & SearchTextFilterSettings::AlbumName && + m_albumNameHash.value(info.albumId()).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) + { + textMatch = true; + } + + // Image Aspect Ratio + if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageAspectRatio) + { + QRegExp expRatio (QLatin1String("^\\d+:\\d+$")); + QRegExp expFloat (QLatin1String("^\\d+(.\\d+)?$")); + + if (expRatio.indexIn(m_textFilterSettings.text) > -1 && m_textFilterSettings.text.contains(QRegExp(QLatin1String(":\\d+")))) + { + QString trimmedTextFilterSettingsText = m_textFilterSettings.text; + QStringList numberStringList = trimmedTextFilterSettingsText.split(QLatin1String(":"), QString::SkipEmptyParts); + + if (numberStringList.length() == 2) + { + QString numString = (QString)numberStringList.at(0), denomString = (QString)numberStringList.at(1); + bool canConverseNum = false; + bool canConverseDenom = false; + int num = numString.toInt(&canConverseNum, 10), denom = denomString.toInt(&canConverseDenom, 10); + + if (canConverseNum && canConverseDenom) + { + if (fabs(info.aspectRatio() - (double)num / denom) < 0.1) + textMatch = true; + } + } + } + else if (expFloat.indexIn(m_textFilterSettings.text) > -1) + { + QString trimmedTextFilterSettingsText = m_textFilterSettings.text; + bool canConverse = false; + double ratio = trimmedTextFilterSettingsText.toDouble(&canConverse); + + if (canConverse) + { + if (fabs(info.aspectRatio() - ratio) < 0.1) + textMatch = true; + } + } + } + + // Image Pixel Size + // See bug #341053 for details. + + if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImagePixelSize) + { + QSize size = info.dimensions(); + int pixelSize = size.height()*size.width(); + QString text = m_textFilterSettings.text; + + if(text.contains(QRegExp(QLatin1String("^>\\d{1,15}$"))) && pixelSize > (text.remove(0,1)).toInt()) + { + textMatch = true; + } + else if(text.contains(QRegExp(QLatin1String("^<\\d{1,15}$"))) && pixelSize < (text.remove(0,1)).toInt()) + { + textMatch = true; + } + else if(text.contains(QRegExp(QLatin1String("^\\d+$"))) && pixelSize == text.toInt()) + { + textMatch = true; + } + } + + match &= textMatch; + + if (foundText) + { + *foundText = textMatch; + } + } + + // -- filter by URL-whitelists ------------------------------------------------ + // NOTE: whitelists are always AND for now. + + if (match) + { + const QUrl url = info.fileUrl(); + + for (QHash>::const_iterator it = m_urlWhitelists.constBegin(); + it!=m_urlWhitelists.constEnd(); ++it) + { + match = it->contains(url); + + if (!match) + { + break; + } + } + } + + if (match) + { + const qlonglong id = info.id(); + + for (QHash >::const_iterator it = m_idWhitelists.constBegin(); + it!=m_idWhitelists.constEnd(); ++it) + { + match = it->contains(id); + + if (!match) + { + break; + } + } + } + + return match; +} + +// ------------------------------------------------------------------------------------------------- + +VersionImageFilterSettings::VersionImageFilterSettings() +{ + m_includeTagFilter = 0; + m_exceptionTagFilter = 0; +} + +VersionImageFilterSettings::VersionImageFilterSettings(const VersionManagerSettings& settings) +{ + setVersionManagerSettings(settings); +} + +bool VersionImageFilterSettings::operator==(const VersionImageFilterSettings& other) const +{ + return m_excludeTagFilter == other.m_excludeTagFilter && + m_exceptionLists == other.m_exceptionLists; +} + +bool VersionImageFilterSettings::matches(const ImageInfo& info) const +{ + if (!isFiltering()) + { + return true; + } + + const qlonglong id = info.id(); + + for (QHash >::const_iterator it = m_exceptionLists.constBegin(); + it != m_exceptionLists.constEnd(); ++it) + { + if (it->contains(id)) + { + return true; + } + } + + bool match = true; + QList tagIds = info.tagIds(); + + if (!tagIds.contains(m_includeTagFilter)) + { + for (QList::const_iterator it = m_excludeTagFilter.begin(); + it != m_excludeTagFilter.end(); ++it) + { + if (tagIds.contains(*it)) + { + match = false; + break; + } + } + } + + if (!match) + { + if (tagIds.contains(m_exceptionTagFilter)) + { + match = true; + } + } + + return match; +} + +bool VersionImageFilterSettings::isHiddenBySettings(const ImageInfo& info) const +{ + QList tagIds = info.tagIds(); + + foreach(int tagId, m_excludeTagFilter) + { + if (tagIds.contains(tagId)) + { + return true; + } + } + + return false; +} + +bool VersionImageFilterSettings::isExemptedBySettings(const ImageInfo& info) const +{ + return info.tagIds().contains(m_exceptionTagFilter); +} + +void VersionImageFilterSettings::setVersionManagerSettings(const VersionManagerSettings& settings) +{ + m_excludeTagFilter.clear(); + + if (!settings.enabled) + { + return; + } + + if (!(settings.showInViewFlags & VersionManagerSettings::ShowOriginal)) + { + m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion()); + } + + if (!(settings.showInViewFlags & VersionManagerSettings::ShowIntermediates)) + { + m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::intermediateVersion()); + } + + m_includeTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::currentVersion()); + m_exceptionTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::versionAlwaysVisible()); +} + +void VersionImageFilterSettings::setExceptionList(const QList& idList, const QString& id) +{ + if (idList.isEmpty()) + { + m_exceptionLists.remove(id); + } + else + { + m_exceptionLists.insert(id, idList); + } +} + +bool VersionImageFilterSettings::isFiltering() const +{ + return !m_excludeTagFilter.isEmpty(); +} + +bool VersionImageFilterSettings::isFilteringByTags() const +{ + return isFiltering(); +} + +// ------------------------------------------------------------------------------------------------- + +GroupImageFilterSettings::GroupImageFilterSettings() + : m_allOpen(false) +{ +} + +bool GroupImageFilterSettings::operator==(const GroupImageFilterSettings& other) const +{ + return (m_allOpen == other.m_allOpen && + m_openGroups == other.m_openGroups); +} + +bool GroupImageFilterSettings::matches(const ImageInfo& info) const +{ + if (m_allOpen) + { + return true; + } + + if (info.isGrouped()) + { + return m_openGroups.contains(info.groupImage().id()); + } + return true; +} + +void GroupImageFilterSettings::setOpen(qlonglong group, bool open) +{ + if (open) + { + m_openGroups << group; + } + else + { + m_openGroups.remove(group); + } +} + +bool GroupImageFilterSettings::isOpen(qlonglong group) const +{ + return m_openGroups.contains(group); +} + +void GroupImageFilterSettings::setAllOpen(bool open) +{ + m_allOpen = open; +} + +bool GroupImageFilterSettings::isAllOpen() const +{ + return m_allOpen; +} + +bool GroupImageFilterSettings::isFiltering() const +{ + return !m_allOpen; +} + +DatabaseFields::Set GroupImageFilterSettings::watchFlags() const +{ + return DatabaseFields::ImageRelations; +} + +} // namespace Digikam diff --git a/libs/database/models/imagefiltersettings.h b/libs/database/models/imagefiltersettings.h new file mode 100644 index 0000000..0e7beae --- /dev/null +++ b/libs/database/models/imagefiltersettings.h @@ -0,0 +1,349 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-05 + * Description : Filter values for use with ImageFilterModel + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * Copyright (C) 2011-2017 by Gilles Caulier + * Copyright (C) 2010 by Andi Clemens + * Copyright (C) 2011 by Michael G. Hansen + * Copyright (C) 2014 by Mohamed Anwer + * + * 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 IMAGEFILTERSETTINGS_H +#define IMAGEFILTERSETTINGS_H + +// Qt includes + +#include +#include +#include +#include +#include +#include + +// Local includes + +#include "searchtextbar.h" +#include "mimefilter.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class ImageInfo; +class VersionManagerSettings; + +namespace DatabaseFields +{ + class Set; +} + +// --------------------------------------------------------------------------------------- + +class DIGIKAM_DATABASE_EXPORT SearchTextFilterSettings : public SearchTextSettings +{ + +public: + + enum TextFilterFields + { + None = 0x00, + ImageName = 0x01, + ImageTitle = 0x02, + ImageComment = 0x04, + TagName = 0x08, + AlbumName = 0x10, + ImageAspectRatio = 0x20, + ImagePixelSize = 0x40, + All = ImageName | ImageTitle | ImageComment | TagName | AlbumName | ImageAspectRatio | ImagePixelSize + }; + +public: + + SearchTextFilterSettings() + { + textFields = None; + } + + explicit SearchTextFilterSettings(const SearchTextSettings& settings) + { + caseSensitive = settings.caseSensitive; + text = settings.text; + textFields = None; + } + + TextFilterFields textFields; +}; + +// --------------------------------------------------------------------------------------- + +class DIGIKAM_DATABASE_EXPORT ImageFilterSettings +{ +public: + + ImageFilterSettings(); + + /** + * Returns true if the given ImageInfo matches the filter criteria. + * Optionally, foundText is set to true if it matched by text search. + */ + bool matches(const ImageInfo& info, bool* const foundText = 0) const; + +public: + + /// --- Tags filter --- + + /// Possible logical matching condition used to sort tags id. + enum MatchingCondition + { + OrCondition, + AndCondition + }; + + void setTagFilter(const QList& includedTags, + const QList& excludedTags, + MatchingCondition matchingCond, + bool showUnTagged, + const QList& clTagIds, + const QList& plTagIds); + +public: + + /// --- Rating filter --- + + /// Possible conditions used to filter rating: >=, =, <= + enum RatingCondition + { + GreaterEqualCondition, + EqualCondition, + LessEqualCondition + }; + + void setRatingFilter(int rating, RatingCondition ratingCond, bool isUnratedExcluded); + +public: + + /// --- Date filter --- + void setDayFilter(const QList& days); + +public: + + /// --- Text filter --- + void setTextFilter(const SearchTextFilterSettings& settings); + void setTagNames(const QHash& tagNameHash); + void setAlbumNames(const QHash& albumNameHash); + +public: + + /// --- Mime filter --- + void setMimeTypeFilter(int mimeTypeFilter); + +public: + + /// --- Geolocation filter + enum GeolocationCondition + { + GeolocationNoFilter = 0, + GeolocationNoCoordinates = 1 << 1, + GeolocationHasCoordinates = 1 << 2 + }; + + void setGeolocationFilter(const GeolocationCondition& condition); + +public: + + /// Returns if the day is a filter criteria + bool isFilteringByDay() const; + + /// Returns if the type mime is a filter criteria + bool isFilteringByTypeMime() const; + + /// Returns whether geolocation is a filter criteria + bool isFilteringByGeolocation() const; + + /// Returns if the rating is a filter criteria + bool isFilteringByRating() const; + + /// Returns if the pick labels is a filter criteria + bool isFilteringByPickLabels() const; + + /// Returns if the color labels is a filter criteria + bool isFilteringByColorLabels() const; + + /// Returns if the tag is a filter criteria + bool isFilteringByTags() const; + + /// Returns if the text (including comment) is a filter criteria + bool isFilteringByText() const; + + /// Returns if images will be filtered by these criteria at all + bool isFiltering() const; + +public: + + /// --- URL whitelist filter + void setUrlWhitelist(const QList& urlList, const QString& id); + +public: + + /// --- ID whitelist filter + void setIdWhitelist(const QList& idList, const QString& id); + +public: + + /// --- Change notification --- + + /** Returns database fields a change in which would affect the current filtering. + * To find out if an image tag change affects filtering, test isFilteringByTags(). + * The text filter will also be affected by changes in tags and album names. + */ + DatabaseFields::Set watchFlags() const; + +private: + + /** + * @brief Returns whether some internal filtering (whitelist by id or URL) or normal filtering is going on + */ + bool isFilteringInternally() const; + +private: + + /// --- Tags filter --- + bool m_untaggedFilter; + QList m_includeTagFilter; + QList m_excludeTagFilter; + MatchingCondition m_matchingCond; + QList m_colorLabelTagFilter; + QList m_pickLabelTagFilter; + + /// --- Rating filter --- + int m_ratingFilter; + RatingCondition m_ratingCond; + bool m_isUnratedExcluded; + + /// --- Date filter --- + QMap m_dayFilter; + + /// --- Text filter --- + SearchTextFilterSettings m_textFilterSettings; + + /// Helpers for text search: Set these if you want to search album or tag names with text search + QHash m_tagNameHash; + QHash m_albumNameHash; + + /// --- Mime filter --- + MimeFilter::TypeMimeFilter m_mimeTypeFilter; + + /// --- Geolocation filter + GeolocationCondition m_geolocationCondition; + + /// --- URL whitelist filter + QHash> m_urlWhitelists; + + /// --- ID whitelist filter + QHash > m_idWhitelists; +}; + +// --------------------------------------------------------------------------------------- + +class DIGIKAM_DATABASE_EXPORT VersionImageFilterSettings +{ +public: + + VersionImageFilterSettings(); + explicit VersionImageFilterSettings(const VersionManagerSettings& settings); + + bool operator==(const VersionImageFilterSettings& other) const; + + /** + * Returns true if the given ImageInfo matches the filter criteria. + */ + bool matches(const ImageInfo& info) const; + + bool isHiddenBySettings(const ImageInfo& info) const; + bool isExemptedBySettings(const ImageInfo& info) const; + + /// --- Tags filter --- + + void setVersionManagerSettings(const VersionManagerSettings& settings); + + /** + * Add list with exceptions: These images will be exempted from filtering by this filter + */ + void setExceptionList(const QList& idlist, const QString& id); + + /// Returns if images will be filtered by these criteria at all + bool isFiltering() const; + + /// Returns if the tag is a filter criteria + bool isFilteringByTags() const; + + /// DatabaseFields::Set watchFlags() const: Would return 0 + +protected: + + QList m_excludeTagFilter; + int m_includeTagFilter; + int m_exceptionTagFilter; + QHash > m_exceptionLists; +}; + +// --------------------------------------------------------------------------------------- + +class DIGIKAM_DATABASE_EXPORT GroupImageFilterSettings +{ +public: + + GroupImageFilterSettings(); + + bool operator==(const GroupImageFilterSettings& other) const; + + /** + * Returns true if the given ImageInfo matches the filter criteria. + */ + bool matches(const ImageInfo& info) const; + + /** + * Open or close a group. + */ + void setOpen(qlonglong group, bool open); + bool isOpen(qlonglong group) const; + + /** + * Open all groups + */ + void setAllOpen(bool open); + bool isAllOpen() const; + + /// Returns if images will be filtered by these criteria at all + bool isFiltering() const; + + DatabaseFields::Set watchFlags() const; + +protected: + + bool m_allOpen; + QSet m_openGroups; +}; + +} // namespace Digikam + +Q_DECLARE_METATYPE(Digikam::ImageFilterSettings::GeolocationCondition) + +#endif // IMAGEFILTERSETTINGS_H diff --git a/libs/database/models/imagelistmodel.cpp b/libs/database/models/imagelistmodel.cpp new file mode 100644 index 0000000..fafce34 --- /dev/null +++ b/libs/database/models/imagelistmodel.cpp @@ -0,0 +1,70 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2010-12-06 + * Description : An image model based on a static list + * + * Copyright (C) 2010-2011 by Marcel Wiesweg + * + * 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 "imagelistmodel.h" + +// Local includes + +#include "digikam_debug.h" +#include "coredbaccess.h" +#include "coredbchangesets.h" +#include "coredbwatch.h" +#include "imageinfo.h" +#include "imageinfolist.h" + +namespace Digikam +{ + +ImageListModel::ImageListModel(QObject* parent) + : ImageThumbnailModel(parent) +{ + connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)), + this, SLOT(slotCollectionImageChange(CollectionImageChangeset))); +} + +ImageListModel::~ImageListModel() +{ +} + +void ImageListModel::slotCollectionImageChange(const CollectionImageChangeset& changeset) +{ + if (isEmpty()) + { + return; + } + + switch (changeset.operation()) + { + case CollectionImageChangeset::Added: + break; + case CollectionImageChangeset::Removed: + case CollectionImageChangeset::RemovedAll: + removeImageInfos(ImageInfoList(changeset.ids())); + break; + + default: + break; + } +} + +} // namespace Digikam diff --git a/libs/database/models/imagelistmodel.h b/libs/database/models/imagelistmodel.h new file mode 100644 index 0000000..a225b1b --- /dev/null +++ b/libs/database/models/imagelistmodel.h @@ -0,0 +1,63 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2010-12-06 + * Description : An image model based on a static list + * + * Copyright (C) 2010-2011 by Marcel Wiesweg + * + * 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 IMAGELISTMODEL_H +#define IMAGELISTMODEL_H + +// Local includes + +#include "imagethumbnailmodel.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class ImageChangeset; +class CollectionImageChangeset; + +class DIGIKAM_DATABASE_EXPORT ImageListModel : public ImageThumbnailModel +{ + Q_OBJECT + +public: + + explicit ImageListModel(QObject* parent = 0); + ~ImageListModel(); + + // NOTE: necessary methods to add and remove ImageInfos to the model are inherited from ImageModel + +Q_SIGNALS: + + /** + * Emitted when images are removed from the model because they are removed in the database + */ + void imageInfosRemoved(const QList& infos); + +protected Q_SLOTS: + + void slotCollectionImageChange(const CollectionImageChangeset& changeset); +}; + +} // namespace Digikam + +#endif // IMAGELISTMODEL_H diff --git a/libs/database/models/imagemodel.cpp b/libs/database/models/imagemodel.cpp new file mode 100644 index 0000000..41b43cf --- /dev/null +++ b/libs/database/models/imagemodel.cpp @@ -0,0 +1,1368 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-05 + * Description : Qt item model for database entries + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * + * 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 "imagemodel.h" + +// Qt includes + +#include +#include + +// Local includes + +#include "digikam_debug.h" +#include "coredbchangesets.h" +#include "coredbfields.h" +#include "coredbwatch.h" +#include "imageinfo.h" +#include "imageinfolist.h" +#include "abstractitemdragdrophandler.h" + +namespace Digikam +{ + +class ImageModel::Private +{ +public: + + Private() + { + preprocessor = 0; + keepFilePathCache = false; + sendRemovalSignals = false; + incrementalUpdater = 0; + refreshing = false; + reAdding = false; + incrementalRefreshRequested = false; + } + + ImageInfoList infos; + QList extraValues; + QHash idHash; + + bool keepFilePathCache; + QHash filePathHash; + + bool sendRemovalSignals; + + QObject* preprocessor; + bool refreshing; + bool reAdding; + bool incrementalRefreshRequested; + + DatabaseFields::Set watchFlags; + + class ImageModelIncrementalUpdater* incrementalUpdater; + + ImageInfoList pendingInfos; + QList pendingExtraValues; + + 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 ImageModelIncrementalUpdater +{ +public: + + explicit ImageModelIncrementalUpdater(ImageModel::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; +}; + +ImageModel::ImageModel(QObject* parent) + : QAbstractListModel(parent), + d(new Private) +{ + connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)), + this, SLOT(slotImageChange(ImageChangeset))); + + connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)), + this, SLOT(slotImageTagChange(ImageTagChangeset))); +} + +ImageModel::~ImageModel() +{ + delete d->incrementalUpdater; + delete d; +} + +// ------------ Access methods ------------- + +void ImageModel::setKeepsFilePathCache(bool keepCache) +{ + d->keepFilePathCache = keepCache; +} + +bool ImageModel::keepsFilePathCache() const +{ + return d->keepFilePathCache; +} + +bool ImageModel::isEmpty() const +{ + return d->infos.isEmpty(); +} + +void ImageModel::setWatchFlags(const DatabaseFields::Set& set) +{ + d->watchFlags = set; +} + +ImageInfo ImageModel::imageInfo(const QModelIndex& index) const +{ + if (!d->isValid(index)) + { + return ImageInfo(); + } + + return d->infos.at(index.row()); +} + +ImageInfo& ImageModel::imageInfoRef(const QModelIndex& index) const +{ + return d->infos[index.row()]; +} + +qlonglong ImageModel::imageId(const QModelIndex& index) const +{ + if (!d->isValid(index)) + { + return 0; + } + + return d->infos.at(index.row()).id(); +} + +QList ImageModel::imageInfos(const QList& indexes) const +{ + QList infos; + + foreach(const QModelIndex& index, indexes) + { + infos << imageInfo(index); + } + + return infos; +} + +QList ImageModel::imageIds(const QList& indexes) const +{ + QList ids; + + foreach(const QModelIndex& index, indexes) + { + ids << imageId(index); + } + + return ids; +} + +ImageInfo ImageModel::imageInfo(int row) const +{ + if (row >= d->infos.size()) + { + return ImageInfo(); + } + + return d->infos.at(row); +} + +ImageInfo& ImageModel::imageInfoRef(int row) const +{ + return d->infos[row]; +} + +qlonglong ImageModel::imageId(int row) const +{ + if (row < 0 || row >= d->infos.size()) + { + return -1; + } + + return d->infos.at(row).id(); +} + +QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info) const +{ + return indexForImageId(info.id()); +} + +QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const +{ + return indexForImageId(info.id(), extraValue); +} + +QList ImageModel::indexesForImageInfo(const ImageInfo& info) const +{ + return indexesForImageId(info.id()); +} + +QModelIndex ImageModel::indexForImageId(qlonglong id) const +{ + int index = d->idHash.value(id, -1); + + if (index != -1) + { + return createIndex(index, 0); + } + + return QModelIndex(); +} + +QModelIndex ImageModel::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 ImageModel::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 ImageModel::numberOfIndexesForImageInfo(const ImageInfo& info) const +{ + return numberOfIndexesForImageId(info.id()); +} + +int ImageModel::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 +ImageInfo ImageModel::retrieveImageInfo(const QModelIndex& index) +{ + if (!index.isValid()) + { + return ImageInfo(); + } + + ImageModel* const model = index.data(ImageModelPointerRole).value(); + int row = index.data(ImageModelInternalId).toInt(); + + if (!model) + { + return ImageInfo(); + } + + return model->imageInfo(row); +} + +// static method +qlonglong ImageModel::retrieveImageId(const QModelIndex& index) +{ + if (!index.isValid()) + { + return 0; + } + + ImageModel* const model = index.data(ImageModelPointerRole).value(); + int row = index.data(ImageModelInternalId).toInt(); + + if (!model) + { + return 0; + } + + return model->imageId(row); +} + +QModelIndex ImageModel::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; iinfos.at(i).filePath() == filePath) + { + return createIndex(i, 0); + } + } + } + + return QModelIndex(); +} + +QList ImageModel::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; iinfos.at(i).filePath() == filePath) + { + indexes << createIndex(i, 0); + } + } + + return indexes; + } +} + +ImageInfo ImageModel::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 ImageInfo& info, d->infos) + { + if (info.filePath() == filePath) + { + return info; + } + } + } + + return ImageInfo(); +} + +QList ImageModel::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 ImageInfo& info, d->infos) + { + if (info.filePath() == filePath) + { + infos << info; + } + } + } + + return infos; +} + +void ImageModel::addImageInfo(const ImageInfo& info) +{ + addImageInfos(QList() << info, QList()); +} + +void ImageModel::addImageInfos(const QList& infos) +{ + addImageInfos(infos, QList()); +} + +void ImageModel::addImageInfos(const QList& infos, const QList& extraValues) +{ + if (infos.isEmpty()) + { + return; + } + + if (d->incrementalUpdater) + { + d->incrementalUpdater->appendInfos(infos, extraValues); + } + else + { + appendInfos(infos, extraValues); + } +} + +void ImageModel::addImageInfoSynchronously(const ImageInfo& info) +{ + addImageInfosSynchronously(QList() << info, QList()); +} + +void ImageModel::addImageInfosSynchronously(const QList& infos) +{ + addImageInfos(infos, QList()); +} + +void ImageModel::addImageInfosSynchronously(const QList& infos, const QList& extraValues) +{ + if (infos.isEmpty()) + { + return; + } + + publiciseInfos(infos, extraValues); + emit processAdded(infos, extraValues); +} + +void ImageModel::ensureHasImageInfo(const ImageInfo& info) +{ + ensureHasImageInfos(QList() << info, QList()); +} + +void ImageModel::ensureHasImageInfos(const QList& infos) +{ + ensureHasImageInfos(infos, QList()); +} + +void ImageModel::ensureHasImageInfos(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 ImageModel::clearImageInfos() +{ + d->infos.clear(); + d->extraValues.clear(); + d->idHash.clear(); + d->filePathHash.clear(); + delete d->incrementalUpdater; + d->incrementalUpdater = 0; + d->pendingInfos.clear(); + d->pendingExtraValues.clear(); + d->refreshing = false; + d->reAdding = false; + d->incrementalRefreshRequested = false; + + beginResetModel(); + endResetModel(); + + imageInfosCleared(); +} + +void ImageModel::setImageInfos(const QList& infos) +{ + clearImageInfos(); + addImageInfos(infos); +} + +QList ImageModel::imageInfos() const +{ + return d->infos; +} + +QList ImageModel::imageIds() const +{ + return d->idHash.keys(); +} + +bool ImageModel::hasImage(qlonglong id) const +{ + return d->idHash.contains(id); +} + +bool ImageModel::hasImage(const ImageInfo& info) const +{ + return d->idHash.contains(info.id()); +} + +bool ImageModel::hasImage(const ImageInfo& info, const QVariant& extraValue) const +{ + return hasImage(info.id(), extraValue); +} + +bool ImageModel::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 ImageModel::uniqueImageInfos() const +{ + if (d->extraValues.isEmpty()) + { + return d->infos; + } + + QList uniqueInfos; + const int size = d->infos.size(); + + for (int i=0; iinfos.at(i); + + if (d->idHash.value(info.id()) == i) + { + uniqueInfos << info; + } + } + + return uniqueInfos; +} + +void ImageModel::emitDataChangedForAll() +{ + if (d->infos.isEmpty()) + { + return; + } + + QModelIndex first = createIndex(0, 0); + QModelIndex last = createIndex(d->infos.size() - 1, 0); + emit dataChanged(first, last); +} + +void ImageModel::emitDataChangedForSelection(const QItemSelection& selection) +{ + if (!selection.isEmpty()) + { + foreach(const QItemSelectionRange& range, selection) + { + emit dataChanged(range.topLeft(), range.bottomRight()); + } + } +} + +void ImageModel::ensureHasGroupedImages(const ImageInfo& groupLeader) +{ + ensureHasImageInfos(groupLeader.groupedImages()); +} + +// ------------ Preprocessing ------------- + +void ImageModel::setPreprocessor(QObject* preprocessor) +{ + unsetPreprocessor(d->preprocessor); + d->preprocessor = preprocessor; +} + +void ImageModel::unsetPreprocessor(QObject* preprocessor) +{ + if (preprocessor && d->preprocessor == preprocessor) + { + disconnect(this, SIGNAL(preprocess(QList,QList)), 0, 0); + disconnect(d->preprocessor, 0, this, SLOT(reAddImageInfos(QList,QList))); + disconnect(d->preprocessor, 0, this, SLOT(reAddingFinished())); + } +} + +void ImageModel::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 ImageModel::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 ImageInfo& 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& infos, const QList& extraValues) +{ + // addImageInfos -> appendInfos -> preprocessor -> reAddImageInfos + publiciseInfos(infos, extraValues); +} + +void ImageModel::reAddingFinished() +{ + d->reAdding = false; + cleanSituationChecks(); +} + +void ImageModel::startRefresh() +{ + d->refreshing = true; +} + +void ImageModel::finishRefresh() +{ + d->refreshing = false; + cleanSituationChecks(); +} + +bool ImageModel::isRefreshing() const +{ + return d->refreshing; +} + +void ImageModel::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 ImageModel::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 ImageInfo& 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 ImageModel::requestIncrementalRefresh() +{ + if (d->reAdding) + { + d->incrementalRefreshRequested = true; + } + else + { + emit readyForIncrementalRefresh(); + } +} + +bool ImageModel::hasIncrementalRefreshPending() const +{ + return d->incrementalRefreshRequested; +} + +void ImageModel::startIncrementalRefresh() +{ + delete d->incrementalUpdater; + + d->incrementalUpdater = new ImageModelIncrementalUpdater(d); +} + +void ImageModel::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 = 0; +} + +void ImageModel::removeIndex(const QModelIndex& index) +{ + removeIndexes(QList() << index); +} + +void ImageModel::removeIndexes(const QList& indexes) +{ + QList listIndexes; + + foreach(const QModelIndex& index, indexes) + { + if (d->isValid(index)) + { + listIndexes << index.row(); + } + } + + if (listIndexes.isEmpty()) + { + return; + } + + removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes)); +} + +void ImageModel::removeImageInfo(const ImageInfo& info) +{ + removeImageInfos(QList() << info); +} + +void ImageModel::removeImageInfos(const QList& infos) +{ + QList listIndexes; + + foreach(const ImageInfo& info, infos) + { + QModelIndex index = indexForImageId(info.id()); + + if (index.isValid()) + { + listIndexes << index.row(); + } + } + removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes)); +} + +void ImageModel::removeImageInfos(const QList& infos, const QList& extraValues) +{ + if (extraValues.isEmpty()) + { + removeImageInfos(infos); + return; + } + + QList listIndexes; + + for (int i=0; isendRemovalSignals = 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 ImageModel::removeRowPairsWithCheck(const QList >& toRemove) +{ + if (d->incrementalUpdater) + { + d->incrementalUpdater->aboutToBeRemovedInModel(toRemove); + } + + removeRowPairs(toRemove); +} + +void ImageModel::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) + { + qCopy(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; + } + } + } +} + +ImageModelIncrementalUpdater::ImageModelIncrementalUpdater(ImageModel::Private* d) +{ + oldIds = d->idHash; + oldExtraValues = d->extraValues; +} + +void ImageModelIncrementalUpdater::appendInfos(const QList& infos, const QList& extraValues) +{ + if (extraValues.isEmpty()) + { + foreach(const ImageInfo& 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::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 ImageModelIncrementalUpdater::aboutToBeRemovedInModel(const IntPairList& toRemove) +{ + modelRemovals << toRemove; +} + +QList > ImageModelIncrementalUpdater::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, 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 > ImageModelIncrementalUpdater::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); + qSort(indices); + + QPair pair(indices.first(), indices.first()); + + for (int i=1; iisValid(index)) + { + return QVariant(); + } + + switch (role) + { + case Qt::DisplayRole: + case Qt::ToolTipRole: + return d->infos.at(index.row()).name(); + + case ImageModelPointerRole: + return QVariant::fromValue(const_cast(this)); + + case ImageModelInternalId: + 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 ImageModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(section) + Q_UNUSED(orientation) + Q_UNUSED(role) + return QVariant(); +} + +int ImageModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + { + return 0; + } + + return d->infos.size(); +} + +Qt::ItemFlags ImageModel::flags(const QModelIndex& index) const +{ + if (!d->isValid(index)) + { + return 0; + } + + Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + + f |= dragDropFlags(index); + + return f; +} + +QModelIndex ImageModel::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 ImageModel::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 ImageModel::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/libs/database/models/imagemodel.h b/libs/database/models/imagemodel.h new file mode 100644 index 0000000..dcf94c2 --- /dev/null +++ b/libs/database/models/imagemodel.h @@ -0,0 +1,364 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-05 + * Description : Qt item model for database entries + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * + * 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 IMAGEMODEL_H +#define IMAGEMODEL_H + +// Qt includes + +#include + +// Local includes + +#include "dragdropimplementations.h" +#include "imageinfo.h" +#include "digikam_export.h" + +class QItemSelection; + +namespace Digikam +{ + +class ImageChangeset; +class ImageTagChangeset; + +namespace DatabaseFields +{ +class Set; +} + +class DIGIKAM_DATABASE_EXPORT ImageModel : public QAbstractListModel, public DragDropModelImplementation +{ + Q_OBJECT + +public: + + enum ImageModelRoles + { + /// An ImageModel* pointer to this model + ImageModelPointerRole = Qt::UserRole, + ImageModelInternalId = Qt::UserRole + 1, + /// Returns a thumbnail pixmap. May be implemented by subclasses. + /// Returns either a valid pixmap or a null QVariant. + ThumbnailRole = Qt::UserRole + 2, + /// Returns a QDateTime with the creation date + CreationDateRole = Qt::UserRole + 3, + /// Return (optional) extraData field + ExtraDataRole = Qt::UserRole + 5, + /// Returns the number of duplicate indexes for the same image id + ExtraDataDuplicateCount = Qt::UserRole + 6, + + // Roles which are defined here but not implemented by ImageModel + /// Returns position of item in Left Light Table preview. + LTLeftPanelRole = Qt::UserRole + 50, + /// Returns position of item in Right Light Table preview. + LTRightPanelRole = Qt::UserRole + 51, + + // For use by subclasses + SubclassRoles = Qt::UserRole + 100, + // For use by filter models + FilterModelRoles = Qt::UserRole + 500 + }; + +public: + + explicit ImageModel(QObject* parent = 0); + ~ImageModel(); + + /** If a cache is kept, lookup by file path is fast, + * without a cache it is O(n). Default is false. + */ + void setKeepsFilePathCache(bool keepCache); + bool keepsFilePathCache() const; + + /** Set a set of database fields to watch. + * If either of these is changed, dataChanged() will be emitted. + * Default is no flag (no signal will be emitted). + */ + void setWatchFlags(const DatabaseFields::Set& set); + + /** Returns the ImageInfo object, reference or image id from the underlying data + * pointed to by the index. + * If the index is not valid, imageInfo will return a null ImageInfo, imageId will + * return 0, imageInfoRef must not be called with an invalid index. + */ + ImageInfo imageInfo(const QModelIndex& index) const; + ImageInfo& imageInfoRef(const QModelIndex& index) const; + qlonglong imageId(const QModelIndex& index) const; + QList imageInfos(const QList& indexes) const; + QList imageIds(const QList& indexes) const; + + /** Returns the ImageInfo object, reference or image id from the underlying data + * of the given row (parent is the invalid QModelIndex, column is 0). + * Note that imageInfoRef will crash if index is invalid. + */ + ImageInfo imageInfo(int row) const; + ImageInfo& imageInfoRef(int row) const; + qlonglong imageId(int row) const; + + /** Return the index for the given ImageInfo or id, if contained in this model. + */ + QModelIndex indexForImageInfo(const ImageInfo& info) const; + QModelIndex indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const; + QModelIndex indexForImageId(qlonglong id) const; + QModelIndex indexForImageId(qlonglong id, const QVariant& extraValue) const; + QList indexesForImageInfo(const ImageInfo& info) const; + QList indexesForImageId(qlonglong id) const; + + int numberOfIndexesForImageInfo(const ImageInfo& info) const; + int numberOfIndexesForImageId(qlonglong id) const; + + /** Returns the index or ImageInfo object from the underlying data + * for the given file path. This is fast if keepsFilePathCache is enabled. + * The file path is as returned by ImageInfo.filePath(). + * In case of multiple occurrences of the same file, the simpler variants return + * any one found first, use the QList methods to retrieve all occurrences. + */ + QModelIndex indexForPath(const QString& filePath) const; + ImageInfo imageInfo(const QString& filePath) const; + QList indexesForPath(const QString& filePath) const; + QList imageInfos(const QString& filePath) const; + + /** Main entry point for subclasses adding image infos to the model. + * If you list entries not unique per image id, you must add an extraValue + * so that every entry is unique by imageId and extraValues. + * Please note that these methods do not prevent addition of duplicate entries. + */ + void addImageInfo(const ImageInfo& info); + void addImageInfos(const QList& infos); + void addImageInfos(const QList& infos, const QList& extraValues); + + /** Clears image infos and resets model. + */ + void clearImageInfos(); + + /** Clears and adds the infos. + */ + void setImageInfos(const QList& infos); + + /** + * Directly remove the given indexes or infos from the model. + */ + void removeIndex(const QModelIndex& indexes); + void removeIndexes(const QList& indexes); + void removeImageInfo(const ImageInfo& info); + void removeImageInfos(const QList& infos); + void removeImageInfos(const QList& infos, const QList& extraValues); + + /** + * addImageInfo() is asynchronous if a prepocessor is set. + * This method first adds the info, synchronously. + * Only afterwards, the preprocessor will have the opportunity to process it. + * This method also bypasses any incremental updates. + * Please note that these methods do not prevent addition of duplicate entries. + */ + void addImageInfoSynchronously(const ImageInfo& info); + void addImageInfosSynchronously(const QList& infos); + void addImageInfosSynchronously(const QList& infos, const QList& extraValues); + + /** + * Add the given entries. Method returns immediately, the + * addition may happen later asynchronously. + * These methods prevent the addition of duplicate entries. + */ + void ensureHasImageInfo(const ImageInfo& info); + void ensureHasImageInfos(const QList& infos); + void ensureHasImageInfos(const QList& infos, const QList& extraValues); + + /** + * Ensure that all images grouped on the given leader are contained in the model. + */ + void ensureHasGroupedImages(const ImageInfo& groupLeader); + + QList imageInfos() const; + QList imageIds() const; + QList uniqueImageInfos() const; + + bool hasImage(qlonglong id) const; + bool hasImage(const ImageInfo& info) const; + bool hasImage(const ImageInfo& info, const QVariant& extraValue) const; + bool hasImage(qlonglong id, const QVariant& extraValue) const; + + bool isEmpty() const; + + // Drag and Drop + DECLARE_MODEL_DRAG_DROP_METHODS + + /** + * Install an object as a preprocessor for ImageInfos added to this model. + * For every QList of ImageInfos added to addImageInfo, the signal preprocess() + * will be emitted. The preprocessor may process the items and shall then readd + * them by calling reAddImageInfos(). It may take some time to process. + * It shall discard any held infos when the modelReset() signal is sent. + * It shall call readdFinished() when no reset occurred and all infos on the way have been readded. + * This means that only after calling this method, you shall make three connections + * (preprocess -> your slot, your signal -> reAddImageInfos, your signal -> reAddingFinished) + * and make or already hold a connection modelReset() -> your slot. + * There is only one preprocessor at a time, a previously set object will be disconnected. + */ + void setPreprocessor(QObject* processor); + void unsetPreprocessor(QObject* processor); + + /** + * Returns true if this model is currently refreshing. + * For a preprocessor this means that, although the preprocessor may currently have + * processed all it got, more batches are to be expected. + */ + bool isRefreshing() const; + + /** + * Enable sending of imageInfosAboutToBeRemoved and imageInfosRemoved signals. + * Default: false + */ + void setSendRemovalSignals(bool send); + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; + virtual Qt::ItemFlags flags(const QModelIndex& index) const; + virtual QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const; + + /** Retrieves the imageInfo object from the data() method of the given index. + * The index may be from a QSortFilterProxyModel as long as an ImageModel is at the end. */ + static ImageInfo retrieveImageInfo(const QModelIndex& index); + static qlonglong retrieveImageId(const QModelIndex& index); + +Q_SIGNALS: + + /** Informs that ImageInfos will be added to the model. + * This signal is sent before the model data is changed and views are informed. + */ + void imageInfosAboutToBeAdded(const QList& infos); + + /** Informs that ImageInfos have been added to the model. + * This signal is sent after the model data is changed and views are informed. + */ + void imageInfosAdded(const QList& infos); + + /** Informs that ImageInfos will be removed from the model. + * This signal is sent before the model data is changed and views are informed. + * Note: You need to explicitly enable sending of this signal. It is not sent + * in clearImageInfos(). + */ + void imageInfosAboutToBeRemoved(const QList& infos); + + /** Informs that ImageInfos have been removed from the model. + * This signal is sent after the model data is changed and views are informed. * + * Note: You need to explicitly enable sending of this signal. It is not sent + * in clearImageInfos(). + */ + void imageInfosRemoved(const QList& infos); + + /** Connect to this signal only if you are the current preprocessor. + */ + void preprocess(const QList& infos, const QList&); + void processAdded(const QList& infos, const QList&); + + /** If an ImageChangeset affected indexes of this model with changes as set in watchFlags(), + * this signal contains the changeset and the affected indexes. + */ + void imageChange(const ImageChangeset&, const QItemSelection&); + + /** If an ImageTagChangeset affected indexes of this model, + * this signal contains the changeset and the affected indexes. + */ + void imageTagChange(const ImageTagChangeset&, const QItemSelection&); + + /** Signals that the model is right now ready to start an incremental refresh. + * This is guaranteed only for the scope of emitting this signal. + */ + void readyForIncrementalRefresh(); + + /** Signals that the model has finished currently with all scheduled + * refreshing, full or incremental, and all preprocessing. + * The model is in polished, clean situation right now. + */ + void allRefreshingFinished(); + +public Q_SLOTS: + + void reAddImageInfos(const QList& infos, const QList& extraValues); + void reAddingFinished(); + +protected: + + /** Subclasses that add ImageInfos in batches shall call startRefresh() + * when they start sending batches and finishRefresh() when they have finished. + * No incremental refreshes will be started while listing. + * A clearImageInfos() always stops listing, calling finishRefresh() is then not necessary. + */ + void startRefresh(); + void finishRefresh(); + + /** As soon as the model is ready to start an incremental refresh, the signal + * readyForIncrementalRefresh() will be emitted. The signal will be emitted inline + * if the model is ready right now. + */ + void requestIncrementalRefresh(); + bool hasIncrementalRefreshPending() const; + + /** Starts an incremental refresh operation. You shall only call this method from a slot + * connected to readyForIncrementalRefresh(). To initiate an incremental refresh, + * call requestIncrementalRefresh(). + */ + void startIncrementalRefresh(); + void finishIncrementalRefresh(); + + void emitDataChangedForAll(); + void emitDataChangedForSelection(const QItemSelection& selection); + + // Called when the internal storage is cleared + virtual void imageInfosCleared() {}; + + // Called before rowsAboutToBeRemoved + virtual void imageInfosAboutToBeRemoved(int /*begin*/, int /*end*/) {}; + +protected Q_SLOTS: + + virtual void slotImageChange(const ImageChangeset& changeset); + virtual void slotImageTagChange(const ImageTagChangeset& changeset); + +private: + + void appendInfos(const QList& infos, const QList& extraValues); + void appendInfosChecked(const QList& infos, const QList& extraValues); + void publiciseInfos(const QList& infos, const QList& extraValues); + void cleanSituationChecks(); + void removeRowPairsWithCheck(const QList >& toRemove); + void removeRowPairs(const QList >& toRemove); + +public: + + // Declared public because it's used in ImageModelIncrementalUpdater class + class Private; + +private: + + Private* const d; +}; + +} // namespace Digikam + +Q_DECLARE_METATYPE(Digikam::ImageModel*) + +#endif // IMAGEMODEL_H diff --git a/libs/database/models/imagesortsettings.cpp b/libs/database/models/imagesortsettings.cpp new file mode 100644 index 0000000..39ee6e1 --- /dev/null +++ b/libs/database/models/imagesortsettings.cpp @@ -0,0 +1,400 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-05 + * Description : Filter values for use with ImageFilterModel + * + * Copyright (C) 2009 by Marcel Wiesweg + * Copyright (C) 2014 by Mohamed Anwer + * + * 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 "imagesortsettings.h" + +// Qt includes + +#include +#include + +// Local includes + +#include "coredbfields.h" +#include "imageinfo.h" + +namespace Digikam +{ + +ImageSortSettings::ImageSortSettings() +{ + categorizationMode = NoCategories; + categorizationSortOrder = DefaultOrder; + categorizationCaseSensitivity = Qt::CaseSensitive; + sortRole = SortByFileName; + sortOrder = DefaultOrder; + strTypeNatural = true; + sortCaseSensitivity = Qt::CaseSensitive; + currentCategorizationSortOrder = Qt::AscendingOrder; + currentSortOrder = Qt::AscendingOrder; +} + +bool ImageSortSettings::operator==(const ImageSortSettings& other) const +{ + return + categorizationMode == other.categorizationMode && + categorizationSortOrder == other.categorizationSortOrder && + categorizationCaseSensitivity == other.categorizationCaseSensitivity && + sortRole == other.sortRole && + sortOrder == other.sortOrder && + sortCaseSensitivity == other.sortCaseSensitivity; +} + +void ImageSortSettings::setCategorizationMode(CategorizationMode mode) +{ + categorizationMode = mode; + + if (categorizationSortOrder == DefaultOrder) + { + currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); + } +} + +void ImageSortSettings::setCategorizationSortOrder(SortOrder order) +{ + categorizationSortOrder = order; + + if (categorizationSortOrder == DefaultOrder) + { + currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); + } + else + { + currentCategorizationSortOrder = (Qt::SortOrder)categorizationSortOrder; + } +} + +void ImageSortSettings::setSortRole(SortRole role) +{ + sortRole = role; + + if (sortOrder == DefaultOrder) + { + currentSortOrder = defaultSortOrderForSortRole(sortRole); + } +} + +void ImageSortSettings::setSortOrder(SortOrder order) +{ + sortOrder = order; + + if (sortOrder == DefaultOrder) + { + currentSortOrder = defaultSortOrderForSortRole(sortRole); + } + else + { + currentSortOrder = (Qt::SortOrder)order; + } +} + +void ImageSortSettings::setStringTypeNatural(bool natural) +{ + strTypeNatural = natural; +} + +Qt::SortOrder ImageSortSettings::defaultSortOrderForCategorizationMode(CategorizationMode mode) +{ + switch (mode) + { + case NoCategories: + case OneCategory: + case CategoryByAlbum: + case CategoryByFormat: + default: + return Qt::AscendingOrder; + } +} + +Qt::SortOrder ImageSortSettings::defaultSortOrderForSortRole(SortRole role) +{ + switch (role) + { + case SortByFileName: + case SortByFilePath: + return Qt::AscendingOrder; + case SortByFileSize: + return Qt::DescendingOrder; + case SortByModificationDate: + case SortByCreationDate: + return Qt::AscendingOrder; + case SortByRating: + case SortByImageSize: + return Qt::DescendingOrder; + case SortByAspectRatio: + return Qt::DescendingOrder; + case SortBySimilarity: + return Qt::DescendingOrder; + default: + return Qt::AscendingOrder; + } +} + +int ImageSortSettings::compareCategories(const ImageInfo& left, const ImageInfo& right) const +{ + switch (categorizationMode) + { + case NoCategories: + case OneCategory: + return 0; + case CategoryByAlbum: + { + int leftAlbum = left.albumId(); + int rightAlbum = right.albumId(); + + // return comparation result + if (leftAlbum == rightAlbum) + { + return 0; + } + else if (lessThanByOrder(leftAlbum, rightAlbum, currentCategorizationSortOrder)) + { + return -1; + } + else + { + return 1; + } + } + case CategoryByFormat: + { + return naturalCompare(left.format(), right.format(), + currentCategorizationSortOrder, categorizationCaseSensitivity, strTypeNatural); + } + default: + return 0; + } +} + +bool ImageSortSettings::lessThan(const ImageInfo& left, const ImageInfo& right) const +{ + int result = compare(left, right, sortRole); + + if (result != 0) + { + return result < 0; + } + + // are they identical? + if (left == right) + { + return false; + } + + // If left and right equal for first sort order, use a hierarchy of all sort orders + if ( (result = compare(left, right, SortByFileName)) != 0) + { + return result < 0; + } + + if ( (result = compare(left, right, SortByCreationDate)) != 0) + { + return result < 0; + } + + if ( (result = compare(left, right, SortByModificationDate)) != 0) + { + return result < 0; + } + + if ( (result = compare(left, right, SortByFilePath)) != 0) + { + return result < 0; + } + + if ( (result = compare(left, right, SortByFileSize)) != 0) + { + return result < 0; + } + + if ( (result = compare(left, right, SortBySimilarity)) != 0) + { + return result < 0; + } + + return false; +} + +int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right) const +{ + return compare(left, right, sortRole); +} + +int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right, SortRole role) const +{ + switch (role) + { + case SortByFileName: + { + bool versioning = (left.name().contains(QLatin1String("_v"), Qt::CaseInsensitive) || + right.name().contains(QLatin1String("_v"), Qt::CaseInsensitive)); + return naturalCompare(left.name(), right.name(), currentSortOrder, sortCaseSensitivity, strTypeNatural, versioning); + } + case SortByFilePath: + return naturalCompare(left.filePath(), right.filePath(), currentSortOrder, sortCaseSensitivity, strTypeNatural); + case SortByFileSize: + return compareByOrder(left.fileSize(), right.fileSize(), currentSortOrder); + case SortByModificationDate: + return compareByOrder(left.modDateTime(), right.modDateTime(), currentSortOrder); + case SortByCreationDate: + return compareByOrder(left.dateTime(), right.dateTime(), currentSortOrder); + case SortByRating: + // I have the feeling that inverting the sort order for rating is the natural order + return - compareByOrder(left.rating(), right.rating(), currentSortOrder); + case SortByImageSize: + { + QSize leftSize = left.dimensions(); + QSize rightSize = right.dimensions(); + int leftPixels = leftSize.width() * leftSize.height(); + int rightPixels = rightSize.width() * rightSize.height(); + return compareByOrder(leftPixels, rightPixels, currentSortOrder); + } + case SortByAspectRatio: + { + QSize leftSize = left.dimensions(); + QSize rightSize = right.dimensions(); + int leftAR = (double(leftSize.width()) / double(leftSize.height())) * 1000000; + int rightAR = (double(rightSize.width()) / double(rightSize.height())) * 1000000; + return compareByOrder(leftAR, rightAR, currentSortOrder); + } + case SortBySimilarity: + { + qlonglong leftReferenceImageId = left.currentReferenceImage(); + qlonglong rightReferenceImageId = right.currentReferenceImage(); + // make sure that the original image has always the highest similarity. + double leftSimilarity = left.id() == leftReferenceImageId ? 1.1 : left.currentSimilarity(); + double rightSimilarity = right.id() == rightReferenceImageId ? 1.1 : right.currentSimilarity(); + return compareByOrder(leftSimilarity, rightSimilarity, currentSortOrder); + } + default: + return 1; + } +} + +bool ImageSortSettings::lessThan(const QVariant& left, const QVariant& right) const +{ + if (left.type() != right.type()) + { + return false; + } + + switch (left.type()) + { + case QVariant::Int: + return compareByOrder(left.toInt(), right.toInt(), currentSortOrder); + case QVariant::UInt: + return compareByOrder(left.toUInt(), right.toUInt(), currentSortOrder); + case QVariant::LongLong: + return compareByOrder(left.toLongLong(), right.toLongLong(), currentSortOrder); + case QVariant::ULongLong: + return compareByOrder(left.toULongLong(), right.toULongLong(), currentSortOrder); + case QVariant::Double: + return compareByOrder(left.toDouble(), right.toDouble(), currentSortOrder); + case QVariant::Date: + return compareByOrder(left.toDate(), right.toDate(), currentSortOrder); + case QVariant::DateTime: + return compareByOrder(left.toDateTime(), right.toDateTime(), currentSortOrder); + case QVariant::Time: + return compareByOrder(left.toTime(), right.toTime(), currentSortOrder); + case QVariant::Rect: + case QVariant::RectF: + { + QRectF rectLeft = left.toRectF(); + QRectF rectRight = right.toRectF(); + int result; + + if ((result = compareByOrder(rectLeft.top(), rectRight.top(), currentSortOrder)) != 0) + { + return result < 0; + } + + if ((result = compareByOrder(rectLeft.left(), rectRight.left(), currentSortOrder)) != 0) + { + return result < 0; + } + + QSizeF sizeLeft = rectLeft.size(), sizeRight = rectRight.size(); + + if ((result = compareByOrder(sizeLeft.width()*sizeLeft.height(), sizeRight.width()*sizeRight.height(), currentSortOrder)) != 0) + { + return result < 0; + } + // FIXME: fall through?? If not, add "break" here + } + default: + return naturalCompare(left.toString(), right.toString(), currentSortOrder, sortCaseSensitivity, strTypeNatural); + } +} + +DatabaseFields::Set ImageSortSettings::watchFlags() const +{ + DatabaseFields::Set set; + + switch (sortRole) + { + case SortByFileName: + set |= DatabaseFields::Name; + break; + case SortByFilePath: + set |= DatabaseFields::Name; + break; + case SortByFileSize: + set |= DatabaseFields::FileSize; + break; + case SortByModificationDate: + set |= DatabaseFields::ModificationDate; + break; + case SortByCreationDate: + set |= DatabaseFields::CreationDate; + break; + case SortByRating: + set |= DatabaseFields::Rating; + break; + case SortByImageSize: + set |= DatabaseFields::Width | DatabaseFields::Height; + break; + case SortByAspectRatio: + set |= DatabaseFields::Width | DatabaseFields::Height; + break; + case SortBySimilarity: + // TODO: Not sure what to do here.... + set |= DatabaseFields::Name; + break; + } + + switch (categorizationMode) + { + case NoCategories: + case OneCategory: + case CategoryByAlbum: + break; + case CategoryByFormat: + set |= DatabaseFields::Format; + break; + } + + return set; +} + +} // namespace Digikam diff --git a/libs/database/models/imagesortsettings.h b/libs/database/models/imagesortsettings.h new file mode 100644 index 0000000..2a5fd8c --- /dev/null +++ b/libs/database/models/imagesortsettings.h @@ -0,0 +1,225 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-05-31 + * Description : Sort settings for use with ImageFilterModel + * + * Copyright (C) 2009 by Marcel Wiesweg + * + * 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 IMAGESORTSETTINGS_H +#define IMAGESORTSETTINGS_H + +// Qt includes + +#include +#include +#include +#include +#include + +// Local includes + +#include "digikam_export.h" + +namespace Digikam +{ + +class ImageInfo; + +namespace DatabaseFields +{ + class Set; +} + +class DIGIKAM_DATABASE_EXPORT ImageSortSettings +{ +public: + + ImageSortSettings(); + + bool operator==(const ImageSortSettings& other) const; + + /** Compares the categories of left and right. + * Return -1 if left is less than right, 0 if both fall in the same category, + * and 1 if left is greater than right. + * Adheres to set categorization mode and current category sort order. + */ + int compareCategories(const ImageInfo& left, const ImageInfo& right) const; + + /** Returns true if left is less than right. + * Adheres to current sort role and sort order. + */ + bool lessThan(const ImageInfo& left, const ImageInfo& right) const; + + /** Compares the ImageInfos left and right. + * Return -1 if left is less than right, 1 if left is greater than right, + * and 0 if left equals right comparing the current sort role's value. + * Adheres to set sort role and sort order. + */ + int compare(const ImageInfo& left, const ImageInfo& right) const; + + /** Returns true if left QVariant is less than right. + * Adheres to current sort role and sort order. + * Use for extraValue, if necessary. + */ + bool lessThan(const QVariant& left, const QVariant& right) const; + + enum SortOrder + { + AscendingOrder = Qt::AscendingOrder, + DescendingOrder = Qt::DescendingOrder, + DefaultOrder /// sort order depends on the chosen sort role + }; + + /// --- Categories --- + + enum CategorizationMode + { + NoCategories, /// categorization switched off + OneCategory, /// all items in one global category + CategoryByAlbum, + CategoryByFormat + }; + + CategorizationMode categorizationMode; + SortOrder categorizationSortOrder; + + void setCategorizationMode(CategorizationMode mode); + void setCategorizationSortOrder(SortOrder order); + + /// Only Ascending or Descending, never DefaultOrder + Qt::SortOrder currentCategorizationSortOrder; + Qt::CaseSensitivity categorizationCaseSensitivity; + + bool isCategorized() const { return categorizationMode >= CategoryByAlbum; } + + /// --- Image Sorting --- + + enum SortRole + { + // Note: For legacy reasons, the order of the first five entries must remain unchanged + SortByFileName, + SortByFilePath, + SortByCreationDate, + SortByFileSize, + SortByRating, + SortByModificationDate, + SortByImageSize, // pixel number + SortByAspectRatio, // width / height * 100000 + SortBySimilarity + }; + + SortRole sortRole; + SortOrder sortOrder; + bool strTypeNatural; + + void setSortRole(SortRole role); + void setSortOrder(SortOrder order); + void setStringTypeNatural(bool natural); + + Qt::SortOrder currentSortOrder; + Qt::CaseSensitivity sortCaseSensitivity; + + int compare(const ImageInfo& left, const ImageInfo& right, SortRole sortRole) const; + + // --- --- + + static Qt::SortOrder defaultSortOrderForCategorizationMode(CategorizationMode mode); + static Qt::SortOrder defaultSortOrderForSortRole(SortRole role); + + /// --- Change notification --- + + /** Returns database fields a change in which would affect the current sorting. + */ + DatabaseFields::Set watchFlags() const; + + /// --- Utilities --- + + /** Returns a < b if sortOrder is Ascending, or b < a if order is descending. + */ + template + static inline bool lessThanByOrder(const T& a, const T& b, Qt::SortOrder sortOrder) + { + if (sortOrder == Qt::AscendingOrder) + { + return a < b; + } + else + { + return b < a; + } + } + + /** Returns the usual compare result of -1, 0, or 1 for lessThan, equals and greaterThan. + */ + template + static inline int compareValue(const T& a, const T& b) + { + if (a == b) + { + return 0; + } + + if (a < b) + { + return -1; + } + else + { + return 1; + } + } + + /** Takes a typical result from a compare method (0 is equal, -1 is less than, 1 is greater than) + * and applies the given sort order to it. + */ + static inline int compareByOrder(int compareResult, Qt::SortOrder sortOrder) + { + if (sortOrder == Qt::AscendingOrder) + { + return compareResult; + } + else + { + return - compareResult; + } + } + + template + static inline int compareByOrder(const T& a, const T& b, Qt::SortOrder sortOrder) + { + return compareByOrder(compareValue(a, b), sortOrder); + } + + /** Compares the two string by natural comparison and adheres to given sort order + */ + static inline int naturalCompare(const QString& a, const QString& b, Qt::SortOrder sortOrder, + Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive, + bool natural = true, bool versioning = false) + { + QCollator collator; + collator.setNumericMode(natural); + collator.setIgnorePunctuation(versioning); + collator.setCaseSensitivity(caseSensitive); + return (compareByOrder(collator.compare(a, b), sortOrder)); + } +}; + +} // namespace Digikam + +#endif // IMAGESORTSETTINGS_H diff --git a/libs/database/models/imagethumbnailmodel.cpp b/libs/database/models/imagethumbnailmodel.cpp new file mode 100644 index 0000000..b7f5661 --- /dev/null +++ b/libs/database/models/imagethumbnailmodel.cpp @@ -0,0 +1,323 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-05 + * Description : Qt item model for database entries with support for thumbnail loading + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * Copyright (C) 2011-2017 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 "imagethumbnailmodel.h" + +// Qt includes + +#include + +// Local includes + +#include "digikam_debug.h" +#include "thumbnailloadthread.h" +#include "digikam_export.h" +#include "digikam_globals.h" + +namespace Digikam +{ + +class ImageThumbnailModel::ImageThumbnailModelPriv +{ +public: + + ImageThumbnailModelPriv() : + thread(0), + preloadThread(0), + thumbSize(0), + lastGlobalThumbSize(0), + preloadThumbSize(0), + emitDataChanged(true) + { + staticListContainingThumbnailRole << ImageModel::ThumbnailRole; + } + + ThumbnailLoadThread* thread; + ThumbnailLoadThread* preloadThread; + ThumbnailSize thumbSize; + ThumbnailSize lastGlobalThumbSize; + ThumbnailSize preloadThumbSize; + QRect detailRect; + QVector staticListContainingThumbnailRole; + + bool emitDataChanged; + + int preloadThumbnailSize() const + { + if (preloadThumbSize.size()) + { + return preloadThumbSize.size(); + } + + return thumbSize.size(); + } +}; + +ImageThumbnailModel::ImageThumbnailModel(QObject* parent) + : ImageModel(parent), d(new ImageThumbnailModelPriv) +{ + setKeepsFilePathCache(true); +} + +ImageThumbnailModel::~ImageThumbnailModel() +{ + delete d->preloadThread; + delete d; +} + +void ImageThumbnailModel::setThumbnailLoadThread(ThumbnailLoadThread* thread) +{ + d->thread = thread; + + connect(d->thread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), + this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap))); +} + +ThumbnailLoadThread* ImageThumbnailModel::thumbnailLoadThread() const +{ + return d->thread; +} + +ThumbnailSize ImageThumbnailModel::thumbnailSize() const +{ + return d->thumbSize; +} + +void ImageThumbnailModel::setThumbnailSize(const ThumbnailSize& size) +{ + d->lastGlobalThumbSize = size; + d->thumbSize = size; +} + +void ImageThumbnailModel::setPreloadThumbnailSize(const ThumbnailSize& size) +{ + d->preloadThumbSize = size; +} + +void ImageThumbnailModel::setEmitDataChanged(bool emitSignal) +{ + d->emitDataChanged = emitSignal; +} + +void ImageThumbnailModel::setPreloadThumbnails(bool preload) +{ + if (preload) + { + if (!d->preloadThread) + { + d->preloadThread = new ThumbnailLoadThread; + d->preloadThread->setPixmapRequested(false); + d->preloadThread->setPriority(QThread::LowestPriority); + } + + connect(this, SIGNAL(allRefreshingFinished()), + this, SLOT(preloadAllThumbnails())); + } + else + { + delete d->preloadThread; + d->preloadThread = 0; + disconnect(this, SIGNAL(allRefreshingFinished()), + this, SLOT(preloadAllThumbnails())); + } +} + +void ImageThumbnailModel::prepareThumbnails(const QList& indexesToPrepare) +{ + prepareThumbnails(indexesToPrepare, d->thumbSize); +} + +void ImageThumbnailModel::prepareThumbnails(const QList& indexesToPrepare, const ThumbnailSize& thumbSize) +{ + if (!d->thread) + { + return; + } + + QList ids; + foreach(const QModelIndex& index, indexesToPrepare) + { + ids << imageInfoRef(index).thumbnailIdentifier(); + } + d->thread->findGroup(ids, thumbSize.size()); +} + +void ImageThumbnailModel::preloadThumbnails(const QList& infos) +{ + if (!d->preloadThread) + { + return; + } + + QList ids; + foreach(const ImageInfo& info, infos) + { + ids << info.thumbnailIdentifier(); + } + d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize()); +} + +void ImageThumbnailModel::preloadThumbnails(const QList& indexesToPreload) +{ + if (!d->preloadThread) + { + return; + } + + QList ids; + foreach(const QModelIndex& index, indexesToPreload) + { + ids << imageInfoRef(index).thumbnailIdentifier(); + } + d->preloadThread->stopAllTasks(); + d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize()); +} + +void ImageThumbnailModel::preloadAllThumbnails() +{ + preloadThumbnails(imageInfos()); +} + +void ImageThumbnailModel::imageInfosCleared() +{ + if (d->preloadThread) + { + d->preloadThread->stopAllTasks(); + } +} + +QVariant ImageThumbnailModel::data(const QModelIndex& index, int role) const +{ + if (role == ThumbnailRole && d->thread && index.isValid()) + { + QPixmap thumbnail; + ImageInfo info = imageInfo(index); + QString path = info.filePath(); + + if (info.isNull()) + { + return QVariant(QVariant::Pixmap); + } + + if (!d->detailRect.isNull()) + { + if (d->thread->find(info.thumbnailIdentifier(), d->detailRect, thumbnail, d->thumbSize.size())) + { + return thumbnail; + } + } + else + { + if (d->thread->find(info.thumbnailIdentifier(), thumbnail, d->thumbSize.size())) + { + return thumbnail; + } + } + + return QVariant(QVariant::Pixmap); + } + + return ImageModel::data(index, role); +} + +bool ImageThumbnailModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (role == ThumbnailRole) + { + switch (value.type()) + { + case QVariant::Invalid: + d->thumbSize = d->lastGlobalThumbSize; + d->detailRect = QRect(); + break; + + case QVariant::Int: + + if (value.isNull()) + { + d->thumbSize = d->lastGlobalThumbSize; + } + else + { + d->thumbSize = value.toInt(); + } + break; + + case QVariant::Rect: + + if (value.isNull()) + { + d->detailRect = QRect(); + } + else + { + d->detailRect = value.toRect(); + } + break; + + default: + break; + } + } + + return ImageModel::setData(index, value, role); +} + +void ImageThumbnailModel::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb) +{ + if (thumb.isNull()) + { + return; + } + + // In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all. + QModelIndexList indexes; + ThumbnailIdentifier thumbId = loadingDescription.thumbnailIdentifier(); + if (thumbId.filePath.isEmpty()) + { + indexes = indexesForImageId(thumbId.id); + } + else + { + indexes = indexesForPath(thumbId.filePath); + } + foreach(const QModelIndex& index, indexes) + { + if (thumb.isNull()) + { + emit thumbnailFailed(index, loadingDescription.previewParameters.size); + } + else + { + emit thumbnailAvailable(index, loadingDescription.previewParameters.size); + + if (d->emitDataChanged) + { + emit dataChanged(index, index, d->staticListContainingThumbnailRole); + } + } + } +} + +} // namespace Digikam diff --git a/libs/database/models/imagethumbnailmodel.h b/libs/database/models/imagethumbnailmodel.h new file mode 100644 index 0000000..366ca65 --- /dev/null +++ b/libs/database/models/imagethumbnailmodel.h @@ -0,0 +1,140 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2009-03-05 + * Description : Qt item model for database entries with support for thumbnail loading + * + * Copyright (C) 2009-2011 by Marcel Wiesweg + * Copyright (C) 2011 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. + * + * ============================================================ */ + +#ifndef IMAGETHUMBNAILMODEL_H +#define IMAGETHUMBNAILMODEL_H + +// Local includes + +#include "imagemodel.h" +#include "thumbnailsize.h" +#include "digikam_export.h" + +namespace Digikam +{ + +class LoadingDescription; +class ThumbnailLoadThread; + +class DIGIKAM_DATABASE_EXPORT ImageThumbnailModel : public ImageModel +{ + Q_OBJECT + +public: + + /** + * An ImageModel that supports thumbnail loading. + * You need to set a ThumbnailLoadThread to enable thumbnail loading. + * Adjust the thumbnail size to your needs. + * Note that setKeepsFilePathCache is enabled per default. + */ + explicit ImageThumbnailModel(QObject* parent); + ~ImageThumbnailModel(); + + /** Enable thumbnail loading and set the thread that shall be used. + * The thumbnail size of this thread will be adjusted. + */ + void setThumbnailLoadThread(ThumbnailLoadThread* thread); + ThumbnailLoadThread* thumbnailLoadThread() const; + + /// Set the thumbnail size to use + void setThumbnailSize(const ThumbnailSize& thumbSize); + + /// If you want to fix a size for preloading, do it here. + void setPreloadThumbnailSize(const ThumbnailSize& thumbSize); + + void setExifRotate(bool rotate); + + /** + * Enable emitting dataChanged() when a thumbnail becomes available. + * The thumbnailAvailable() signal will be emitted in any case. + * Default is true. + */ + void setEmitDataChanged(bool emitSignal); + + /** + * Enable preloading of thumbnails: + * If preloading is enabled, for every entry in the model a thumbnail generation is started. + * Default: false. + */ + void setPreloadThumbnails(bool preload); + + ThumbnailSize thumbnailSize() const; + + /** + * Handles the ThumbnailRole. + * If the pixmap is available, returns it in the QVariant. + * If it still needs to be loaded, returns a null QVariant and emits + * thumbnailAvailable() as soon as it is available. + */ + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + + /** + * You can override the current thumbnail size by giving an integer value for ThumbnailRole. + * Set a null QVariant to use the thumbnail size set by setThumbnailSize() again. + * The index given here is ignored for this purpose. + */ + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::DisplayRole); + +public Q_SLOTS: + + /** Prepare the thumbnail loading for the given indexes + */ + void prepareThumbnails(const QList& indexesToPrepare); + void prepareThumbnails(const QList& indexesToPrepare, const ThumbnailSize& thumbSize); + + /** + * Preload thumbnail for the given infos resp. indexes. + * Note: Use setPreloadThumbnails to automatically preload all entries in the model. + * Note: This only ensures thumbnail generation. It is not guaranteed that pixmaps + * are stored in the cache. For thumbnails that are expect to be drawn immediately, + * include them in prepareThumbnails(). + * Note: Stops preloading of previously added thumbnails. + */ + void preloadThumbnails(const QList&); + void preloadThumbnails(const QList&); + void preloadAllThumbnails(); + +Q_SIGNALS: + + void thumbnailAvailable(const QModelIndex& index, int requestedSize); + void thumbnailFailed(const QModelIndex& index, int requestedSize); + +protected: + + virtual void imageInfosCleared(); + +protected Q_SLOTS: + + void slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb); + +private: + + class ImageThumbnailModelPriv; + ImageThumbnailModelPriv* const d; +}; + +} // namespace Digikam + +#endif /* IMAGETHUMBNAILMODEL_H */ diff --git a/libs/database/models/imageversionsmodel.cpp b/libs/database/models/imageversionsmodel.cpp new file mode 100644 index 0000000..e6ba582 --- /dev/null +++ b/libs/database/models/imageversionsmodel.cpp @@ -0,0 +1,183 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2010-07-13 + * Description : Model for image versions + * + * Copyright (C) 2010 by Martin Klapetek + * + * 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 "imageversionsmodel.h" + +// KDE includes + +#include + +// Local includes + +#include "digikam_debug.h" +#include "workingwidget.h" + +namespace Digikam +{ + +class ImageVersionsModel::Private +{ +public: + + Private() + { + data = 0; + paintTree = false; + } + + ///Complete paths with filenames and tree level + QList >* data; + ///This is for delegate to paint it as selected + QString currentSelectedImage; + ///If true, the delegate will paint items as a tree + ///if false, it will be painted as a list + bool paintTree; +}; + +ImageVersionsModel::ImageVersionsModel(QObject* parent) + : QAbstractListModel(parent), + d(new Private) +{ + d->data = new QList >; +} + +ImageVersionsModel::~ImageVersionsModel() +{ + //qDeleteAll(d->data); + delete d; +} + +Qt::ItemFlags ImageVersionsModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + { + return 0; + } + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +QVariant ImageVersionsModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + { + return QVariant(); + } + + if (role == Qt::DisplayRole && !d->data->isEmpty()) + { + return d->data->at(index.row()).first; + } + else if (role == Qt::UserRole && !d->data->isEmpty()) + { + return d->data->at(index.row()).second; + } + else if (role == Qt::DisplayRole && d->data->isEmpty()) + { + //TODO: make this text Italic + return QVariant(QString(i18n("No image selected"))); + } + + return QVariant(); +} + +int ImageVersionsModel::rowCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent) + return d->data->count(); +} + +void ImageVersionsModel::setupModelData(QList >& data) +{ + beginResetModel(); + + d->data->clear(); + + if (!data.isEmpty()) + { + d->data->append(data); + } + else + { + d->data->append(qMakePair(QString(i18n("This is the original image")), 0)); + } + + endResetModel(); +} + +void ImageVersionsModel::clearModelData() +{ + beginResetModel(); + + if (!d->data->isEmpty()) + { + d->data->clear(); + } + + endResetModel(); +} + +void ImageVersionsModel::slotAnimationStep() +{ + emit dataChanged(createIndex(0, 0), createIndex(rowCount()-1, 1)); +} + +QString ImageVersionsModel::currentSelectedImage() const +{ + return d->currentSelectedImage; +} + +void ImageVersionsModel::setCurrentSelectedImage(const QString& path) +{ + d->currentSelectedImage = path; +} + +QModelIndex ImageVersionsModel::currentSelectedImageIndex() const +{ + return index(listIndexOf(d->currentSelectedImage), 0); +} + +bool ImageVersionsModel::paintTree() const +{ + return d->paintTree; +} + +void ImageVersionsModel::setPaintTree(bool paint) +{ + d->paintTree = paint; +} + +int ImageVersionsModel::listIndexOf(const QString& item) const +{ + for (int i = 0; i < d->data->size(); ++i) + { + if (d->data->at(i).first == item) + { + return i; + } + } + + return -1; +} + +} // namespace Digikam diff --git a/libs/database/models/imageversionsmodel.h b/libs/database/models/imageversionsmodel.h new file mode 100644 index 0000000..ed08529 --- /dev/null +++ b/libs/database/models/imageversionsmodel.h @@ -0,0 +1,75 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2010-07-13 + * Description : Model for image versions + * + * Copyright (C) 2010 by Martin Klapetek + * + * 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 IMAGEVERSIONSMODEL_H +#define IMAGEVERSIONSMODEL_H + +// Qt includes + +#include +#include + +// Local includes + +#include "digikam_export.h" + +namespace Digikam +{ + +class DIGIKAM_DATABASE_EXPORT ImageVersionsModel : public QAbstractListModel +{ + Q_OBJECT + +public: + + explicit ImageVersionsModel(QObject* parent = 0); + ~ImageVersionsModel(); + + Qt::ItemFlags flags(const QModelIndex& index) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex& parent = QModelIndex()) const; + + void setupModelData(QList >& data); + void clearModelData(); + + QString currentSelectedImage() const; + void setCurrentSelectedImage(const QString& path); + QModelIndex currentSelectedImageIndex() const; + + bool paintTree() const; + int listIndexOf(const QString& item) const; + +public Q_SLOTS: + + void slotAnimationStep(); + void setPaintTree(bool paint); + +private: + + class Private; + Private* const d; +}; + +} // namespace Digikam + +#endif // IMAGEVERSIONSMODEL_H diff --git a/libs/models/CMakeLists.txt b/libs/models/CMakeLists.txt index cbabfaa..804456b 100644 --- a/libs/models/CMakeLists.txt +++ b/libs/models/CMakeLists.txt @@ -9,18 +9,6 @@ if (POLICY CMP0063) cmake_policy(SET CMP0063 NEW) endif (POLICY CMP0063) -set(libdatabasemodels_SRCS - imagemodel.cpp - imagefiltermodel.cpp - imagefiltermodelpriv.cpp - imagefiltermodelthreads.cpp - imagefiltersettings.cpp - imagelistmodel.cpp - imagesortsettings.cpp - imagethumbnailmodel.cpp - imageversionsmodel.cpp -) - set(libalbummodels_SRCS imagealbummodel.cpp imagealbumfiltermodel.cpp @@ -52,5 +40,4 @@ endif() #for digikam core lib add_library(digikamgenericmodels_src OBJECT ${libgenericmodels_SRCS}) -add_library(digikamdatabasemodels_src OBJECT ${libdatabasemodels_SRCS}) -add_library(digikammodels_src OBJECT ${libalbummodels_SRCS} ${libgenericmodels_SRCS}) +add_library(digikammodels_src OBJECT ${libalbummodels_SRCS} ${libgenericmodels_SRCS}) diff --git a/libs/models/imagefiltermodel.cpp b/libs/models/imagefiltermodel.cpp deleted file mode 100644 index 3d57e05..0000000 --- a/libs/models/imagefiltermodel.cpp +++ /dev/null @@ -1,1116 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-05 - * Description : Qt item model for database entries - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * Copyright (C) 2011-2017 by Gilles Caulier - * Copyright (C) 2010 by Andi Clemens - * Copyright (C) 2011 by Michael G. Hansen - * Copyright (C) 2014 by Mohamed Anwer - * - * 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 "imagefiltermodel.h" -#include "imagefiltermodelpriv.h" -#include "imagefiltermodelthreads.h" - -// Local includes - -#include "digikam_debug.h" -#include "coredbaccess.h" -#include "coredbchangesets.h" -#include "coredbwatch.h" -#include "imageinfolist.h" -#include "imagemodel.h" - -namespace Digikam -{ - -ImageSortFilterModel::ImageSortFilterModel(QObject* parent) - : DCategorizedSortFilterProxyModel(parent), m_chainedModel(0) -{ -} - -void ImageSortFilterModel::setSourceImageModel(ImageModel* source) -{ - if (m_chainedModel) - { - m_chainedModel->setSourceImageModel(source); - } - else - { - setDirectSourceImageModel(source); - } -} - -void ImageSortFilterModel::setSourceFilterModel(ImageSortFilterModel* source) -{ - if (source) - { - ImageModel* const model = sourceImageModel(); - - if (model) - { - source->setSourceImageModel(model); - } - } - - m_chainedModel = source; - setSourceModel(source); -} - -void ImageSortFilterModel::setDirectSourceImageModel(ImageModel* model) -{ - setSourceModel(model); -} - -void ImageSortFilterModel::setSourceModel(QAbstractItemModel* model) -{ - // made it protected, only setSourceImageModel is public - DCategorizedSortFilterProxyModel::setSourceModel(model); -} - -ImageModel* ImageSortFilterModel::sourceImageModel() const -{ - if (m_chainedModel) - { - return m_chainedModel->sourceImageModel(); - } - - return static_cast(sourceModel()); -} - -ImageSortFilterModel* ImageSortFilterModel::sourceFilterModel() const -{ - return m_chainedModel; -} - -ImageFilterModel* ImageSortFilterModel::imageFilterModel() const -{ - // reimplemented in ImageFilterModel - if (m_chainedModel) - { - return m_chainedModel->imageFilterModel(); - } - - return 0; -} - -QModelIndex ImageSortFilterModel::mapToSourceImageModel(const QModelIndex& index) const -{ - if (m_chainedModel) - { - return m_chainedModel->mapToSourceImageModel(mapToSource(index)); - } - - return mapToSource(index); -} - -QModelIndex ImageSortFilterModel::mapFromSourceImageModel(const QModelIndex& albummodel_index) const -{ - if (m_chainedModel) - { - return mapFromSource(m_chainedModel->mapFromSourceImageModel(albummodel_index)); - } - - return mapFromSource(albummodel_index); -} - - -QModelIndex ImageSortFilterModel::mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const -{ - if (m_chainedModel) - { - return m_chainedModel->mapToSourceImageModel(sourceModel_index); - } - return sourceModel_index; -} - -// -------------- Convenience mappers ------------------------------------------------------------------- - -QList ImageSortFilterModel::mapListToSource(const QList& indexes) const -{ - QList sourceIndexes; - foreach(const QModelIndex& index, indexes) - { - sourceIndexes << mapToSourceImageModel(index); - } - return sourceIndexes; -} - -QList ImageSortFilterModel::mapListFromSource(const QList& sourceIndexes) const -{ - QList indexes; - foreach(const QModelIndex& index, sourceIndexes) - { - indexes << mapFromSourceImageModel(index); - } - return indexes; -} - -ImageInfo ImageSortFilterModel::imageInfo(const QModelIndex& index) const -{ - return sourceImageModel()->imageInfo(mapToSourceImageModel(index)); -} - -qlonglong ImageSortFilterModel::imageId(const QModelIndex& index) const -{ - return sourceImageModel()->imageId(mapToSourceImageModel(index)); -} - -QList ImageSortFilterModel::imageInfos(const QList& indexes) const -{ - QList infos; - ImageModel* const model = sourceImageModel(); - - foreach(const QModelIndex& index, indexes) - { - infos << model->imageInfo(mapToSourceImageModel(index)); - } - - return infos; -} - -QList ImageSortFilterModel::imageIds(const QList& indexes) const -{ - QList ids; - ImageModel* const model = sourceImageModel(); - - foreach(const QModelIndex& index, indexes) - { - ids << model->imageId(mapToSourceImageModel(index)); - } - - return ids; -} - -QModelIndex ImageSortFilterModel::indexForPath(const QString& filePath) const -{ - return mapFromSourceImageModel(sourceImageModel()->indexForPath(filePath)); -} - -QModelIndex ImageSortFilterModel::indexForImageInfo(const ImageInfo& info) const -{ - return mapFromSourceImageModel(sourceImageModel()->indexForImageInfo(info)); -} - -QModelIndex ImageSortFilterModel::indexForImageId(qlonglong id) const -{ - return mapFromSourceImageModel(sourceImageModel()->indexForImageId(id)); -} - -QList ImageSortFilterModel::imageInfosSorted() const -{ - QList infos; - const int size = rowCount(); - ImageModel* const model = sourceImageModel(); - - for (int i=0; iimageInfo(mapToSourceImageModel(index(i, 0))); - } - - return infos; -} - -// -------------------------------------------------------------------------------------------- - -ImageFilterModel::ImageFilterModel(QObject* parent) - : ImageSortFilterModel(parent), - d_ptr(new ImageFilterModelPrivate) -{ - d_ptr->init(this); -} - -ImageFilterModel::ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent) - : ImageSortFilterModel(parent), - d_ptr(&dd) -{ - d_ptr->init(this); -} - -ImageFilterModel::~ImageFilterModel() -{ - Q_D(ImageFilterModel); - delete d; -} - -void ImageFilterModel::setDirectSourceImageModel(ImageModel* sourceModel) -{ - Q_D(ImageFilterModel); - - if (d->imageModel) - { - d->imageModel->unsetPreprocessor(d); - disconnect(d->imageModel, SIGNAL(modelReset()), - this, SLOT(slotModelReset())); - slotModelReset(); - } - - d->imageModel = sourceModel; - - if (d->imageModel) - { - d->imageModel->setPreprocessor(d); - - connect(d->imageModel, SIGNAL(preprocess(QList,QList)), - d, SLOT(preprocessInfos(QList,QList))); - - connect(d->imageModel, SIGNAL(processAdded(QList,QList)), - d, SLOT(processAddedInfos(QList,QList))); - - connect(d, SIGNAL(reAddImageInfos(QList,QList)), - d->imageModel, SLOT(reAddImageInfos(QList,QList))); - - connect(d, SIGNAL(reAddingFinished()), - d->imageModel, SLOT(reAddingFinished())); - - connect(d->imageModel, SIGNAL(modelReset()), - this, SLOT(slotModelReset())); - - connect(d->imageModel, SIGNAL(imageChange(ImageChangeset,QItemSelection)), - this, SLOT(slotImageChange(ImageChangeset))); - - connect(d->imageModel, SIGNAL(imageTagChange(ImageTagChangeset,QItemSelection)), - this, SLOT(slotImageTagChange(ImageTagChangeset))); - } - - setSourceModel(d->imageModel); -} - -QVariant ImageFilterModel::data(const QModelIndex& index, int role) const -{ - Q_D(const ImageFilterModel); - - if (!index.isValid()) - { - return QVariant(); - } - - switch (role) - { - // Attention: This breaks should there ever be another filter model between this and the ImageModel - - case DCategorizedSortFilterProxyModel::CategoryDisplayRole: - return categoryIdentifier(d->imageModel->imageInfoRef(mapToSource(index))); - case CategorizationModeRole: - return d->sorter.categorizationMode; - case SortOrderRole: - return d->sorter.sortRole; - //case CategoryCountRole: - // return categoryCount(d->imageModel->imageInfoRef(mapToSource(index))); - case CategoryAlbumIdRole: - return d->imageModel->imageInfoRef(mapToSource(index)).albumId(); - case CategoryFormatRole: - return d->imageModel->imageInfoRef(mapToSource(index)).format(); - case GroupIsOpenRole: - return d->groupFilter.isAllOpen() || - d->groupFilter.isOpen(d->imageModel->imageInfoRef(mapToSource(index)).id()); - case ImageFilterModelPointerRole: - return QVariant::fromValue(const_cast(this)); - } - - return DCategorizedSortFilterProxyModel::data(index, role); -} - -ImageFilterModel* ImageFilterModel::imageFilterModel() const -{ - return const_cast(this); -} - -DatabaseFields::Set ImageFilterModel::suggestedWatchFlags() const -{ - DatabaseFields::Set watchFlags; - watchFlags |= DatabaseFields::Name | DatabaseFields::FileSize | DatabaseFields::ModificationDate; - watchFlags |= DatabaseFields::Rating | DatabaseFields::CreationDate | DatabaseFields::Orientation | - DatabaseFields::Width | DatabaseFields::Height; - watchFlags |= DatabaseFields::Comment; - watchFlags |= DatabaseFields::ImageRelations; - return watchFlags; -} - -// -------------- Filter settings -------------- - -void ImageFilterModel::setDayFilter(const QList& days) -{ - Q_D(ImageFilterModel); - d->filter.setDayFilter(days); - setImageFilterSettings(d->filter); -} - -void ImageFilterModel::setTagFilter(const QList& includedTags, const QList& excludedTags, - ImageFilterSettings::MatchingCondition matchingCond, - bool showUnTagged, const QList& clTagIds, const QList& plTagIds) -{ - Q_D(ImageFilterModel); - d->filter.setTagFilter(includedTags, excludedTags, matchingCond, showUnTagged, clTagIds, plTagIds); - setImageFilterSettings(d->filter); -} - -void ImageFilterModel::setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded) -{ - Q_D(ImageFilterModel); - d->filter.setRatingFilter(rating, ratingCond, isUnratedExcluded); - setImageFilterSettings(d->filter); -} - -void ImageFilterModel::setUrlWhitelist(const QList urlList, const QString& id) -{ - Q_D(ImageFilterModel); - d->filter.setUrlWhitelist(urlList, id); - setImageFilterSettings(d->filter); -} - -void ImageFilterModel::setIdWhitelist(const QList& idList, const QString& id) -{ - Q_D(ImageFilterModel); - d->filter.setIdWhitelist(idList, id); - setImageFilterSettings(d->filter); -} - -void ImageFilterModel::setMimeTypeFilter(int mimeTypeFilter) -{ - Q_D(ImageFilterModel); - d->filter.setMimeTypeFilter(mimeTypeFilter); - setImageFilterSettings(d->filter); -} - -void ImageFilterModel::setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition) -{ - Q_D(ImageFilterModel); - d->filter.setGeolocationFilter(condition); - setImageFilterSettings(d->filter); -} - -void ImageFilterModel::setTextFilter(const SearchTextFilterSettings& settings) -{ - Q_D(ImageFilterModel); - d->filter.setTextFilter(settings); - setImageFilterSettings(d->filter); -} - -void ImageFilterModel::setImageFilterSettings(const ImageFilterSettings& settings) -{ - Q_D(ImageFilterModel); - - { - QMutexLocker lock(&d->mutex); - d->version++; - d->filter = settings; - d->filterCopy = settings; - d->versionFilterCopy = d->versionFilter; - d->groupFilterCopy = d->groupFilter; - - d->needPrepareComments = settings.isFilteringByText(); - d->needPrepareTags = settings.isFilteringByTags(); - d->needPrepareGroups = true; - d->needPrepare = d->needPrepareComments || d->needPrepareTags || d->needPrepareGroups; - - d->hasOneMatch = false; - d->hasOneMatchForText = false; - } - - d->filterResults.clear(); - - //d->categoryCountHashInt.clear(); - //d->categoryCountHashString.clear(); - if (d->imageModel) - { - d->infosToProcess(d->imageModel->imageInfos()); - } - - emit filterSettingsChanged(settings); -} - -void ImageFilterModel::setVersionManagerSettings(const VersionManagerSettings& settings) -{ - Q_D(ImageFilterModel); - d->versionFilter.setVersionManagerSettings(settings); - setVersionImageFilterSettings(d->versionFilter); -} - -void ImageFilterModel::setExceptionList(const QList& idList, const QString& id) -{ - Q_D(ImageFilterModel); - d->versionFilter.setExceptionList(idList, id); - setVersionImageFilterSettings(d->versionFilter); -} - -void ImageFilterModel::setVersionImageFilterSettings(const VersionImageFilterSettings& settings) -{ - Q_D(ImageFilterModel); - d->versionFilter = settings; - slotUpdateFilter(); -} - -bool ImageFilterModel::isGroupOpen(qlonglong group) const -{ - Q_D(const ImageFilterModel); - return d->groupFilter.isOpen(group); -} - -bool ImageFilterModel::isAllGroupsOpen() const -{ - Q_D(const ImageFilterModel); - return d->groupFilter.isAllOpen(); -} - -void ImageFilterModel::setGroupOpen(qlonglong group, bool open) -{ - Q_D(ImageFilterModel); - d->groupFilter.setOpen(group, open); - setGroupImageFilterSettings(d->groupFilter); -} - -void ImageFilterModel::toggleGroupOpen(qlonglong group) -{ - setGroupOpen(group, !isGroupOpen(group)); -} - -void ImageFilterModel::setAllGroupsOpen(bool open) -{ - Q_D(ImageFilterModel); - d->groupFilter.setAllOpen(open); - setGroupImageFilterSettings(d->groupFilter); -} - -void ImageFilterModel::setGroupImageFilterSettings(const GroupImageFilterSettings& settings) -{ - Q_D(ImageFilterModel); - d->groupFilter = settings; - slotUpdateFilter(); -} - -void ImageFilterModel::slotUpdateFilter() -{ - Q_D(ImageFilterModel); - setImageFilterSettings(d->filter); -} - -ImageFilterSettings ImageFilterModel::imageFilterSettings() const -{ - Q_D(const ImageFilterModel); - return d->filter; -} - -ImageSortSettings ImageFilterModel::imageSortSettings() const -{ - Q_D(const ImageFilterModel); - return d->sorter; -} - -VersionImageFilterSettings ImageFilterModel::versionImageFilterSettings() const -{ - Q_D(const ImageFilterModel); - return d->versionFilter; -} - -GroupImageFilterSettings ImageFilterModel::groupImageFilterSettings() const -{ - Q_D(const ImageFilterModel); - return d->groupFilter; -} - -void ImageFilterModel::slotModelReset() -{ - Q_D(ImageFilterModel); - { - QMutexLocker lock(&d->mutex); - // discard all packages on the way that are marked as send out for re-add - d->lastDiscardVersion = d->version; - d->sentOutForReAdd = 0; - // discard all packages on the way - d->version++; - d->sentOut = 0; - - d->hasOneMatch = false; - d->hasOneMatchForText = false; - } - d->filterResults.clear(); -} - -bool ImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const -{ - Q_D(const ImageFilterModel); - - if (source_parent.isValid()) - { - return false; - } - - qlonglong id = d->imageModel->imageId(source_row); - QHash::const_iterator it = d->filterResults.constFind(id); - - if (it != d->filterResults.constEnd()) - { - return it.value(); - } - - // usually done in thread and cache, unless source model changed - ImageInfo info = d->imageModel->imageInfo(source_row); - bool match = d->filter.matches(info); - match = match ? d->versionFilter.matches(info) : false; - - return match ? d->groupFilter.matches(info) : false; -} - -void ImageFilterModel::setSendImageInfoSignals(bool sendSignals) -{ - if (sendSignals) - { - connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), - this, SLOT(slotRowsInserted(QModelIndex,int,int))); - - connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), - this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); - } - else - { - disconnect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), - this, SLOT(slotRowsInserted(QModelIndex,int,int))); - - disconnect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), - this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); - } -} - -void ImageFilterModel::slotRowsInserted(const QModelIndex& /*parent*/, int start, int end) -{ - QList infos; - - for (int i=start; i<=end; ++i) - { - infos << imageInfo(index(i, 0)); - } - - emit imageInfosAdded(infos); -} - -void ImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end) -{ - QList infos; - - for (int i=start; i<=end; ++i) - { - infos << imageInfo(index(i, 0)); - } - - emit imageInfosAboutToBeRemoved(infos); -} - -// -------------- Threaded preparation & filtering -------------- - -void ImageFilterModel::addPrepareHook(ImageFilterModelPrepareHook* hook) -{ - Q_D(ImageFilterModel); - QMutexLocker lock(&d->mutex); - d->prepareHooks << hook; -} - -void ImageFilterModel::removePrepareHook(ImageFilterModelPrepareHook* hook) -{ - Q_D(ImageFilterModel); - QMutexLocker lock(&d->mutex); - d->prepareHooks.removeAll(hook); -} - -void ImageFilterModelPreparer::process(ImageFilterModelTodoPackage package) -{ - if (!checkVersion(package)) - { - emit discarded(package); - return; - } - - // get thread-local copy - bool needPrepareTags, needPrepareComments, needPrepareGroups; - QList prepareHooks; - { - QMutexLocker lock(&d->mutex); - needPrepareTags = d->needPrepareTags; - needPrepareComments = d->needPrepareComments; - needPrepareGroups = d->needPrepareGroups; - prepareHooks = d->prepareHooks; - } - - //TODO: Make efficient!! - if (needPrepareComments) - { - foreach(const ImageInfo& info, package.infos) - { - info.comment(); - } - } - - if (!checkVersion(package)) - { - emit discarded(package); - return; - } - - // The downside of QVector: At some point, we may need a QList for an API. - // Nonetheless, QList and ImageInfo is fast. We could as well - // reimplement ImageInfoList to ImageInfoVector (internally with templates?) - ImageInfoList infoList; - - if (needPrepareTags || needPrepareGroups) - { - infoList = package.infos.toList(); - } - - if (needPrepareTags) - { - infoList.loadTagIds(); - } - - if (needPrepareGroups) - { - infoList.loadGroupImageIds(); - } - - foreach(ImageFilterModelPrepareHook* hook, prepareHooks) - { - hook->prepare(package.infos); - } - - emit processed(package); -} - -void ImageFilterModelFilterer::process(ImageFilterModelTodoPackage package) -{ - if (!checkVersion(package)) - { - emit discarded(package); - return; - } - - // get thread-local copy - ImageFilterSettings localFilter; - VersionImageFilterSettings localVersionFilter; - GroupImageFilterSettings localGroupFilter; - bool hasOneMatch; - bool hasOneMatchForText; - { - QMutexLocker lock(&d->mutex); - localFilter = d->filterCopy; - localVersionFilter = d->versionFilterCopy; - localGroupFilter = d->groupFilterCopy; - hasOneMatch = d->hasOneMatch; - hasOneMatchForText = d->hasOneMatchForText; - } - - // Actual filtering. The variants to spare checking hasOneMatch over and over again. - if (hasOneMatch && hasOneMatchForText) - { - foreach(const ImageInfo& info, package.infos) - { - package.filterResults[info.id()] = localFilter.matches(info) && - localVersionFilter.matches(info) && - localGroupFilter.matches(info); - } - } - else if (hasOneMatch) - { - bool matchForText; - - foreach(const ImageInfo& info, package.infos) - { - package.filterResults[info.id()] = localFilter.matches(info, &matchForText) && - localVersionFilter.matches(info) && - localGroupFilter.matches(info); - - if (matchForText) - { - hasOneMatchForText = true; - } - } - } - else - { - bool result, matchForText; - - foreach(const ImageInfo& info, package.infos) - { - result = localFilter.matches(info, &matchForText) && - localVersionFilter.matches(info) && - localGroupFilter.matches(info); - package.filterResults[info.id()] = result; - - if (result) - { - hasOneMatch = true; - } - - if (matchForText) - { - hasOneMatchForText = true; - } - } - } - - if (checkVersion(package)) - { - QMutexLocker lock(&d->mutex); - d->hasOneMatch = hasOneMatch; - d->hasOneMatchForText = hasOneMatchForText; - } - - emit processed(package); -} - -// -------------- Sorting and Categorization ------------------------------------------------------- - -void ImageFilterModel::setImageSortSettings(const ImageSortSettings& sorter) -{ - Q_D(ImageFilterModel); - d->sorter = sorter; - setCategorizedModel(d->sorter.categorizationMode != ImageSortSettings::NoCategories); - invalidate(); -} - -void ImageFilterModel::setCategorizationMode(ImageSortSettings::CategorizationMode mode) -{ - Q_D(ImageFilterModel); - d->sorter.setCategorizationMode(mode); - setImageSortSettings(d->sorter); -} - -void ImageFilterModel::setCategorizationSortOrder(ImageSortSettings::SortOrder order) -{ - Q_D(ImageFilterModel); - d->sorter.setCategorizationSortOrder(order); - setImageSortSettings(d->sorter); -} - -void ImageFilterModel::setSortRole(ImageSortSettings::SortRole role) -{ - Q_D(ImageFilterModel); - d->sorter.setSortRole(role); - setImageSortSettings(d->sorter); -} - -void ImageFilterModel::setSortOrder(ImageSortSettings::SortOrder order) -{ - Q_D(ImageFilterModel); - d->sorter.setSortOrder(order); - setImageSortSettings(d->sorter); -} - -void ImageFilterModel::setStringTypeNatural(bool natural) -{ - Q_D(ImageFilterModel); - d->sorter.setStringTypeNatural(natural); - setImageSortSettings(d->sorter); -} - -int ImageFilterModel::compareCategories(const QModelIndex& left, const QModelIndex& right) const -{ - // source indexes - Q_D(const ImageFilterModel); - - if (!d->sorter.isCategorized()) - { - return 0; - } - - if (!left.isValid() || !right.isValid()) - { - return -1; - } - - const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left); - const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right); - - // Check grouping - qlonglong leftGroupImageId = leftInfo.groupImageId(); - qlonglong rightGroupImageId = rightInfo.groupImageId(); - - return compareInfosCategories(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId), - rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId)); -} - -bool ImageFilterModel::subSortLessThan(const QModelIndex& left, const QModelIndex& right) const -{ - // source indexes - Q_D(const ImageFilterModel); - - if (!left.isValid() || !right.isValid()) - { - return true; - } - - if (left == right) - { - return false; - } - - const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left); - const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right); - - if (leftInfo == rightInfo) - { - return d->sorter.lessThan(left.data(ImageModel::ExtraDataRole), right.data(ImageModel::ExtraDataRole)); - } - - // Check grouping - qlonglong leftGroupImageId = leftInfo.groupImageId(); - qlonglong rightGroupImageId = rightInfo.groupImageId(); - - // Either no grouping (-1), or same group image, or same image - if (leftGroupImageId == rightGroupImageId) - { - return infosLessThan(leftInfo, rightInfo); - } - - // We have grouping to handle - - // Is one grouped on the other? Sort behind leader. - if (leftGroupImageId == rightInfo.id()) - { - return false; - } - if (rightGroupImageId == leftInfo.id()) - { - return true; - } - - // Use the group leader for sorting - return infosLessThan(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId), - rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId)); -} - -int ImageFilterModel::compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const -{ - // Note: reimplemented in ImageAlbumFilterModel - Q_D(const ImageFilterModel); - return d->sorter.compareCategories(left, right); -} - -// Feel free to optimize. QString::number is 3x slower. -static inline QString fastNumberToString(int id) -{ - const int size = sizeof(int) * 2; - char c[size+1]; - c[size] = '\0'; - char* p = c; - int number = id; - - for (int i=0; i>= 4; - ++p; - } - - return QString::fromLatin1(c); -} - -QString ImageFilterModel::categoryIdentifier(const ImageInfo& i) const -{ - Q_D(const ImageFilterModel); - - if (!d->sorter.isCategorized()) - { - return QString(); - } - - qlonglong groupedImageId = i.groupImageId(); - ImageInfo info = groupedImageId == -1 ? i : ImageInfo(groupedImageId); - - switch (d->sorter.categorizationMode) - { - case ImageSortSettings::NoCategories: - return QString(); - case ImageSortSettings::OneCategory: - return QString(); - case ImageSortSettings::CategoryByAlbum: - return fastNumberToString(info.albumId()); - case ImageSortSettings::CategoryByFormat: - return info.format(); - default: - return QString(); - } -} - -bool ImageFilterModel::infosLessThan(const ImageInfo& left, const ImageInfo& right) const -{ - Q_D(const ImageFilterModel); - return d->sorter.lessThan(left, right); -} - -// -------------- Watching changes ----------------------------------------------------------------- - -void ImageFilterModel::slotImageTagChange(const ImageTagChangeset& changeset) -{ - Q_D(ImageFilterModel); - - if (!d->imageModel || d->imageModel->isEmpty()) - { - return; - } - - // already scheduled to re-filter? - if (d->updateFilterTimer->isActive()) - { - return; - } - - // do we filter at all? - if (!d->versionFilter.isFilteringByTags() && - !d->filter.isFilteringByTags() && - !d->filter.isFilteringByText()) - { - return; - } - - // is one of our images affected? - foreach(const qlonglong& id, changeset.ids()) - { - // if one matching image id is found, trigger a refresh - if (d->imageModel->hasImage(id)) - { - d->updateFilterTimer->start(); - return; - } - } -} - -void ImageFilterModel::slotImageChange(const ImageChangeset& changeset) -{ - Q_D(ImageFilterModel); - - if (!d->imageModel || d->imageModel->isEmpty()) - { - return; - } - - // already scheduled to re-filter? - if (d->updateFilterTimer->isActive()) - { - return; - } - - // is one of the values affected that we filter or sort by? - DatabaseFields::Set set = changeset.changes(); - bool sortAffected = (set & d->sorter.watchFlags()); - bool filterAffected = (set & d->filter.watchFlags()) || (set & d->groupFilter.watchFlags()); - - if (!sortAffected && !filterAffected) - { - return; - } - - // is one of our images affected? - bool imageAffected = false; - - foreach(const qlonglong& id, changeset.ids()) - { - // if one matching image id is found, trigger a refresh - if (d->imageModel->hasImage(id)) - { - imageAffected = true; - break; - } - } - - if (!imageAffected) - { - return; - } - - if (filterAffected) - { - d->updateFilterTimer->start(); - } - else - { - invalidate(); // just resort, reuse filter results - } -} - -// ------------------------------------------------------------------------------------------------------- - -NoDuplicatesImageFilterModel::NoDuplicatesImageFilterModel(QObject* parent) - : ImageSortFilterModel(parent) -{ -} - -bool NoDuplicatesImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const -{ - QModelIndex index = sourceModel()->index(source_row, 0, source_parent); - - if (index.data(ImageModel::ExtraDataDuplicateCount).toInt() <= 1) - { - return true; - } - - QModelIndex previousIndex = sourceModel()->index(source_row - 1, 0, source_parent); - - if (!previousIndex.isValid()) - { - return true; - } - - if (sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(index)) == sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(previousIndex))) - { - return false; - } - return true; -} - -/* -void NoDuplicatesImageFilterModel::setSourceModel(QAbstractItemModel* model) -{ - if (sourceModel()) - { - } - - ImageSortFilterModel::setSourceModel(model); - - if (sourceModel()) - { - connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), - this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); - } -} - -void NoDuplicatesImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& parent, int begin, int end) -{ - bool needInvalidate = false; - - for (int i = begin; i<=end; ++i) - { - QModelIndex index = sourceModel()->index(i, 0, parent); - - // filtered out by us? - if (!mapFromSource(index).isValid()) - { - continue; - } - - QModelIndex sourceIndex = mapFromDirectSourceToSourceImageModel(index); - qlonglong id = sourceImageModel()->imageId(sourceIndex); - - if (sourceImageModel()->numberOfIndexesForImageId(id) > 1) - { - needInvalidate = true; - } - } -}*/ - -} // namespace Digikam diff --git a/libs/models/imagefiltermodel.h b/libs/models/imagefiltermodel.h deleted file mode 100644 index d131b3e..0000000 --- a/libs/models/imagefiltermodel.h +++ /dev/null @@ -1,299 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-05 - * Description : Qt item model for database entries - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * Copyright (C) 2011 by Gilles Caulier - * Copyright (C) 2010 by Andi Clemens - * Copyright (C) 2011 by Michael G. Hansen - * Copyright (C) 2014 by Mohamed Anwer - * - * 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 IMAGEFILTERMODEL_H -#define IMAGEFILTERMODEL_H - -// Local includes - -#include "dcategorizedsortfilterproxymodel.h" -#include "textfilter.h" -#include "imagefiltersettings.h" -#include "imagemodel.h" -#include "imagesortsettings.h" -#include "digikam_export.h" - -namespace Digikam -{ - -class ImageChangeset; -class ImageFilterModel; -class ImageTagChangeset; - -class DIGIKAM_DATABASE_EXPORT ImageFilterModelPrepareHook -{ -public: - - virtual ~ImageFilterModelPrepareHook() {}; - virtual void prepare(const QVector& infos) = 0; -}; - -// ----------------------------------------------------------------------------------------------- - -class DIGIKAM_DATABASE_EXPORT ImageSortFilterModel : public DCategorizedSortFilterProxyModel -{ - Q_OBJECT - -public: - - explicit ImageSortFilterModel(QObject* parent = 0); - - void setSourceImageModel(ImageModel* model); - ImageModel* sourceImageModel() const; - - void setSourceFilterModel(ImageSortFilterModel* model); - ImageSortFilterModel* sourceFilterModel() const; - - QModelIndex mapToSourceImageModel(const QModelIndex& index) const; - QModelIndex mapFromSourceImageModel(const QModelIndex& imagemodel_index) const; - QModelIndex mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const; - - /// Convenience methods mapped to ImageModel. - /// Mentioned indexes returned come from the source image model. - QList mapListToSource(const QList& indexes) const; - QList mapListFromSource(const QList& sourceIndexes) const; - - ImageInfo imageInfo(const QModelIndex& index) const; - qlonglong imageId(const QModelIndex& index) const; - QList imageInfos(const QList& indexes) const; - QList imageIds(const QList& indexes) const; - - QModelIndex indexForPath(const QString& filePath) const; - QModelIndex indexForImageInfo(const ImageInfo& info) const; - QModelIndex indexForImageId(qlonglong id) const; - - /** Returns a list of all image infos, sorted according to this model. - * If you do not need a sorted list, use ImageModel's imageInfos() method. - */ - QList imageInfosSorted() const; - - /// Returns this, any chained ImageFilterModel, or 0. - virtual ImageFilterModel* imageFilterModel() const; - -protected: - - /// Reimplement if needed. Called only when model shall be set as (direct) sourceModel. - virtual void setDirectSourceImageModel(ImageModel* model); - - // made protected - virtual void setSourceModel(QAbstractItemModel* model); - -protected: - - ImageSortFilterModel* m_chainedModel; -}; - -// ----------------------------------------------------------------------------------------------- - -class DIGIKAM_DATABASE_EXPORT ImageFilterModel : public ImageSortFilterModel -{ - Q_OBJECT - -public: - - enum ImageFilterModelRoles - { - /// Returns the current categorization mode - CategorizationModeRole = ImageModel::FilterModelRoles + 1, - /// Returns the current sort order - SortOrderRole = ImageModel::FilterModelRoles + 2, - // / Returns the number of items in the index' category - //CategoryCountRole = ImageModel::FilterModelRoles + 3, - /// Returns the id of the PAlbum of the index which is used for category - CategoryAlbumIdRole = ImageModel::FilterModelRoles + 3, - /// Returns the format of the index which is used for category - CategoryFormatRole = ImageModel::FilterModelRoles + 4, - /// Returns true if the given image is a group leader, and the group is opened - GroupIsOpenRole = ImageModel::FilterModelRoles + 5, - ImageFilterModelPointerRole = ImageModel::FilterModelRoles + 50 - }; - -public: - - explicit ImageFilterModel(QObject* parent = 0); - ~ImageFilterModel(); - - /** Add a hook to get added images for preparation tasks before they are added in the model */ - void addPrepareHook(ImageFilterModelPrepareHook* hook); - void removePrepareHook(ImageFilterModelPrepareHook* hook); - - /** Returns a set of DatabaseFields suggested to set as watch flags on the source ImageModel. - * The contained flags will be those that this model can sort or filter by. */ - DatabaseFields::Set suggestedWatchFlags() const; - - ImageFilterSettings imageFilterSettings() const; - VersionImageFilterSettings versionImageFilterSettings() const; - GroupImageFilterSettings groupImageFilterSettings() const; - ImageSortSettings imageSortSettings() const; - - // group is identified by the id of its group leader - bool isGroupOpen(qlonglong group) const; - bool isAllGroupsOpen() const; - - /// Enables sending imageInfosAdded and imageInfosAboutToBeRemoved - void setSendImageInfoSignals(bool sendSignals); - - virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; - virtual ImageFilterModel* imageFilterModel() const; - -public Q_SLOTS: - - /** Changes the current version image filter settings and refilters. */ - void setVersionImageFilterSettings(const VersionImageFilterSettings& settings); - - /** Changes the current version image filter settings and refilters. */ - void setGroupImageFilterSettings(const GroupImageFilterSettings& settings); - - /** Adjust the current ImageFilterSettings. - * Equivalent to retrieving the current filter settings, adjusting the parameter - * and calling setImageFilterSettings. - * Provided for convenience. - * It is encouraged to use setImageFilterSettings if you change more than one - * parameter at a time. - */ - void setDayFilter(const QList& days); - void setTagFilter(const QList& includedTags, const QList& excludedTags, - ImageFilterSettings::MatchingCondition matchingCond, bool showUnTagged, - const QList& clTagIds, const QList& plTagIds); - void setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded); - void setMimeTypeFilter(int mimeTypeFilter); - void setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition); - void setTextFilter(const SearchTextFilterSettings& settings); - - void setCategorizationMode(ImageSortSettings::CategorizationMode mode); - void setCategorizationSortOrder(ImageSortSettings::SortOrder order); - void setSortRole(ImageSortSettings::SortRole role); - void setSortOrder(ImageSortSettings::SortOrder order); - void setStringTypeNatural(bool natural); - void setUrlWhitelist(const QList urlList, const QString& id); - void setIdWhitelist(const QList& idList, const QString& id); - - void setVersionManagerSettings(const VersionManagerSettings& settings); - void setExceptionList(const QList& idlist, const QString& id); - - void setGroupOpen(qlonglong group, bool open); - void toggleGroupOpen(qlonglong group); - void setAllGroupsOpen(bool open); - - /** Changes the current image filter settings and refilters. */ - virtual void setImageFilterSettings(const ImageFilterSettings& settings); - - /** Changes the current image sort settings and resorts. */ - virtual void setImageSortSettings(const ImageSortSettings& settings); - -Q_SIGNALS: - - /// Signals that the set filter matches at least one index - void filterMatches(bool matches); - - /** Signals that the set text filter matches at least one entry. - If no text filter is set, this signal is emitted - with 'false' when filterMatches() is emitted. - */ - void filterMatchesForText(bool matchesByText); - - /** Emitted when the filter settings have been changed - (the model may not yet have been updated) - */ - void filterSettingsChanged(const ImageFilterSettings& settings); - - /** These signals need to be explicitly enabled with setSendImageInfoSignals() - */ - void imageInfosAdded(const QList& infos); - void imageInfosAboutToBeRemoved(const QList& infos); - -public: - - // Declared as public because of use in sub-classes. - class ImageFilterModelPrivate; - -protected: - - ImageFilterModelPrivate* const d_ptr; - -protected: - - ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent); - - virtual void setDirectSourceImageModel(ImageModel* model); - - virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; - - virtual int compareCategories(const QModelIndex& left, const QModelIndex& right) const; - virtual bool subSortLessThan(const QModelIndex& left, const QModelIndex& right) const; - //virtual int categoryCount(const ImageInfo& info) const; - - /** Reimplement to customize category sorting, - * Return negative if category of left < category right, - * Return 0 if left and right are in the same category, else return positive. - */ - virtual int compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const; - - /** Reimplement to customize sorting. Do not take categories into account here. - */ - virtual bool infosLessThan(const ImageInfo& left, const ImageInfo& right) const; - - /** Returns a unique identifier for the category if info. The string need not be for user display. - */ - virtual QString categoryIdentifier(const ImageInfo& info) const; - -protected Q_SLOTS: - - void slotModelReset(); - void slotUpdateFilter(); - - void slotImageTagChange(const ImageTagChangeset& changeset); - void slotImageChange(const ImageChangeset& changeset); - - void slotRowsInserted(const QModelIndex& parent, int start, int end); - void slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); - -private: - - Q_DECLARE_PRIVATE(ImageFilterModel) -}; - -// ----------------------------------------------------------------------------------------------------- - -class DIGIKAM_DATABASE_EXPORT NoDuplicatesImageFilterModel : public ImageSortFilterModel -{ - Q_OBJECT - -public: - - explicit NoDuplicatesImageFilterModel(QObject* parent = 0); - -protected: - - virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; -}; - -} // namespace Digikam - -Q_DECLARE_METATYPE(Digikam::ImageFilterModel*) - -#endif // IMAGEMODEL_H diff --git a/libs/models/imagefiltermodelpriv.cpp b/libs/models/imagefiltermodelpriv.cpp deleted file mode 100644 index 07d9e79..0000000 --- a/libs/models/imagefiltermodelpriv.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-05 - * Description : Qt item model for database entries - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * Copyright (C) 2011-2017 by Gilles Caulier - * Copyright (C) 2010 by Andi Clemens - * Copyright (C) 2011 by Michael G. Hansen - * Copyright (C) 2014 by Mohamed Anwer - * - * 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 "imagefiltermodelpriv.h" - -// Local includes - -#include "digikam_debug.h" -#include "imagefiltermodelthreads.h" - -namespace Digikam -{ - -ImageFilterModel::ImageFilterModelPrivate::ImageFilterModelPrivate() -{ - imageModel = 0; - version = 0; - lastDiscardVersion = 0; - sentOut = 0; - sentOutForReAdd = 0; - updateFilterTimer = 0; - needPrepare = false; - needPrepareComments = false; - needPrepareTags = false; - needPrepareGroups = false; - preparer = 0; - filterer = 0; - hasOneMatch = false; - hasOneMatchForText = false; - - setupWorkers(); -} - -ImageFilterModel::ImageFilterModelPrivate::~ImageFilterModelPrivate() -{ - // facilitate thread stopping - ++version; - preparer->deactivate(); - filterer->deactivate(); - delete preparer; - delete filterer; -} - -void ImageFilterModel::ImageFilterModelPrivate::init(ImageFilterModel* _q) -{ - q = _q; - - updateFilterTimer = new QTimer(this); - updateFilterTimer->setSingleShot(true); - updateFilterTimer->setInterval(250); - - connect(updateFilterTimer, SIGNAL(timeout()), - q, SLOT(slotUpdateFilter())); - - // inter-thread redirection - qRegisterMetaType("ImageFilterModelTodoPackage"); -} - -void ImageFilterModel::ImageFilterModelPrivate::preprocessInfos(const QList& infos, const QList& extraValues) -{ - infosToProcess(infos, extraValues, true); -} - -void ImageFilterModel::ImageFilterModelPrivate::processAddedInfos(const QList& infos, const QList& extraValues) -{ - // These have already been added, we just process them afterwards - infosToProcess(infos, extraValues, false); -} - -void ImageFilterModel::ImageFilterModelPrivate::setupWorkers() -{ - preparer = new ImageFilterModelPreparer(this); - filterer = new ImageFilterModelFilterer(this); - - // A package in constructed in infosToProcess. - // Normal flow is infosToProcess -> preparer::process -> filterer::process -> packageFinished. - // If no preparation is needed, the first step is skipped. - // If filter version changes, both will discard old package and send them to packageDiscarded. - - connect(this, SIGNAL(packageToPrepare(ImageFilterModelTodoPackage)), - preparer, SLOT(process(ImageFilterModelTodoPackage))); - - connect(this, SIGNAL(packageToFilter(ImageFilterModelTodoPackage)), - filterer, SLOT(process(ImageFilterModelTodoPackage))); - - connect(preparer, SIGNAL(processed(ImageFilterModelTodoPackage)), - filterer, SLOT(process(ImageFilterModelTodoPackage))); - - connect(filterer, SIGNAL(processed(ImageFilterModelTodoPackage)), - this, SLOT(packageFinished(ImageFilterModelTodoPackage))); - - connect(preparer, SIGNAL(discarded(ImageFilterModelTodoPackage)), - this, SLOT(packageDiscarded(ImageFilterModelTodoPackage))); - - connect(filterer, SIGNAL(discarded(ImageFilterModelTodoPackage)), - this, SLOT(packageDiscarded(ImageFilterModelTodoPackage))); -} - -void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList& infos) -{ - infosToProcess(infos, QList(), false); -} - -void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList& infos, const QList& extraValues, bool forReAdd) -{ - if (infos.isEmpty()) - { - return; - } - - filterer->schedule(); - - if (needPrepare) - { - preparer->schedule(); - } - - Q_ASSERT(extraValues.isEmpty() || infos.size() == extraValues.size()); - - // prepare and filter in chunks - const int size = infos.size(); - const int maxChunkSize = needPrepare ? PrepareChunkSize : FilterChunkSize; - const bool hasExtraValues = !extraValues.isEmpty(); - QList::const_iterator it = infos.constBegin(), end; - QList::const_iterator xit = extraValues.constBegin(), xend; - int index = 0; - QVector infoVector; - QVector extraValueVector; - - while (it != infos.constEnd()) - { - const int chunkSize = qMin(maxChunkSize, size - index); - infoVector.resize(chunkSize); - end = it + chunkSize; - qCopy(it, end, infoVector.begin()); - - if (hasExtraValues) - { - extraValueVector.resize(chunkSize); - xend = xit + chunkSize; - qCopy(xit, xend, extraValueVector.begin()); - xit = xend; - } - - it = end; - index += chunkSize; - - ++sentOut; - - if (forReAdd) - { - ++sentOutForReAdd; - } - - if (needPrepare) - { - emit packageToPrepare(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd)); - } - else - { - emit packageToFilter(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd)); - } - } -} - -void ImageFilterModel::ImageFilterModelPrivate::packageFinished(const ImageFilterModelTodoPackage& package) -{ - // check if it got discarded on the journey - if (package.version != version) - { - packageDiscarded(package); - return; - } - - // incorporate result - QHash::const_iterator it = package.filterResults.constBegin(); - - for (; it != package.filterResults.constEnd(); ++it) - { - filterResults.insert(it.key(), it.value()); - } - - // re-add if necessary - if (package.isForReAdd) - { - emit reAddImageInfos(package.infos.toList(), package.extraValues.toList()); - - if (sentOutForReAdd == 1) // last package - { - emit reAddingFinished(); - } - } - - // decrement counters - --sentOut; - - if (package.isForReAdd) - { - --sentOutForReAdd; - } - - // If all packages have returned, filtered and readded, and no more are expected, - // and there is need to tell the filter result to the view, do that - if (sentOut == 0 && sentOutForReAdd == 0 && !imageModel->isRefreshing()) - { - q->invalidate(); // use invalidate, not invalidateFilter only. Sorting may have changed as well. - emit (q->filterMatches(hasOneMatch)); - emit (q->filterMatchesForText(hasOneMatchForText)); - filterer->deactivate(); - preparer->deactivate(); - } -} - -void ImageFilterModel::ImageFilterModelPrivate::packageDiscarded(const ImageFilterModelTodoPackage& package) -{ - // Either, the model was reset, or the filter changed - // In the former case throw all away, in the latter case, recycle - if (package.version > lastDiscardVersion) - { - // Recycle packages: Send again with current version - // Do not increment sentOut or sentOutForReAdd here: it was not decremented! - - if (needPrepare) - { - emit packageToPrepare(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd)); - } - else - { - emit packageToFilter(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd)); - } - } -} - -} // namespace Digikam diff --git a/libs/models/imagefiltermodelpriv.h b/libs/models/imagefiltermodelpriv.h deleted file mode 100644 index a9e3f22..0000000 --- a/libs/models/imagefiltermodelpriv.h +++ /dev/null @@ -1,159 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-11 - * Description : Qt item model for database entries - private shared header - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * - * 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 IMAGEFILTERMODELPRIV_H -#define IMAGEFILTERMODELPRIV_H - -// Qt includes - -#include -#include -#include -#include -#include -#include -#include - -// Local includes - -#include "imageinfo.h" -#include "imagefiltermodel.h" - -#include "digikam_export.h" -// Yes, we need the EXPORT macro in a private header because -// this private header is shared across binary objects. -// This does NOT make this classes here any more public! - -namespace Digikam -{ - -const int PrepareChunkSize = 101; -const int FilterChunkSize = 2001; - -class ImageFilterModelTodoPackage -{ -public: - - ImageFilterModelTodoPackage() - : version(0), isForReAdd(false) - { - } - - ImageFilterModelTodoPackage(const QVector& infos, const QVector& extraValues, int version, bool isForReAdd) - : infos(infos), extraValues(extraValues), version(version), isForReAdd(isForReAdd) - { - } - - QVector infos; - QVector extraValues; - unsigned int version; - bool isForReAdd; - QHash filterResults; -}; - -// ------------------------------------------------------------------------------------------------ - -class ImageFilterModelPreparer; -class ImageFilterModelFilterer; - -class DIGIKAM_DATABASE_EXPORT ImageFilterModel::ImageFilterModelPrivate : public QObject -{ - Q_OBJECT - -public: - - ImageFilterModelPrivate(); - ~ImageFilterModelPrivate(); - - void init(ImageFilterModel* q); - void setupWorkers(); - void infosToProcess(const QList& infos); - void infosToProcess(const QList& infos, const QList& extraValues, bool forReAdd = true); - -public: - - ImageFilterModel* q; - - ImageModel* imageModel; - - ImageFilterSettings filter; - ImageSortSettings sorter; - VersionImageFilterSettings versionFilter; - GroupImageFilterSettings groupFilter; - - volatile unsigned int version; - unsigned int lastDiscardVersion; - unsigned int lastFilteredVersion; - int sentOut; - int sentOutForReAdd; - - QTimer* updateFilterTimer; - - bool needPrepare; - bool needPrepareComments; - bool needPrepareTags; - bool needPrepareGroups; - - QMutex mutex; - ImageFilterSettings filterCopy; - VersionImageFilterSettings versionFilterCopy; - GroupImageFilterSettings groupFilterCopy; - ImageFilterModelPreparer* preparer; - ImageFilterModelFilterer* filterer; - - QHash filterResults; - bool hasOneMatch; - bool hasOneMatchForText; - - QList prepareHooks; - -/* - QHash > categoryCountHashInt; - QHash > categoryCountHashString; - -public: - - void cacheCategoryCount(int id, qlonglong imageid) const - { const_cast(this)->categoryCountHashInt[id].insert(imageid); } - void cacheCategoryCount(const QString& id, qlonglong imageid) const - { const_cast(this)->categoryCountHashString[id].insert(imageid); } -*/ - -public Q_SLOTS: - - void preprocessInfos(const QList& infos, const QList& extraValues); - void processAddedInfos(const QList& infos, const QList& extraValues); - void packageFinished(const ImageFilterModelTodoPackage& package); - void packageDiscarded(const ImageFilterModelTodoPackage& package); - -Q_SIGNALS: - - void packageToPrepare(const ImageFilterModelTodoPackage& package); - void packageToFilter(const ImageFilterModelTodoPackage& package); - void reAddImageInfos(const QList& infos, const QList& extraValues); - void reAddingFinished(); -}; - -} // namespace Digikam - -#endif // IMAGEFILTERMODELPRIV_H diff --git a/libs/models/imagefiltermodelthreads.cpp b/libs/models/imagefiltermodelthreads.cpp deleted file mode 100644 index aa5c462..0000000 --- a/libs/models/imagefiltermodelthreads.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-05 - * Description : Qt item model for database entries - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * Copyright (C) 2011-2017 by Gilles Caulier - * Copyright (C) 2010 by Andi Clemens - * Copyright (C) 2011 by Michael G. Hansen - * Copyright (C) 2014 by Mohamed Anwer - * - * 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 "imagefiltermodel.h" -#include "imagefiltermodelpriv.h" -#include "imagefiltermodelthreads.h" - -namespace Digikam -{ - -ImageFilterModelWorker::ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d) - : d(d) -{ -} - -} // namespace Digikam diff --git a/libs/models/imagefiltermodelthreads.h b/libs/models/imagefiltermodelthreads.h deleted file mode 100644 index 83fa987..0000000 --- a/libs/models/imagefiltermodelthreads.h +++ /dev/null @@ -1,100 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-11 - * Description : Qt item model for database entries - private header - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * - * 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 IMAGEFILTERMODELTHREADS_H -#define IMAGEFILTERMODELTHREADS_H - -// Qt includes - -#include - -// Local includes - -#include "digikam_export.h" -#include "workerobject.h" - -namespace Digikam -{ - -class DIGIKAM_DATABASE_EXPORT ImageFilterModelWorker : public WorkerObject -{ - Q_OBJECT - -public: - - explicit ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d); - - bool checkVersion(const ImageFilterModelTodoPackage& package) - { - return d->version == package.version; - } - -public Q_SLOTS: - - virtual void process(ImageFilterModelTodoPackage package) = 0; - -Q_SIGNALS: - - void processed(const ImageFilterModelTodoPackage& package); - void discarded(const ImageFilterModelTodoPackage& package); - -protected: - - ImageFilterModel::ImageFilterModelPrivate* d; -}; - -// ----------------------------------------------------------------------------------------- - -class DIGIKAM_DATABASE_EXPORT ImageFilterModelPreparer : public ImageFilterModelWorker -{ - Q_OBJECT - -public: - - explicit ImageFilterModelPreparer(ImageFilterModel::ImageFilterModelPrivate* const d) - : ImageFilterModelWorker(d) - { - } - - void process(ImageFilterModelTodoPackage package); -}; - -// ---------------------------------------------------------------------------------------- - -class DIGIKAM_DATABASE_EXPORT ImageFilterModelFilterer : public ImageFilterModelWorker -{ - Q_OBJECT - -public: - - explicit ImageFilterModelFilterer(ImageFilterModel::ImageFilterModelPrivate* const d) - : ImageFilterModelWorker(d) - { - } - - void process(ImageFilterModelTodoPackage package); -}; - -} // namespace Digikam - -#endif // IMAGEFILTERMODELTHREADS_H diff --git a/libs/models/imagefiltersettings.cpp b/libs/models/imagefiltersettings.cpp deleted file mode 100644 index b61e7f9..0000000 --- a/libs/models/imagefiltersettings.cpp +++ /dev/null @@ -1,952 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-05 - * Description : Filter values for use with ImageFilterModel - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * Copyright (C) 2011-2017 by Gilles Caulier - * Copyright (C) 2010 by Andi Clemens - * Copyright (C) 2011 by Michael G. Hansen - * Copyright (C) 2014 by Mohamed Anwer - * - * 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 "imagefiltersettings.h" - -// C++ includes - -#include - -// Qt includes - -#include - -// Local includes - -#include "digikam_debug.h" -#include "coredbfields.h" -#include "digikam_globals.h" -#include "imageinfo.h" -#include "tagscache.h" -#include "versionmanagersettings.h" - -namespace Digikam -{ - -ImageFilterSettings::ImageFilterSettings() -{ - m_untaggedFilter = false; - m_isUnratedExcluded = false; - m_ratingFilter = 0; - m_mimeTypeFilter = MimeFilter::AllFiles; - m_ratingCond = GreaterEqualCondition; - m_matchingCond = OrCondition; - m_geolocationCondition = GeolocationNoFilter; -} - -DatabaseFields::Set ImageFilterSettings::watchFlags() const -{ - DatabaseFields::Set set; - - if (isFilteringByDay()) - { - set |= DatabaseFields::CreationDate; - } - - if (isFilteringByText()) - { - set |= DatabaseFields::Name; - set |= DatabaseFields::Comment; - } - - if (isFilteringByRating()) - { - set |= DatabaseFields::Rating; - } - - if (isFilteringByTypeMime()) - { - set |= DatabaseFields::Category; - set |= DatabaseFields::Format; - } - - if (isFilteringByGeolocation()) - { - set |= DatabaseFields::ImagePositionsAll; - } - - if (isFilteringByColorLabels()) - { - set |= DatabaseFields::ColorLabel; - } - - if (isFilteringByPickLabels()) - { - set |= DatabaseFields::PickLabel; - } - - return set; -} - -bool ImageFilterSettings::isFilteringByDay() const -{ - if (!m_dayFilter.isEmpty()) - { - return true; - } - - return false; -} - -bool ImageFilterSettings::isFilteringByTags() const -{ - if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty() || m_untaggedFilter) - { - return true; - } - - return false; -} - -bool ImageFilterSettings::isFilteringByColorLabels() const -{ - if (!m_colorLabelTagFilter.isEmpty()) - { - return true; - } - - return false; -} - -bool ImageFilterSettings::isFilteringByPickLabels() const -{ - if (!m_pickLabelTagFilter.isEmpty()) - { - return true; - } - - return false; -} - -bool ImageFilterSettings::isFilteringByText() const -{ - if (!m_textFilterSettings.text.isEmpty()) - { - return true; - } - - return false; -} - -bool ImageFilterSettings::isFilteringByTypeMime() const -{ - if (m_mimeTypeFilter != MimeFilter::AllFiles) - { - return true; - } - - return false; -} - -bool ImageFilterSettings::isFilteringByGeolocation() const -{ - return (m_geolocationCondition != GeolocationNoFilter); -} - -bool ImageFilterSettings::isFilteringByRating() const -{ - if (m_ratingFilter != 0 || m_ratingCond != GreaterEqualCondition || m_isUnratedExcluded) - { - return true; - } - - return false; -} - -bool ImageFilterSettings::isFilteringInternally() const -{ - return (isFiltering() || !m_urlWhitelists.isEmpty() || !m_idWhitelists.isEmpty()); -} - -bool ImageFilterSettings::isFiltering() const -{ - return isFilteringByDay() || - isFilteringByTags() || - isFilteringByText() || - isFilteringByRating() || - isFilteringByTypeMime() || - isFilteringByColorLabels() || - isFilteringByPickLabels() || - isFilteringByGeolocation(); -} - -void ImageFilterSettings::setDayFilter(const QList& days) -{ - m_dayFilter.clear(); - - for (QList::const_iterator it = days.constBegin(); it != days.constEnd(); ++it) - { - m_dayFilter.insert(*it, true); - } -} - -void ImageFilterSettings::setTagFilter(const QList& includedTags, - const QList& excludedTags, - MatchingCondition matchingCondition, - bool showUnTagged, - const QList& clTagIds, - const QList& plTagIds) -{ - m_includeTagFilter = includedTags; - m_excludeTagFilter = excludedTags; - m_matchingCond = matchingCondition; - m_untaggedFilter = showUnTagged; - m_colorLabelTagFilter = clTagIds; - m_pickLabelTagFilter = plTagIds; -} - -void ImageFilterSettings::setRatingFilter(int rating, RatingCondition ratingCondition, bool isUnratedExcluded) -{ - m_ratingFilter = rating; - m_ratingCond = ratingCondition; - m_isUnratedExcluded = isUnratedExcluded; -} - -void ImageFilterSettings::setMimeTypeFilter(int mime) -{ - m_mimeTypeFilter = (MimeFilter::TypeMimeFilter)mime; -} - -void ImageFilterSettings::setGeolocationFilter(const GeolocationCondition& condition) -{ - m_geolocationCondition = condition; -} - -void ImageFilterSettings::setTextFilter(const SearchTextFilterSettings& settings) -{ - m_textFilterSettings = settings; -} - -void ImageFilterSettings::setTagNames(const QHash& hash) -{ - m_tagNameHash = hash; -} - -void ImageFilterSettings::setAlbumNames(const QHash& hash) -{ - m_albumNameHash = hash; -} - -void ImageFilterSettings::setUrlWhitelist(const QList& urlList, const QString& id) -{ - if (urlList.isEmpty()) - { - m_urlWhitelists.remove(id); - } - else - { - m_urlWhitelists.insert(id, urlList); - } -} - -void ImageFilterSettings::setIdWhitelist(const QList& idList, const QString& id) -{ - if (idList.isEmpty()) - { - m_idWhitelists.remove(id); - } - else - { - m_idWhitelists.insert(id, idList); - } -} - -template -bool containsAnyOf(const ContainerA& listA, const ContainerB& listB) -{ - foreach (const typename ContainerA::value_type& a, listA) - { - if (listB.contains(a)) - { - return true; - } - } - return false; -} - -template -bool containsNoneOfExcept(const ContainerA& list, const ContainerB& noneOfList, const Value& exception) -{ - foreach (const typename ContainerB::value_type& n, noneOfList) - { - if (n != exception && list.contains(n)) - { - return false; - } - } - return true; -} - -bool ImageFilterSettings::matches(const ImageInfo& info, bool* const foundText) const -{ - if (foundText) - { - *foundText = false; - } - - if (!isFilteringInternally()) - { - return true; - } - - bool match = false; - - if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty()) - { - QList tagIds = info.tagIds(); - QList::const_iterator it; - - match = m_includeTagFilter.isEmpty(); - - if (m_matchingCond == OrCondition) - { - for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it) - { - if (tagIds.contains(*it)) - { - match = true; - break; - } - } - - match |= (m_untaggedFilter && tagIds.isEmpty()); - } - else // AND matching condition... - { - // m_untaggedFilter and non-empty tag filter, combined with AND, is logically no match - if (!m_untaggedFilter) - { - for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it) - { - if (!tagIds.contains(*it)) - { - break; - } - } - - if (it == m_includeTagFilter.end()) - { - match = true; - } - } - } - - for (it = m_excludeTagFilter.begin(); it != m_excludeTagFilter.end(); ++it) - { - if (tagIds.contains(*it)) - { - match = false; - break; - } - } - } - else if (m_untaggedFilter) - { - match = !TagsCache::instance()->containsPublicTags(info.tagIds()); - } - else - { - match = true; - } - - //-- Filter by pick labels ------------------------------------------------ - - if (!m_pickLabelTagFilter.isEmpty()) - { - QList tagIds = info.tagIds(); - bool matchPL = false; - - if (containsAnyOf(m_pickLabelTagFilter, tagIds)) - { - matchPL = true; - } - else if (!matchPL) - { - int noPickLabelTagId = TagsCache::instance()->tagForPickLabel(NoPickLabel); - - if (m_pickLabelTagFilter.contains(noPickLabelTagId)) - { - // Searching for "has no ColorLabel" requires special handling: - // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag - matchPL = containsNoneOfExcept(tagIds, TagsCache::instance()->pickLabelTags(), noPickLabelTagId); - } - } - - match &= matchPL; - } - - //-- Filter by color labels ------------------------------------------------ - - if (!m_colorLabelTagFilter.isEmpty()) - { - QList tagIds = info.tagIds(); - bool matchCL = false; - - if (containsAnyOf(m_colorLabelTagFilter, tagIds)) - { - matchCL = true; - } - else if (!matchCL) - { - int noColorLabelTagId = TagsCache::instance()->tagForColorLabel(NoColorLabel); - - if (m_colorLabelTagFilter.contains(noColorLabelTagId)) - { - // Searching for "has no ColorLabel" requires special handling: - // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag - matchCL = containsNoneOfExcept(tagIds, TagsCache::instance()->colorLabelTags(), noColorLabelTagId); - } - } - - match &= matchCL; - } - - //-- Filter by date ----------------------------------------------------------- - - if (!m_dayFilter.isEmpty()) - { - match &= m_dayFilter.contains(QDateTime(info.dateTime().date(), QTime())); - } - - //-- Filter by rating --------------------------------------------------------- - - if (m_ratingFilter >= 0) - { - // for now we treat -1 (no rating) just like a rating of 0. - int rating = info.rating(); - - if (rating == -1) - { - rating = 0; - } - - if(m_isUnratedExcluded && rating == 0) - { - match = false; - } - else - { - if (m_ratingCond == GreaterEqualCondition) - { - // If the rating is not >=, i.e it is <, then it does not match. - if (rating < m_ratingFilter) - { - match = false; - } - } - else if (m_ratingCond == EqualCondition) - { - // If the rating is not =, i.e it is !=, then it does not match. - if (rating != m_ratingFilter) - { - match = false; - } - } - else - { - // If the rating is not <=, i.e it is >, then it does not match. - if (rating > m_ratingFilter) - { - match = false; - } - } - } - } - - // -- Filter by mime type ----------------------------------------------------- - - switch (m_mimeTypeFilter) - { - // info.format is a standardized string: Only one possibility per mime type - case MimeFilter::ImageFiles: - { - if (info.category() != DatabaseItem::Image) - { - match = false; - } - - break; - } - case MimeFilter::JPGFiles: - { - if (info.format() != QLatin1String("JPG")) - { - match = false; - } - - break; - } - case MimeFilter::PNGFiles: - { - if (info.format() != QLatin1String("PNG")) - { - match = false; - } - - break; - } - case MimeFilter::TIFFiles: - { - if (info.format() != QLatin1String("TIFF")) - { - match = false; - } - - break; - } - case MimeFilter::DNGFiles: - { - if (info.format() != QLatin1String("RAW-DNG")) - { - match = false; - } - - break; - } - case MimeFilter::NoRAWFiles: - { - if (info.format().startsWith(QLatin1String("RAW"))) - { - match = false; - } - - break; - } - case MimeFilter::RAWFiles: - { - if (!info.format().startsWith(QLatin1String("RAW"))) - { - match = false; - } - - break; - } - case MimeFilter::MoviesFiles: - { - if (info.category() != DatabaseItem::Video) - { - match = false; - } - - break; - } - case MimeFilter::AudioFiles: - { - if (info.category() != DatabaseItem::Audio) - { - match = false; - } - - break; - } - case MimeFilter::RasterFiles: - { - if (info.format() != QLatin1String("PSD") && // Adobe Photoshop Document - info.format() != QLatin1String("PSB") && // Adobe Photoshop Big - info.format() != QLatin1String("XCF") && // Gimp - info.format() != QLatin1String("KRA") && // Krita - info.format() != QLatin1String("ORA") // Open Raster - ) - { - match = false; - } - - break; - } - default: - { - // All Files: do nothing... - break; - } - } - - //-- Filter by geolocation ---------------------------------------------------- - - if (m_geolocationCondition!=GeolocationNoFilter) - { - if (m_geolocationCondition==GeolocationNoCoordinates) - { - if (info.hasCoordinates()) - { - match = false; - } - } - else if (m_geolocationCondition==GeolocationHasCoordinates) - { - if (!info.hasCoordinates()) - { - match = false; - } - } - } - - //-- Filter by text ----------------------------------------------------------- - - if (!m_textFilterSettings.text.isEmpty()) - { - bool textMatch = false; - - // Image name - if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageName && - info.name().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) - { - textMatch = true; - } - - // Image title - if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageTitle && - info.title().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) - { - textMatch = true; - } - - // Image comment - if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageComment && - info.comment().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) - { - textMatch = true; - } - - // Tag names - foreach(int id, info.tagIds()) - { - if (m_textFilterSettings.textFields & SearchTextFilterSettings::TagName && - m_tagNameHash.value(id).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) - { - textMatch = true; - } - } - - // Album names - if (m_textFilterSettings.textFields & SearchTextFilterSettings::AlbumName && - m_albumNameHash.value(info.albumId()).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) - { - textMatch = true; - } - - // Image Aspect Ratio - if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageAspectRatio) - { - QRegExp expRatio (QLatin1String("^\\d+:\\d+$")); - QRegExp expFloat (QLatin1String("^\\d+(.\\d+)?$")); - - if (expRatio.indexIn(m_textFilterSettings.text) > -1 && m_textFilterSettings.text.contains(QRegExp(QLatin1String(":\\d+")))) - { - QString trimmedTextFilterSettingsText = m_textFilterSettings.text; - QStringList numberStringList = trimmedTextFilterSettingsText.split(QLatin1String(":"), QString::SkipEmptyParts); - - if (numberStringList.length() == 2) - { - QString numString = (QString)numberStringList.at(0), denomString = (QString)numberStringList.at(1); - bool canConverseNum = false; - bool canConverseDenom = false; - int num = numString.toInt(&canConverseNum, 10), denom = denomString.toInt(&canConverseDenom, 10); - - if (canConverseNum && canConverseDenom) - { - if (fabs(info.aspectRatio() - (double)num / denom) < 0.1) - textMatch = true; - } - } - } - else if (expFloat.indexIn(m_textFilterSettings.text) > -1) - { - QString trimmedTextFilterSettingsText = m_textFilterSettings.text; - bool canConverse = false; - double ratio = trimmedTextFilterSettingsText.toDouble(&canConverse); - - if (canConverse) - { - if (fabs(info.aspectRatio() - ratio) < 0.1) - textMatch = true; - } - } - } - - // Image Pixel Size - // See bug #341053 for details. - - if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImagePixelSize) - { - QSize size = info.dimensions(); - int pixelSize = size.height()*size.width(); - QString text = m_textFilterSettings.text; - - if(text.contains(QRegExp(QLatin1String("^>\\d{1,15}$"))) && pixelSize > (text.remove(0,1)).toInt()) - { - textMatch = true; - } - else if(text.contains(QRegExp(QLatin1String("^<\\d{1,15}$"))) && pixelSize < (text.remove(0,1)).toInt()) - { - textMatch = true; - } - else if(text.contains(QRegExp(QLatin1String("^\\d+$"))) && pixelSize == text.toInt()) - { - textMatch = true; - } - } - - match &= textMatch; - - if (foundText) - { - *foundText = textMatch; - } - } - - // -- filter by URL-whitelists ------------------------------------------------ - // NOTE: whitelists are always AND for now. - - if (match) - { - const QUrl url = info.fileUrl(); - - for (QHash>::const_iterator it = m_urlWhitelists.constBegin(); - it!=m_urlWhitelists.constEnd(); ++it) - { - match = it->contains(url); - - if (!match) - { - break; - } - } - } - - if (match) - { - const qlonglong id = info.id(); - - for (QHash >::const_iterator it = m_idWhitelists.constBegin(); - it!=m_idWhitelists.constEnd(); ++it) - { - match = it->contains(id); - - if (!match) - { - break; - } - } - } - - return match; -} - -// ------------------------------------------------------------------------------------------------- - -VersionImageFilterSettings::VersionImageFilterSettings() -{ - m_includeTagFilter = 0; - m_exceptionTagFilter = 0; -} - -VersionImageFilterSettings::VersionImageFilterSettings(const VersionManagerSettings& settings) -{ - setVersionManagerSettings(settings); -} - -bool VersionImageFilterSettings::operator==(const VersionImageFilterSettings& other) const -{ - return m_excludeTagFilter == other.m_excludeTagFilter && - m_exceptionLists == other.m_exceptionLists; -} - -bool VersionImageFilterSettings::matches(const ImageInfo& info) const -{ - if (!isFiltering()) - { - return true; - } - - const qlonglong id = info.id(); - - for (QHash >::const_iterator it = m_exceptionLists.constBegin(); - it != m_exceptionLists.constEnd(); ++it) - { - if (it->contains(id)) - { - return true; - } - } - - bool match = true; - QList tagIds = info.tagIds(); - - if (!tagIds.contains(m_includeTagFilter)) - { - for (QList::const_iterator it = m_excludeTagFilter.begin(); - it != m_excludeTagFilter.end(); ++it) - { - if (tagIds.contains(*it)) - { - match = false; - break; - } - } - } - - if (!match) - { - if (tagIds.contains(m_exceptionTagFilter)) - { - match = true; - } - } - - return match; -} - -bool VersionImageFilterSettings::isHiddenBySettings(const ImageInfo& info) const -{ - QList tagIds = info.tagIds(); - - foreach(int tagId, m_excludeTagFilter) - { - if (tagIds.contains(tagId)) - { - return true; - } - } - - return false; -} - -bool VersionImageFilterSettings::isExemptedBySettings(const ImageInfo& info) const -{ - return info.tagIds().contains(m_exceptionTagFilter); -} - -void VersionImageFilterSettings::setVersionManagerSettings(const VersionManagerSettings& settings) -{ - m_excludeTagFilter.clear(); - - if (!settings.enabled) - { - return; - } - - if (!(settings.showInViewFlags & VersionManagerSettings::ShowOriginal)) - { - m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion()); - } - - if (!(settings.showInViewFlags & VersionManagerSettings::ShowIntermediates)) - { - m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::intermediateVersion()); - } - - m_includeTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::currentVersion()); - m_exceptionTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::versionAlwaysVisible()); -} - -void VersionImageFilterSettings::setExceptionList(const QList& idList, const QString& id) -{ - if (idList.isEmpty()) - { - m_exceptionLists.remove(id); - } - else - { - m_exceptionLists.insert(id, idList); - } -} - -bool VersionImageFilterSettings::isFiltering() const -{ - return !m_excludeTagFilter.isEmpty(); -} - -bool VersionImageFilterSettings::isFilteringByTags() const -{ - return isFiltering(); -} - -// ------------------------------------------------------------------------------------------------- - -GroupImageFilterSettings::GroupImageFilterSettings() - : m_allOpen(false) -{ -} - -bool GroupImageFilterSettings::operator==(const GroupImageFilterSettings& other) const -{ - return (m_allOpen == other.m_allOpen && - m_openGroups == other.m_openGroups); -} - -bool GroupImageFilterSettings::matches(const ImageInfo& info) const -{ - if (m_allOpen) - { - return true; - } - - if (info.isGrouped()) - { - return m_openGroups.contains(info.groupImage().id()); - } - return true; -} - -void GroupImageFilterSettings::setOpen(qlonglong group, bool open) -{ - if (open) - { - m_openGroups << group; - } - else - { - m_openGroups.remove(group); - } -} - -bool GroupImageFilterSettings::isOpen(qlonglong group) const -{ - return m_openGroups.contains(group); -} - -void GroupImageFilterSettings::setAllOpen(bool open) -{ - m_allOpen = open; -} - -bool GroupImageFilterSettings::isAllOpen() const -{ - return m_allOpen; -} - -bool GroupImageFilterSettings::isFiltering() const -{ - return !m_allOpen; -} - -DatabaseFields::Set GroupImageFilterSettings::watchFlags() const -{ - return DatabaseFields::ImageRelations; -} - -} // namespace Digikam diff --git a/libs/models/imagefiltersettings.h b/libs/models/imagefiltersettings.h deleted file mode 100644 index 0e7beae..0000000 --- a/libs/models/imagefiltersettings.h +++ /dev/null @@ -1,349 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-05 - * Description : Filter values for use with ImageFilterModel - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * Copyright (C) 2011-2017 by Gilles Caulier - * Copyright (C) 2010 by Andi Clemens - * Copyright (C) 2011 by Michael G. Hansen - * Copyright (C) 2014 by Mohamed Anwer - * - * 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 IMAGEFILTERSETTINGS_H -#define IMAGEFILTERSETTINGS_H - -// Qt includes - -#include -#include -#include -#include -#include -#include - -// Local includes - -#include "searchtextbar.h" -#include "mimefilter.h" -#include "digikam_export.h" - -namespace Digikam -{ - -class ImageInfo; -class VersionManagerSettings; - -namespace DatabaseFields -{ - class Set; -} - -// --------------------------------------------------------------------------------------- - -class DIGIKAM_DATABASE_EXPORT SearchTextFilterSettings : public SearchTextSettings -{ - -public: - - enum TextFilterFields - { - None = 0x00, - ImageName = 0x01, - ImageTitle = 0x02, - ImageComment = 0x04, - TagName = 0x08, - AlbumName = 0x10, - ImageAspectRatio = 0x20, - ImagePixelSize = 0x40, - All = ImageName | ImageTitle | ImageComment | TagName | AlbumName | ImageAspectRatio | ImagePixelSize - }; - -public: - - SearchTextFilterSettings() - { - textFields = None; - } - - explicit SearchTextFilterSettings(const SearchTextSettings& settings) - { - caseSensitive = settings.caseSensitive; - text = settings.text; - textFields = None; - } - - TextFilterFields textFields; -}; - -// --------------------------------------------------------------------------------------- - -class DIGIKAM_DATABASE_EXPORT ImageFilterSettings -{ -public: - - ImageFilterSettings(); - - /** - * Returns true if the given ImageInfo matches the filter criteria. - * Optionally, foundText is set to true if it matched by text search. - */ - bool matches(const ImageInfo& info, bool* const foundText = 0) const; - -public: - - /// --- Tags filter --- - - /// Possible logical matching condition used to sort tags id. - enum MatchingCondition - { - OrCondition, - AndCondition - }; - - void setTagFilter(const QList& includedTags, - const QList& excludedTags, - MatchingCondition matchingCond, - bool showUnTagged, - const QList& clTagIds, - const QList& plTagIds); - -public: - - /// --- Rating filter --- - - /// Possible conditions used to filter rating: >=, =, <= - enum RatingCondition - { - GreaterEqualCondition, - EqualCondition, - LessEqualCondition - }; - - void setRatingFilter(int rating, RatingCondition ratingCond, bool isUnratedExcluded); - -public: - - /// --- Date filter --- - void setDayFilter(const QList& days); - -public: - - /// --- Text filter --- - void setTextFilter(const SearchTextFilterSettings& settings); - void setTagNames(const QHash& tagNameHash); - void setAlbumNames(const QHash& albumNameHash); - -public: - - /// --- Mime filter --- - void setMimeTypeFilter(int mimeTypeFilter); - -public: - - /// --- Geolocation filter - enum GeolocationCondition - { - GeolocationNoFilter = 0, - GeolocationNoCoordinates = 1 << 1, - GeolocationHasCoordinates = 1 << 2 - }; - - void setGeolocationFilter(const GeolocationCondition& condition); - -public: - - /// Returns if the day is a filter criteria - bool isFilteringByDay() const; - - /// Returns if the type mime is a filter criteria - bool isFilteringByTypeMime() const; - - /// Returns whether geolocation is a filter criteria - bool isFilteringByGeolocation() const; - - /// Returns if the rating is a filter criteria - bool isFilteringByRating() const; - - /// Returns if the pick labels is a filter criteria - bool isFilteringByPickLabels() const; - - /// Returns if the color labels is a filter criteria - bool isFilteringByColorLabels() const; - - /// Returns if the tag is a filter criteria - bool isFilteringByTags() const; - - /// Returns if the text (including comment) is a filter criteria - bool isFilteringByText() const; - - /// Returns if images will be filtered by these criteria at all - bool isFiltering() const; - -public: - - /// --- URL whitelist filter - void setUrlWhitelist(const QList& urlList, const QString& id); - -public: - - /// --- ID whitelist filter - void setIdWhitelist(const QList& idList, const QString& id); - -public: - - /// --- Change notification --- - - /** Returns database fields a change in which would affect the current filtering. - * To find out if an image tag change affects filtering, test isFilteringByTags(). - * The text filter will also be affected by changes in tags and album names. - */ - DatabaseFields::Set watchFlags() const; - -private: - - /** - * @brief Returns whether some internal filtering (whitelist by id or URL) or normal filtering is going on - */ - bool isFilteringInternally() const; - -private: - - /// --- Tags filter --- - bool m_untaggedFilter; - QList m_includeTagFilter; - QList m_excludeTagFilter; - MatchingCondition m_matchingCond; - QList m_colorLabelTagFilter; - QList m_pickLabelTagFilter; - - /// --- Rating filter --- - int m_ratingFilter; - RatingCondition m_ratingCond; - bool m_isUnratedExcluded; - - /// --- Date filter --- - QMap m_dayFilter; - - /// --- Text filter --- - SearchTextFilterSettings m_textFilterSettings; - - /// Helpers for text search: Set these if you want to search album or tag names with text search - QHash m_tagNameHash; - QHash m_albumNameHash; - - /// --- Mime filter --- - MimeFilter::TypeMimeFilter m_mimeTypeFilter; - - /// --- Geolocation filter - GeolocationCondition m_geolocationCondition; - - /// --- URL whitelist filter - QHash> m_urlWhitelists; - - /// --- ID whitelist filter - QHash > m_idWhitelists; -}; - -// --------------------------------------------------------------------------------------- - -class DIGIKAM_DATABASE_EXPORT VersionImageFilterSettings -{ -public: - - VersionImageFilterSettings(); - explicit VersionImageFilterSettings(const VersionManagerSettings& settings); - - bool operator==(const VersionImageFilterSettings& other) const; - - /** - * Returns true if the given ImageInfo matches the filter criteria. - */ - bool matches(const ImageInfo& info) const; - - bool isHiddenBySettings(const ImageInfo& info) const; - bool isExemptedBySettings(const ImageInfo& info) const; - - /// --- Tags filter --- - - void setVersionManagerSettings(const VersionManagerSettings& settings); - - /** - * Add list with exceptions: These images will be exempted from filtering by this filter - */ - void setExceptionList(const QList& idlist, const QString& id); - - /// Returns if images will be filtered by these criteria at all - bool isFiltering() const; - - /// Returns if the tag is a filter criteria - bool isFilteringByTags() const; - - /// DatabaseFields::Set watchFlags() const: Would return 0 - -protected: - - QList m_excludeTagFilter; - int m_includeTagFilter; - int m_exceptionTagFilter; - QHash > m_exceptionLists; -}; - -// --------------------------------------------------------------------------------------- - -class DIGIKAM_DATABASE_EXPORT GroupImageFilterSettings -{ -public: - - GroupImageFilterSettings(); - - bool operator==(const GroupImageFilterSettings& other) const; - - /** - * Returns true if the given ImageInfo matches the filter criteria. - */ - bool matches(const ImageInfo& info) const; - - /** - * Open or close a group. - */ - void setOpen(qlonglong group, bool open); - bool isOpen(qlonglong group) const; - - /** - * Open all groups - */ - void setAllOpen(bool open); - bool isAllOpen() const; - - /// Returns if images will be filtered by these criteria at all - bool isFiltering() const; - - DatabaseFields::Set watchFlags() const; - -protected: - - bool m_allOpen; - QSet m_openGroups; -}; - -} // namespace Digikam - -Q_DECLARE_METATYPE(Digikam::ImageFilterSettings::GeolocationCondition) - -#endif // IMAGEFILTERSETTINGS_H diff --git a/libs/models/imagelistmodel.cpp b/libs/models/imagelistmodel.cpp deleted file mode 100644 index fafce34..0000000 --- a/libs/models/imagelistmodel.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2010-12-06 - * Description : An image model based on a static list - * - * Copyright (C) 2010-2011 by Marcel Wiesweg - * - * 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 "imagelistmodel.h" - -// Local includes - -#include "digikam_debug.h" -#include "coredbaccess.h" -#include "coredbchangesets.h" -#include "coredbwatch.h" -#include "imageinfo.h" -#include "imageinfolist.h" - -namespace Digikam -{ - -ImageListModel::ImageListModel(QObject* parent) - : ImageThumbnailModel(parent) -{ - connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)), - this, SLOT(slotCollectionImageChange(CollectionImageChangeset))); -} - -ImageListModel::~ImageListModel() -{ -} - -void ImageListModel::slotCollectionImageChange(const CollectionImageChangeset& changeset) -{ - if (isEmpty()) - { - return; - } - - switch (changeset.operation()) - { - case CollectionImageChangeset::Added: - break; - case CollectionImageChangeset::Removed: - case CollectionImageChangeset::RemovedAll: - removeImageInfos(ImageInfoList(changeset.ids())); - break; - - default: - break; - } -} - -} // namespace Digikam diff --git a/libs/models/imagelistmodel.h b/libs/models/imagelistmodel.h deleted file mode 100644 index a225b1b..0000000 --- a/libs/models/imagelistmodel.h +++ /dev/null @@ -1,63 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2010-12-06 - * Description : An image model based on a static list - * - * Copyright (C) 2010-2011 by Marcel Wiesweg - * - * 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 IMAGELISTMODEL_H -#define IMAGELISTMODEL_H - -// Local includes - -#include "imagethumbnailmodel.h" -#include "digikam_export.h" - -namespace Digikam -{ - -class ImageChangeset; -class CollectionImageChangeset; - -class DIGIKAM_DATABASE_EXPORT ImageListModel : public ImageThumbnailModel -{ - Q_OBJECT - -public: - - explicit ImageListModel(QObject* parent = 0); - ~ImageListModel(); - - // NOTE: necessary methods to add and remove ImageInfos to the model are inherited from ImageModel - -Q_SIGNALS: - - /** - * Emitted when images are removed from the model because they are removed in the database - */ - void imageInfosRemoved(const QList& infos); - -protected Q_SLOTS: - - void slotCollectionImageChange(const CollectionImageChangeset& changeset); -}; - -} // namespace Digikam - -#endif // IMAGELISTMODEL_H diff --git a/libs/models/imagemodel.cpp b/libs/models/imagemodel.cpp deleted file mode 100644 index 41b43cf..0000000 --- a/libs/models/imagemodel.cpp +++ /dev/null @@ -1,1368 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-05 - * Description : Qt item model for database entries - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * - * 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 "imagemodel.h" - -// Qt includes - -#include -#include - -// Local includes - -#include "digikam_debug.h" -#include "coredbchangesets.h" -#include "coredbfields.h" -#include "coredbwatch.h" -#include "imageinfo.h" -#include "imageinfolist.h" -#include "abstractitemdragdrophandler.h" - -namespace Digikam -{ - -class ImageModel::Private -{ -public: - - Private() - { - preprocessor = 0; - keepFilePathCache = false; - sendRemovalSignals = false; - incrementalUpdater = 0; - refreshing = false; - reAdding = false; - incrementalRefreshRequested = false; - } - - ImageInfoList infos; - QList extraValues; - QHash idHash; - - bool keepFilePathCache; - QHash filePathHash; - - bool sendRemovalSignals; - - QObject* preprocessor; - bool refreshing; - bool reAdding; - bool incrementalRefreshRequested; - - DatabaseFields::Set watchFlags; - - class ImageModelIncrementalUpdater* incrementalUpdater; - - ImageInfoList pendingInfos; - QList pendingExtraValues; - - 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 ImageModelIncrementalUpdater -{ -public: - - explicit ImageModelIncrementalUpdater(ImageModel::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; -}; - -ImageModel::ImageModel(QObject* parent) - : QAbstractListModel(parent), - d(new Private) -{ - connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)), - this, SLOT(slotImageChange(ImageChangeset))); - - connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)), - this, SLOT(slotImageTagChange(ImageTagChangeset))); -} - -ImageModel::~ImageModel() -{ - delete d->incrementalUpdater; - delete d; -} - -// ------------ Access methods ------------- - -void ImageModel::setKeepsFilePathCache(bool keepCache) -{ - d->keepFilePathCache = keepCache; -} - -bool ImageModel::keepsFilePathCache() const -{ - return d->keepFilePathCache; -} - -bool ImageModel::isEmpty() const -{ - return d->infos.isEmpty(); -} - -void ImageModel::setWatchFlags(const DatabaseFields::Set& set) -{ - d->watchFlags = set; -} - -ImageInfo ImageModel::imageInfo(const QModelIndex& index) const -{ - if (!d->isValid(index)) - { - return ImageInfo(); - } - - return d->infos.at(index.row()); -} - -ImageInfo& ImageModel::imageInfoRef(const QModelIndex& index) const -{ - return d->infos[index.row()]; -} - -qlonglong ImageModel::imageId(const QModelIndex& index) const -{ - if (!d->isValid(index)) - { - return 0; - } - - return d->infos.at(index.row()).id(); -} - -QList ImageModel::imageInfos(const QList& indexes) const -{ - QList infos; - - foreach(const QModelIndex& index, indexes) - { - infos << imageInfo(index); - } - - return infos; -} - -QList ImageModel::imageIds(const QList& indexes) const -{ - QList ids; - - foreach(const QModelIndex& index, indexes) - { - ids << imageId(index); - } - - return ids; -} - -ImageInfo ImageModel::imageInfo(int row) const -{ - if (row >= d->infos.size()) - { - return ImageInfo(); - } - - return d->infos.at(row); -} - -ImageInfo& ImageModel::imageInfoRef(int row) const -{ - return d->infos[row]; -} - -qlonglong ImageModel::imageId(int row) const -{ - if (row < 0 || row >= d->infos.size()) - { - return -1; - } - - return d->infos.at(row).id(); -} - -QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info) const -{ - return indexForImageId(info.id()); -} - -QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const -{ - return indexForImageId(info.id(), extraValue); -} - -QList ImageModel::indexesForImageInfo(const ImageInfo& info) const -{ - return indexesForImageId(info.id()); -} - -QModelIndex ImageModel::indexForImageId(qlonglong id) const -{ - int index = d->idHash.value(id, -1); - - if (index != -1) - { - return createIndex(index, 0); - } - - return QModelIndex(); -} - -QModelIndex ImageModel::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 ImageModel::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 ImageModel::numberOfIndexesForImageInfo(const ImageInfo& info) const -{ - return numberOfIndexesForImageId(info.id()); -} - -int ImageModel::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 -ImageInfo ImageModel::retrieveImageInfo(const QModelIndex& index) -{ - if (!index.isValid()) - { - return ImageInfo(); - } - - ImageModel* const model = index.data(ImageModelPointerRole).value(); - int row = index.data(ImageModelInternalId).toInt(); - - if (!model) - { - return ImageInfo(); - } - - return model->imageInfo(row); -} - -// static method -qlonglong ImageModel::retrieveImageId(const QModelIndex& index) -{ - if (!index.isValid()) - { - return 0; - } - - ImageModel* const model = index.data(ImageModelPointerRole).value(); - int row = index.data(ImageModelInternalId).toInt(); - - if (!model) - { - return 0; - } - - return model->imageId(row); -} - -QModelIndex ImageModel::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; iinfos.at(i).filePath() == filePath) - { - return createIndex(i, 0); - } - } - } - - return QModelIndex(); -} - -QList ImageModel::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; iinfos.at(i).filePath() == filePath) - { - indexes << createIndex(i, 0); - } - } - - return indexes; - } -} - -ImageInfo ImageModel::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 ImageInfo& info, d->infos) - { - if (info.filePath() == filePath) - { - return info; - } - } - } - - return ImageInfo(); -} - -QList ImageModel::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 ImageInfo& info, d->infos) - { - if (info.filePath() == filePath) - { - infos << info; - } - } - } - - return infos; -} - -void ImageModel::addImageInfo(const ImageInfo& info) -{ - addImageInfos(QList() << info, QList()); -} - -void ImageModel::addImageInfos(const QList& infos) -{ - addImageInfos(infos, QList()); -} - -void ImageModel::addImageInfos(const QList& infos, const QList& extraValues) -{ - if (infos.isEmpty()) - { - return; - } - - if (d->incrementalUpdater) - { - d->incrementalUpdater->appendInfos(infos, extraValues); - } - else - { - appendInfos(infos, extraValues); - } -} - -void ImageModel::addImageInfoSynchronously(const ImageInfo& info) -{ - addImageInfosSynchronously(QList() << info, QList()); -} - -void ImageModel::addImageInfosSynchronously(const QList& infos) -{ - addImageInfos(infos, QList()); -} - -void ImageModel::addImageInfosSynchronously(const QList& infos, const QList& extraValues) -{ - if (infos.isEmpty()) - { - return; - } - - publiciseInfos(infos, extraValues); - emit processAdded(infos, extraValues); -} - -void ImageModel::ensureHasImageInfo(const ImageInfo& info) -{ - ensureHasImageInfos(QList() << info, QList()); -} - -void ImageModel::ensureHasImageInfos(const QList& infos) -{ - ensureHasImageInfos(infos, QList()); -} - -void ImageModel::ensureHasImageInfos(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 ImageModel::clearImageInfos() -{ - d->infos.clear(); - d->extraValues.clear(); - d->idHash.clear(); - d->filePathHash.clear(); - delete d->incrementalUpdater; - d->incrementalUpdater = 0; - d->pendingInfos.clear(); - d->pendingExtraValues.clear(); - d->refreshing = false; - d->reAdding = false; - d->incrementalRefreshRequested = false; - - beginResetModel(); - endResetModel(); - - imageInfosCleared(); -} - -void ImageModel::setImageInfos(const QList& infos) -{ - clearImageInfos(); - addImageInfos(infos); -} - -QList ImageModel::imageInfos() const -{ - return d->infos; -} - -QList ImageModel::imageIds() const -{ - return d->idHash.keys(); -} - -bool ImageModel::hasImage(qlonglong id) const -{ - return d->idHash.contains(id); -} - -bool ImageModel::hasImage(const ImageInfo& info) const -{ - return d->idHash.contains(info.id()); -} - -bool ImageModel::hasImage(const ImageInfo& info, const QVariant& extraValue) const -{ - return hasImage(info.id(), extraValue); -} - -bool ImageModel::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 ImageModel::uniqueImageInfos() const -{ - if (d->extraValues.isEmpty()) - { - return d->infos; - } - - QList uniqueInfos; - const int size = d->infos.size(); - - for (int i=0; iinfos.at(i); - - if (d->idHash.value(info.id()) == i) - { - uniqueInfos << info; - } - } - - return uniqueInfos; -} - -void ImageModel::emitDataChangedForAll() -{ - if (d->infos.isEmpty()) - { - return; - } - - QModelIndex first = createIndex(0, 0); - QModelIndex last = createIndex(d->infos.size() - 1, 0); - emit dataChanged(first, last); -} - -void ImageModel::emitDataChangedForSelection(const QItemSelection& selection) -{ - if (!selection.isEmpty()) - { - foreach(const QItemSelectionRange& range, selection) - { - emit dataChanged(range.topLeft(), range.bottomRight()); - } - } -} - -void ImageModel::ensureHasGroupedImages(const ImageInfo& groupLeader) -{ - ensureHasImageInfos(groupLeader.groupedImages()); -} - -// ------------ Preprocessing ------------- - -void ImageModel::setPreprocessor(QObject* preprocessor) -{ - unsetPreprocessor(d->preprocessor); - d->preprocessor = preprocessor; -} - -void ImageModel::unsetPreprocessor(QObject* preprocessor) -{ - if (preprocessor && d->preprocessor == preprocessor) - { - disconnect(this, SIGNAL(preprocess(QList,QList)), 0, 0); - disconnect(d->preprocessor, 0, this, SLOT(reAddImageInfos(QList,QList))); - disconnect(d->preprocessor, 0, this, SLOT(reAddingFinished())); - } -} - -void ImageModel::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 ImageModel::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 ImageInfo& 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& infos, const QList& extraValues) -{ - // addImageInfos -> appendInfos -> preprocessor -> reAddImageInfos - publiciseInfos(infos, extraValues); -} - -void ImageModel::reAddingFinished() -{ - d->reAdding = false; - cleanSituationChecks(); -} - -void ImageModel::startRefresh() -{ - d->refreshing = true; -} - -void ImageModel::finishRefresh() -{ - d->refreshing = false; - cleanSituationChecks(); -} - -bool ImageModel::isRefreshing() const -{ - return d->refreshing; -} - -void ImageModel::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 ImageModel::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 ImageInfo& 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 ImageModel::requestIncrementalRefresh() -{ - if (d->reAdding) - { - d->incrementalRefreshRequested = true; - } - else - { - emit readyForIncrementalRefresh(); - } -} - -bool ImageModel::hasIncrementalRefreshPending() const -{ - return d->incrementalRefreshRequested; -} - -void ImageModel::startIncrementalRefresh() -{ - delete d->incrementalUpdater; - - d->incrementalUpdater = new ImageModelIncrementalUpdater(d); -} - -void ImageModel::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 = 0; -} - -void ImageModel::removeIndex(const QModelIndex& index) -{ - removeIndexes(QList() << index); -} - -void ImageModel::removeIndexes(const QList& indexes) -{ - QList listIndexes; - - foreach(const QModelIndex& index, indexes) - { - if (d->isValid(index)) - { - listIndexes << index.row(); - } - } - - if (listIndexes.isEmpty()) - { - return; - } - - removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes)); -} - -void ImageModel::removeImageInfo(const ImageInfo& info) -{ - removeImageInfos(QList() << info); -} - -void ImageModel::removeImageInfos(const QList& infos) -{ - QList listIndexes; - - foreach(const ImageInfo& info, infos) - { - QModelIndex index = indexForImageId(info.id()); - - if (index.isValid()) - { - listIndexes << index.row(); - } - } - removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes)); -} - -void ImageModel::removeImageInfos(const QList& infos, const QList& extraValues) -{ - if (extraValues.isEmpty()) - { - removeImageInfos(infos); - return; - } - - QList listIndexes; - - for (int i=0; isendRemovalSignals = 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 ImageModel::removeRowPairsWithCheck(const QList >& toRemove) -{ - if (d->incrementalUpdater) - { - d->incrementalUpdater->aboutToBeRemovedInModel(toRemove); - } - - removeRowPairs(toRemove); -} - -void ImageModel::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) - { - qCopy(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; - } - } - } -} - -ImageModelIncrementalUpdater::ImageModelIncrementalUpdater(ImageModel::Private* d) -{ - oldIds = d->idHash; - oldExtraValues = d->extraValues; -} - -void ImageModelIncrementalUpdater::appendInfos(const QList& infos, const QList& extraValues) -{ - if (extraValues.isEmpty()) - { - foreach(const ImageInfo& 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::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 ImageModelIncrementalUpdater::aboutToBeRemovedInModel(const IntPairList& toRemove) -{ - modelRemovals << toRemove; -} - -QList > ImageModelIncrementalUpdater::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, 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 > ImageModelIncrementalUpdater::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); - qSort(indices); - - QPair pair(indices.first(), indices.first()); - - for (int i=1; iisValid(index)) - { - return QVariant(); - } - - switch (role) - { - case Qt::DisplayRole: - case Qt::ToolTipRole: - return d->infos.at(index.row()).name(); - - case ImageModelPointerRole: - return QVariant::fromValue(const_cast(this)); - - case ImageModelInternalId: - 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 ImageModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - Q_UNUSED(section) - Q_UNUSED(orientation) - Q_UNUSED(role) - return QVariant(); -} - -int ImageModel::rowCount(const QModelIndex& parent) const -{ - if (parent.isValid()) - { - return 0; - } - - return d->infos.size(); -} - -Qt::ItemFlags ImageModel::flags(const QModelIndex& index) const -{ - if (!d->isValid(index)) - { - return 0; - } - - Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled; - - f |= dragDropFlags(index); - - return f; -} - -QModelIndex ImageModel::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 ImageModel::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 ImageModel::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/libs/models/imagemodel.h b/libs/models/imagemodel.h deleted file mode 100644 index dcf94c2..0000000 --- a/libs/models/imagemodel.h +++ /dev/null @@ -1,364 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-05 - * Description : Qt item model for database entries - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * - * 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 IMAGEMODEL_H -#define IMAGEMODEL_H - -// Qt includes - -#include - -// Local includes - -#include "dragdropimplementations.h" -#include "imageinfo.h" -#include "digikam_export.h" - -class QItemSelection; - -namespace Digikam -{ - -class ImageChangeset; -class ImageTagChangeset; - -namespace DatabaseFields -{ -class Set; -} - -class DIGIKAM_DATABASE_EXPORT ImageModel : public QAbstractListModel, public DragDropModelImplementation -{ - Q_OBJECT - -public: - - enum ImageModelRoles - { - /// An ImageModel* pointer to this model - ImageModelPointerRole = Qt::UserRole, - ImageModelInternalId = Qt::UserRole + 1, - /// Returns a thumbnail pixmap. May be implemented by subclasses. - /// Returns either a valid pixmap or a null QVariant. - ThumbnailRole = Qt::UserRole + 2, - /// Returns a QDateTime with the creation date - CreationDateRole = Qt::UserRole + 3, - /// Return (optional) extraData field - ExtraDataRole = Qt::UserRole + 5, - /// Returns the number of duplicate indexes for the same image id - ExtraDataDuplicateCount = Qt::UserRole + 6, - - // Roles which are defined here but not implemented by ImageModel - /// Returns position of item in Left Light Table preview. - LTLeftPanelRole = Qt::UserRole + 50, - /// Returns position of item in Right Light Table preview. - LTRightPanelRole = Qt::UserRole + 51, - - // For use by subclasses - SubclassRoles = Qt::UserRole + 100, - // For use by filter models - FilterModelRoles = Qt::UserRole + 500 - }; - -public: - - explicit ImageModel(QObject* parent = 0); - ~ImageModel(); - - /** If a cache is kept, lookup by file path is fast, - * without a cache it is O(n). Default is false. - */ - void setKeepsFilePathCache(bool keepCache); - bool keepsFilePathCache() const; - - /** Set a set of database fields to watch. - * If either of these is changed, dataChanged() will be emitted. - * Default is no flag (no signal will be emitted). - */ - void setWatchFlags(const DatabaseFields::Set& set); - - /** Returns the ImageInfo object, reference or image id from the underlying data - * pointed to by the index. - * If the index is not valid, imageInfo will return a null ImageInfo, imageId will - * return 0, imageInfoRef must not be called with an invalid index. - */ - ImageInfo imageInfo(const QModelIndex& index) const; - ImageInfo& imageInfoRef(const QModelIndex& index) const; - qlonglong imageId(const QModelIndex& index) const; - QList imageInfos(const QList& indexes) const; - QList imageIds(const QList& indexes) const; - - /** Returns the ImageInfo object, reference or image id from the underlying data - * of the given row (parent is the invalid QModelIndex, column is 0). - * Note that imageInfoRef will crash if index is invalid. - */ - ImageInfo imageInfo(int row) const; - ImageInfo& imageInfoRef(int row) const; - qlonglong imageId(int row) const; - - /** Return the index for the given ImageInfo or id, if contained in this model. - */ - QModelIndex indexForImageInfo(const ImageInfo& info) const; - QModelIndex indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const; - QModelIndex indexForImageId(qlonglong id) const; - QModelIndex indexForImageId(qlonglong id, const QVariant& extraValue) const; - QList indexesForImageInfo(const ImageInfo& info) const; - QList indexesForImageId(qlonglong id) const; - - int numberOfIndexesForImageInfo(const ImageInfo& info) const; - int numberOfIndexesForImageId(qlonglong id) const; - - /** Returns the index or ImageInfo object from the underlying data - * for the given file path. This is fast if keepsFilePathCache is enabled. - * The file path is as returned by ImageInfo.filePath(). - * In case of multiple occurrences of the same file, the simpler variants return - * any one found first, use the QList methods to retrieve all occurrences. - */ - QModelIndex indexForPath(const QString& filePath) const; - ImageInfo imageInfo(const QString& filePath) const; - QList indexesForPath(const QString& filePath) const; - QList imageInfos(const QString& filePath) const; - - /** Main entry point for subclasses adding image infos to the model. - * If you list entries not unique per image id, you must add an extraValue - * so that every entry is unique by imageId and extraValues. - * Please note that these methods do not prevent addition of duplicate entries. - */ - void addImageInfo(const ImageInfo& info); - void addImageInfos(const QList& infos); - void addImageInfos(const QList& infos, const QList& extraValues); - - /** Clears image infos and resets model. - */ - void clearImageInfos(); - - /** Clears and adds the infos. - */ - void setImageInfos(const QList& infos); - - /** - * Directly remove the given indexes or infos from the model. - */ - void removeIndex(const QModelIndex& indexes); - void removeIndexes(const QList& indexes); - void removeImageInfo(const ImageInfo& info); - void removeImageInfos(const QList& infos); - void removeImageInfos(const QList& infos, const QList& extraValues); - - /** - * addImageInfo() is asynchronous if a prepocessor is set. - * This method first adds the info, synchronously. - * Only afterwards, the preprocessor will have the opportunity to process it. - * This method also bypasses any incremental updates. - * Please note that these methods do not prevent addition of duplicate entries. - */ - void addImageInfoSynchronously(const ImageInfo& info); - void addImageInfosSynchronously(const QList& infos); - void addImageInfosSynchronously(const QList& infos, const QList& extraValues); - - /** - * Add the given entries. Method returns immediately, the - * addition may happen later asynchronously. - * These methods prevent the addition of duplicate entries. - */ - void ensureHasImageInfo(const ImageInfo& info); - void ensureHasImageInfos(const QList& infos); - void ensureHasImageInfos(const QList& infos, const QList& extraValues); - - /** - * Ensure that all images grouped on the given leader are contained in the model. - */ - void ensureHasGroupedImages(const ImageInfo& groupLeader); - - QList imageInfos() const; - QList imageIds() const; - QList uniqueImageInfos() const; - - bool hasImage(qlonglong id) const; - bool hasImage(const ImageInfo& info) const; - bool hasImage(const ImageInfo& info, const QVariant& extraValue) const; - bool hasImage(qlonglong id, const QVariant& extraValue) const; - - bool isEmpty() const; - - // Drag and Drop - DECLARE_MODEL_DRAG_DROP_METHODS - - /** - * Install an object as a preprocessor for ImageInfos added to this model. - * For every QList of ImageInfos added to addImageInfo, the signal preprocess() - * will be emitted. The preprocessor may process the items and shall then readd - * them by calling reAddImageInfos(). It may take some time to process. - * It shall discard any held infos when the modelReset() signal is sent. - * It shall call readdFinished() when no reset occurred and all infos on the way have been readded. - * This means that only after calling this method, you shall make three connections - * (preprocess -> your slot, your signal -> reAddImageInfos, your signal -> reAddingFinished) - * and make or already hold a connection modelReset() -> your slot. - * There is only one preprocessor at a time, a previously set object will be disconnected. - */ - void setPreprocessor(QObject* processor); - void unsetPreprocessor(QObject* processor); - - /** - * Returns true if this model is currently refreshing. - * For a preprocessor this means that, although the preprocessor may currently have - * processed all it got, more batches are to be expected. - */ - bool isRefreshing() const; - - /** - * Enable sending of imageInfosAboutToBeRemoved and imageInfosRemoved signals. - * Default: false - */ - void setSendRemovalSignals(bool send); - - virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; - virtual Qt::ItemFlags flags(const QModelIndex& index) const; - virtual QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const; - - /** Retrieves the imageInfo object from the data() method of the given index. - * The index may be from a QSortFilterProxyModel as long as an ImageModel is at the end. */ - static ImageInfo retrieveImageInfo(const QModelIndex& index); - static qlonglong retrieveImageId(const QModelIndex& index); - -Q_SIGNALS: - - /** Informs that ImageInfos will be added to the model. - * This signal is sent before the model data is changed and views are informed. - */ - void imageInfosAboutToBeAdded(const QList& infos); - - /** Informs that ImageInfos have been added to the model. - * This signal is sent after the model data is changed and views are informed. - */ - void imageInfosAdded(const QList& infos); - - /** Informs that ImageInfos will be removed from the model. - * This signal is sent before the model data is changed and views are informed. - * Note: You need to explicitly enable sending of this signal. It is not sent - * in clearImageInfos(). - */ - void imageInfosAboutToBeRemoved(const QList& infos); - - /** Informs that ImageInfos have been removed from the model. - * This signal is sent after the model data is changed and views are informed. * - * Note: You need to explicitly enable sending of this signal. It is not sent - * in clearImageInfos(). - */ - void imageInfosRemoved(const QList& infos); - - /** Connect to this signal only if you are the current preprocessor. - */ - void preprocess(const QList& infos, const QList&); - void processAdded(const QList& infos, const QList&); - - /** If an ImageChangeset affected indexes of this model with changes as set in watchFlags(), - * this signal contains the changeset and the affected indexes. - */ - void imageChange(const ImageChangeset&, const QItemSelection&); - - /** If an ImageTagChangeset affected indexes of this model, - * this signal contains the changeset and the affected indexes. - */ - void imageTagChange(const ImageTagChangeset&, const QItemSelection&); - - /** Signals that the model is right now ready to start an incremental refresh. - * This is guaranteed only for the scope of emitting this signal. - */ - void readyForIncrementalRefresh(); - - /** Signals that the model has finished currently with all scheduled - * refreshing, full or incremental, and all preprocessing. - * The model is in polished, clean situation right now. - */ - void allRefreshingFinished(); - -public Q_SLOTS: - - void reAddImageInfos(const QList& infos, const QList& extraValues); - void reAddingFinished(); - -protected: - - /** Subclasses that add ImageInfos in batches shall call startRefresh() - * when they start sending batches and finishRefresh() when they have finished. - * No incremental refreshes will be started while listing. - * A clearImageInfos() always stops listing, calling finishRefresh() is then not necessary. - */ - void startRefresh(); - void finishRefresh(); - - /** As soon as the model is ready to start an incremental refresh, the signal - * readyForIncrementalRefresh() will be emitted. The signal will be emitted inline - * if the model is ready right now. - */ - void requestIncrementalRefresh(); - bool hasIncrementalRefreshPending() const; - - /** Starts an incremental refresh operation. You shall only call this method from a slot - * connected to readyForIncrementalRefresh(). To initiate an incremental refresh, - * call requestIncrementalRefresh(). - */ - void startIncrementalRefresh(); - void finishIncrementalRefresh(); - - void emitDataChangedForAll(); - void emitDataChangedForSelection(const QItemSelection& selection); - - // Called when the internal storage is cleared - virtual void imageInfosCleared() {}; - - // Called before rowsAboutToBeRemoved - virtual void imageInfosAboutToBeRemoved(int /*begin*/, int /*end*/) {}; - -protected Q_SLOTS: - - virtual void slotImageChange(const ImageChangeset& changeset); - virtual void slotImageTagChange(const ImageTagChangeset& changeset); - -private: - - void appendInfos(const QList& infos, const QList& extraValues); - void appendInfosChecked(const QList& infos, const QList& extraValues); - void publiciseInfos(const QList& infos, const QList& extraValues); - void cleanSituationChecks(); - void removeRowPairsWithCheck(const QList >& toRemove); - void removeRowPairs(const QList >& toRemove); - -public: - - // Declared public because it's used in ImageModelIncrementalUpdater class - class Private; - -private: - - Private* const d; -}; - -} // namespace Digikam - -Q_DECLARE_METATYPE(Digikam::ImageModel*) - -#endif // IMAGEMODEL_H diff --git a/libs/models/imagesortsettings.cpp b/libs/models/imagesortsettings.cpp deleted file mode 100644 index 39ee6e1..0000000 --- a/libs/models/imagesortsettings.cpp +++ /dev/null @@ -1,400 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-05 - * Description : Filter values for use with ImageFilterModel - * - * Copyright (C) 2009 by Marcel Wiesweg - * Copyright (C) 2014 by Mohamed Anwer - * - * 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 "imagesortsettings.h" - -// Qt includes - -#include -#include - -// Local includes - -#include "coredbfields.h" -#include "imageinfo.h" - -namespace Digikam -{ - -ImageSortSettings::ImageSortSettings() -{ - categorizationMode = NoCategories; - categorizationSortOrder = DefaultOrder; - categorizationCaseSensitivity = Qt::CaseSensitive; - sortRole = SortByFileName; - sortOrder = DefaultOrder; - strTypeNatural = true; - sortCaseSensitivity = Qt::CaseSensitive; - currentCategorizationSortOrder = Qt::AscendingOrder; - currentSortOrder = Qt::AscendingOrder; -} - -bool ImageSortSettings::operator==(const ImageSortSettings& other) const -{ - return - categorizationMode == other.categorizationMode && - categorizationSortOrder == other.categorizationSortOrder && - categorizationCaseSensitivity == other.categorizationCaseSensitivity && - sortRole == other.sortRole && - sortOrder == other.sortOrder && - sortCaseSensitivity == other.sortCaseSensitivity; -} - -void ImageSortSettings::setCategorizationMode(CategorizationMode mode) -{ - categorizationMode = mode; - - if (categorizationSortOrder == DefaultOrder) - { - currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); - } -} - -void ImageSortSettings::setCategorizationSortOrder(SortOrder order) -{ - categorizationSortOrder = order; - - if (categorizationSortOrder == DefaultOrder) - { - currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); - } - else - { - currentCategorizationSortOrder = (Qt::SortOrder)categorizationSortOrder; - } -} - -void ImageSortSettings::setSortRole(SortRole role) -{ - sortRole = role; - - if (sortOrder == DefaultOrder) - { - currentSortOrder = defaultSortOrderForSortRole(sortRole); - } -} - -void ImageSortSettings::setSortOrder(SortOrder order) -{ - sortOrder = order; - - if (sortOrder == DefaultOrder) - { - currentSortOrder = defaultSortOrderForSortRole(sortRole); - } - else - { - currentSortOrder = (Qt::SortOrder)order; - } -} - -void ImageSortSettings::setStringTypeNatural(bool natural) -{ - strTypeNatural = natural; -} - -Qt::SortOrder ImageSortSettings::defaultSortOrderForCategorizationMode(CategorizationMode mode) -{ - switch (mode) - { - case NoCategories: - case OneCategory: - case CategoryByAlbum: - case CategoryByFormat: - default: - return Qt::AscendingOrder; - } -} - -Qt::SortOrder ImageSortSettings::defaultSortOrderForSortRole(SortRole role) -{ - switch (role) - { - case SortByFileName: - case SortByFilePath: - return Qt::AscendingOrder; - case SortByFileSize: - return Qt::DescendingOrder; - case SortByModificationDate: - case SortByCreationDate: - return Qt::AscendingOrder; - case SortByRating: - case SortByImageSize: - return Qt::DescendingOrder; - case SortByAspectRatio: - return Qt::DescendingOrder; - case SortBySimilarity: - return Qt::DescendingOrder; - default: - return Qt::AscendingOrder; - } -} - -int ImageSortSettings::compareCategories(const ImageInfo& left, const ImageInfo& right) const -{ - switch (categorizationMode) - { - case NoCategories: - case OneCategory: - return 0; - case CategoryByAlbum: - { - int leftAlbum = left.albumId(); - int rightAlbum = right.albumId(); - - // return comparation result - if (leftAlbum == rightAlbum) - { - return 0; - } - else if (lessThanByOrder(leftAlbum, rightAlbum, currentCategorizationSortOrder)) - { - return -1; - } - else - { - return 1; - } - } - case CategoryByFormat: - { - return naturalCompare(left.format(), right.format(), - currentCategorizationSortOrder, categorizationCaseSensitivity, strTypeNatural); - } - default: - return 0; - } -} - -bool ImageSortSettings::lessThan(const ImageInfo& left, const ImageInfo& right) const -{ - int result = compare(left, right, sortRole); - - if (result != 0) - { - return result < 0; - } - - // are they identical? - if (left == right) - { - return false; - } - - // If left and right equal for first sort order, use a hierarchy of all sort orders - if ( (result = compare(left, right, SortByFileName)) != 0) - { - return result < 0; - } - - if ( (result = compare(left, right, SortByCreationDate)) != 0) - { - return result < 0; - } - - if ( (result = compare(left, right, SortByModificationDate)) != 0) - { - return result < 0; - } - - if ( (result = compare(left, right, SortByFilePath)) != 0) - { - return result < 0; - } - - if ( (result = compare(left, right, SortByFileSize)) != 0) - { - return result < 0; - } - - if ( (result = compare(left, right, SortBySimilarity)) != 0) - { - return result < 0; - } - - return false; -} - -int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right) const -{ - return compare(left, right, sortRole); -} - -int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right, SortRole role) const -{ - switch (role) - { - case SortByFileName: - { - bool versioning = (left.name().contains(QLatin1String("_v"), Qt::CaseInsensitive) || - right.name().contains(QLatin1String("_v"), Qt::CaseInsensitive)); - return naturalCompare(left.name(), right.name(), currentSortOrder, sortCaseSensitivity, strTypeNatural, versioning); - } - case SortByFilePath: - return naturalCompare(left.filePath(), right.filePath(), currentSortOrder, sortCaseSensitivity, strTypeNatural); - case SortByFileSize: - return compareByOrder(left.fileSize(), right.fileSize(), currentSortOrder); - case SortByModificationDate: - return compareByOrder(left.modDateTime(), right.modDateTime(), currentSortOrder); - case SortByCreationDate: - return compareByOrder(left.dateTime(), right.dateTime(), currentSortOrder); - case SortByRating: - // I have the feeling that inverting the sort order for rating is the natural order - return - compareByOrder(left.rating(), right.rating(), currentSortOrder); - case SortByImageSize: - { - QSize leftSize = left.dimensions(); - QSize rightSize = right.dimensions(); - int leftPixels = leftSize.width() * leftSize.height(); - int rightPixels = rightSize.width() * rightSize.height(); - return compareByOrder(leftPixels, rightPixels, currentSortOrder); - } - case SortByAspectRatio: - { - QSize leftSize = left.dimensions(); - QSize rightSize = right.dimensions(); - int leftAR = (double(leftSize.width()) / double(leftSize.height())) * 1000000; - int rightAR = (double(rightSize.width()) / double(rightSize.height())) * 1000000; - return compareByOrder(leftAR, rightAR, currentSortOrder); - } - case SortBySimilarity: - { - qlonglong leftReferenceImageId = left.currentReferenceImage(); - qlonglong rightReferenceImageId = right.currentReferenceImage(); - // make sure that the original image has always the highest similarity. - double leftSimilarity = left.id() == leftReferenceImageId ? 1.1 : left.currentSimilarity(); - double rightSimilarity = right.id() == rightReferenceImageId ? 1.1 : right.currentSimilarity(); - return compareByOrder(leftSimilarity, rightSimilarity, currentSortOrder); - } - default: - return 1; - } -} - -bool ImageSortSettings::lessThan(const QVariant& left, const QVariant& right) const -{ - if (left.type() != right.type()) - { - return false; - } - - switch (left.type()) - { - case QVariant::Int: - return compareByOrder(left.toInt(), right.toInt(), currentSortOrder); - case QVariant::UInt: - return compareByOrder(left.toUInt(), right.toUInt(), currentSortOrder); - case QVariant::LongLong: - return compareByOrder(left.toLongLong(), right.toLongLong(), currentSortOrder); - case QVariant::ULongLong: - return compareByOrder(left.toULongLong(), right.toULongLong(), currentSortOrder); - case QVariant::Double: - return compareByOrder(left.toDouble(), right.toDouble(), currentSortOrder); - case QVariant::Date: - return compareByOrder(left.toDate(), right.toDate(), currentSortOrder); - case QVariant::DateTime: - return compareByOrder(left.toDateTime(), right.toDateTime(), currentSortOrder); - case QVariant::Time: - return compareByOrder(left.toTime(), right.toTime(), currentSortOrder); - case QVariant::Rect: - case QVariant::RectF: - { - QRectF rectLeft = left.toRectF(); - QRectF rectRight = right.toRectF(); - int result; - - if ((result = compareByOrder(rectLeft.top(), rectRight.top(), currentSortOrder)) != 0) - { - return result < 0; - } - - if ((result = compareByOrder(rectLeft.left(), rectRight.left(), currentSortOrder)) != 0) - { - return result < 0; - } - - QSizeF sizeLeft = rectLeft.size(), sizeRight = rectRight.size(); - - if ((result = compareByOrder(sizeLeft.width()*sizeLeft.height(), sizeRight.width()*sizeRight.height(), currentSortOrder)) != 0) - { - return result < 0; - } - // FIXME: fall through?? If not, add "break" here - } - default: - return naturalCompare(left.toString(), right.toString(), currentSortOrder, sortCaseSensitivity, strTypeNatural); - } -} - -DatabaseFields::Set ImageSortSettings::watchFlags() const -{ - DatabaseFields::Set set; - - switch (sortRole) - { - case SortByFileName: - set |= DatabaseFields::Name; - break; - case SortByFilePath: - set |= DatabaseFields::Name; - break; - case SortByFileSize: - set |= DatabaseFields::FileSize; - break; - case SortByModificationDate: - set |= DatabaseFields::ModificationDate; - break; - case SortByCreationDate: - set |= DatabaseFields::CreationDate; - break; - case SortByRating: - set |= DatabaseFields::Rating; - break; - case SortByImageSize: - set |= DatabaseFields::Width | DatabaseFields::Height; - break; - case SortByAspectRatio: - set |= DatabaseFields::Width | DatabaseFields::Height; - break; - case SortBySimilarity: - // TODO: Not sure what to do here.... - set |= DatabaseFields::Name; - break; - } - - switch (categorizationMode) - { - case NoCategories: - case OneCategory: - case CategoryByAlbum: - break; - case CategoryByFormat: - set |= DatabaseFields::Format; - break; - } - - return set; -} - -} // namespace Digikam diff --git a/libs/models/imagesortsettings.h b/libs/models/imagesortsettings.h deleted file mode 100644 index 2a5fd8c..0000000 --- a/libs/models/imagesortsettings.h +++ /dev/null @@ -1,225 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-05-31 - * Description : Sort settings for use with ImageFilterModel - * - * Copyright (C) 2009 by Marcel Wiesweg - * - * 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 IMAGESORTSETTINGS_H -#define IMAGESORTSETTINGS_H - -// Qt includes - -#include -#include -#include -#include -#include - -// Local includes - -#include "digikam_export.h" - -namespace Digikam -{ - -class ImageInfo; - -namespace DatabaseFields -{ - class Set; -} - -class DIGIKAM_DATABASE_EXPORT ImageSortSettings -{ -public: - - ImageSortSettings(); - - bool operator==(const ImageSortSettings& other) const; - - /** Compares the categories of left and right. - * Return -1 if left is less than right, 0 if both fall in the same category, - * and 1 if left is greater than right. - * Adheres to set categorization mode and current category sort order. - */ - int compareCategories(const ImageInfo& left, const ImageInfo& right) const; - - /** Returns true if left is less than right. - * Adheres to current sort role and sort order. - */ - bool lessThan(const ImageInfo& left, const ImageInfo& right) const; - - /** Compares the ImageInfos left and right. - * Return -1 if left is less than right, 1 if left is greater than right, - * and 0 if left equals right comparing the current sort role's value. - * Adheres to set sort role and sort order. - */ - int compare(const ImageInfo& left, const ImageInfo& right) const; - - /** Returns true if left QVariant is less than right. - * Adheres to current sort role and sort order. - * Use for extraValue, if necessary. - */ - bool lessThan(const QVariant& left, const QVariant& right) const; - - enum SortOrder - { - AscendingOrder = Qt::AscendingOrder, - DescendingOrder = Qt::DescendingOrder, - DefaultOrder /// sort order depends on the chosen sort role - }; - - /// --- Categories --- - - enum CategorizationMode - { - NoCategories, /// categorization switched off - OneCategory, /// all items in one global category - CategoryByAlbum, - CategoryByFormat - }; - - CategorizationMode categorizationMode; - SortOrder categorizationSortOrder; - - void setCategorizationMode(CategorizationMode mode); - void setCategorizationSortOrder(SortOrder order); - - /// Only Ascending or Descending, never DefaultOrder - Qt::SortOrder currentCategorizationSortOrder; - Qt::CaseSensitivity categorizationCaseSensitivity; - - bool isCategorized() const { return categorizationMode >= CategoryByAlbum; } - - /// --- Image Sorting --- - - enum SortRole - { - // Note: For legacy reasons, the order of the first five entries must remain unchanged - SortByFileName, - SortByFilePath, - SortByCreationDate, - SortByFileSize, - SortByRating, - SortByModificationDate, - SortByImageSize, // pixel number - SortByAspectRatio, // width / height * 100000 - SortBySimilarity - }; - - SortRole sortRole; - SortOrder sortOrder; - bool strTypeNatural; - - void setSortRole(SortRole role); - void setSortOrder(SortOrder order); - void setStringTypeNatural(bool natural); - - Qt::SortOrder currentSortOrder; - Qt::CaseSensitivity sortCaseSensitivity; - - int compare(const ImageInfo& left, const ImageInfo& right, SortRole sortRole) const; - - // --- --- - - static Qt::SortOrder defaultSortOrderForCategorizationMode(CategorizationMode mode); - static Qt::SortOrder defaultSortOrderForSortRole(SortRole role); - - /// --- Change notification --- - - /** Returns database fields a change in which would affect the current sorting. - */ - DatabaseFields::Set watchFlags() const; - - /// --- Utilities --- - - /** Returns a < b if sortOrder is Ascending, or b < a if order is descending. - */ - template - static inline bool lessThanByOrder(const T& a, const T& b, Qt::SortOrder sortOrder) - { - if (sortOrder == Qt::AscendingOrder) - { - return a < b; - } - else - { - return b < a; - } - } - - /** Returns the usual compare result of -1, 0, or 1 for lessThan, equals and greaterThan. - */ - template - static inline int compareValue(const T& a, const T& b) - { - if (a == b) - { - return 0; - } - - if (a < b) - { - return -1; - } - else - { - return 1; - } - } - - /** Takes a typical result from a compare method (0 is equal, -1 is less than, 1 is greater than) - * and applies the given sort order to it. - */ - static inline int compareByOrder(int compareResult, Qt::SortOrder sortOrder) - { - if (sortOrder == Qt::AscendingOrder) - { - return compareResult; - } - else - { - return - compareResult; - } - } - - template - static inline int compareByOrder(const T& a, const T& b, Qt::SortOrder sortOrder) - { - return compareByOrder(compareValue(a, b), sortOrder); - } - - /** Compares the two string by natural comparison and adheres to given sort order - */ - static inline int naturalCompare(const QString& a, const QString& b, Qt::SortOrder sortOrder, - Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive, - bool natural = true, bool versioning = false) - { - QCollator collator; - collator.setNumericMode(natural); - collator.setIgnorePunctuation(versioning); - collator.setCaseSensitivity(caseSensitive); - return (compareByOrder(collator.compare(a, b), sortOrder)); - } -}; - -} // namespace Digikam - -#endif // IMAGESORTSETTINGS_H diff --git a/libs/models/imagethumbnailmodel.cpp b/libs/models/imagethumbnailmodel.cpp deleted file mode 100644 index b7f5661..0000000 --- a/libs/models/imagethumbnailmodel.cpp +++ /dev/null @@ -1,323 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-05 - * Description : Qt item model for database entries with support for thumbnail loading - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * Copyright (C) 2011-2017 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 "imagethumbnailmodel.h" - -// Qt includes - -#include - -// Local includes - -#include "digikam_debug.h" -#include "thumbnailloadthread.h" -#include "digikam_export.h" -#include "digikam_globals.h" - -namespace Digikam -{ - -class ImageThumbnailModel::ImageThumbnailModelPriv -{ -public: - - ImageThumbnailModelPriv() : - thread(0), - preloadThread(0), - thumbSize(0), - lastGlobalThumbSize(0), - preloadThumbSize(0), - emitDataChanged(true) - { - staticListContainingThumbnailRole << ImageModel::ThumbnailRole; - } - - ThumbnailLoadThread* thread; - ThumbnailLoadThread* preloadThread; - ThumbnailSize thumbSize; - ThumbnailSize lastGlobalThumbSize; - ThumbnailSize preloadThumbSize; - QRect detailRect; - QVector staticListContainingThumbnailRole; - - bool emitDataChanged; - - int preloadThumbnailSize() const - { - if (preloadThumbSize.size()) - { - return preloadThumbSize.size(); - } - - return thumbSize.size(); - } -}; - -ImageThumbnailModel::ImageThumbnailModel(QObject* parent) - : ImageModel(parent), d(new ImageThumbnailModelPriv) -{ - setKeepsFilePathCache(true); -} - -ImageThumbnailModel::~ImageThumbnailModel() -{ - delete d->preloadThread; - delete d; -} - -void ImageThumbnailModel::setThumbnailLoadThread(ThumbnailLoadThread* thread) -{ - d->thread = thread; - - connect(d->thread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), - this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap))); -} - -ThumbnailLoadThread* ImageThumbnailModel::thumbnailLoadThread() const -{ - return d->thread; -} - -ThumbnailSize ImageThumbnailModel::thumbnailSize() const -{ - return d->thumbSize; -} - -void ImageThumbnailModel::setThumbnailSize(const ThumbnailSize& size) -{ - d->lastGlobalThumbSize = size; - d->thumbSize = size; -} - -void ImageThumbnailModel::setPreloadThumbnailSize(const ThumbnailSize& size) -{ - d->preloadThumbSize = size; -} - -void ImageThumbnailModel::setEmitDataChanged(bool emitSignal) -{ - d->emitDataChanged = emitSignal; -} - -void ImageThumbnailModel::setPreloadThumbnails(bool preload) -{ - if (preload) - { - if (!d->preloadThread) - { - d->preloadThread = new ThumbnailLoadThread; - d->preloadThread->setPixmapRequested(false); - d->preloadThread->setPriority(QThread::LowestPriority); - } - - connect(this, SIGNAL(allRefreshingFinished()), - this, SLOT(preloadAllThumbnails())); - } - else - { - delete d->preloadThread; - d->preloadThread = 0; - disconnect(this, SIGNAL(allRefreshingFinished()), - this, SLOT(preloadAllThumbnails())); - } -} - -void ImageThumbnailModel::prepareThumbnails(const QList& indexesToPrepare) -{ - prepareThumbnails(indexesToPrepare, d->thumbSize); -} - -void ImageThumbnailModel::prepareThumbnails(const QList& indexesToPrepare, const ThumbnailSize& thumbSize) -{ - if (!d->thread) - { - return; - } - - QList ids; - foreach(const QModelIndex& index, indexesToPrepare) - { - ids << imageInfoRef(index).thumbnailIdentifier(); - } - d->thread->findGroup(ids, thumbSize.size()); -} - -void ImageThumbnailModel::preloadThumbnails(const QList& infos) -{ - if (!d->preloadThread) - { - return; - } - - QList ids; - foreach(const ImageInfo& info, infos) - { - ids << info.thumbnailIdentifier(); - } - d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize()); -} - -void ImageThumbnailModel::preloadThumbnails(const QList& indexesToPreload) -{ - if (!d->preloadThread) - { - return; - } - - QList ids; - foreach(const QModelIndex& index, indexesToPreload) - { - ids << imageInfoRef(index).thumbnailIdentifier(); - } - d->preloadThread->stopAllTasks(); - d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize()); -} - -void ImageThumbnailModel::preloadAllThumbnails() -{ - preloadThumbnails(imageInfos()); -} - -void ImageThumbnailModel::imageInfosCleared() -{ - if (d->preloadThread) - { - d->preloadThread->stopAllTasks(); - } -} - -QVariant ImageThumbnailModel::data(const QModelIndex& index, int role) const -{ - if (role == ThumbnailRole && d->thread && index.isValid()) - { - QPixmap thumbnail; - ImageInfo info = imageInfo(index); - QString path = info.filePath(); - - if (info.isNull()) - { - return QVariant(QVariant::Pixmap); - } - - if (!d->detailRect.isNull()) - { - if (d->thread->find(info.thumbnailIdentifier(), d->detailRect, thumbnail, d->thumbSize.size())) - { - return thumbnail; - } - } - else - { - if (d->thread->find(info.thumbnailIdentifier(), thumbnail, d->thumbSize.size())) - { - return thumbnail; - } - } - - return QVariant(QVariant::Pixmap); - } - - return ImageModel::data(index, role); -} - -bool ImageThumbnailModel::setData(const QModelIndex& index, const QVariant& value, int role) -{ - if (role == ThumbnailRole) - { - switch (value.type()) - { - case QVariant::Invalid: - d->thumbSize = d->lastGlobalThumbSize; - d->detailRect = QRect(); - break; - - case QVariant::Int: - - if (value.isNull()) - { - d->thumbSize = d->lastGlobalThumbSize; - } - else - { - d->thumbSize = value.toInt(); - } - break; - - case QVariant::Rect: - - if (value.isNull()) - { - d->detailRect = QRect(); - } - else - { - d->detailRect = value.toRect(); - } - break; - - default: - break; - } - } - - return ImageModel::setData(index, value, role); -} - -void ImageThumbnailModel::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb) -{ - if (thumb.isNull()) - { - return; - } - - // In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all. - QModelIndexList indexes; - ThumbnailIdentifier thumbId = loadingDescription.thumbnailIdentifier(); - if (thumbId.filePath.isEmpty()) - { - indexes = indexesForImageId(thumbId.id); - } - else - { - indexes = indexesForPath(thumbId.filePath); - } - foreach(const QModelIndex& index, indexes) - { - if (thumb.isNull()) - { - emit thumbnailFailed(index, loadingDescription.previewParameters.size); - } - else - { - emit thumbnailAvailable(index, loadingDescription.previewParameters.size); - - if (d->emitDataChanged) - { - emit dataChanged(index, index, d->staticListContainingThumbnailRole); - } - } - } -} - -} // namespace Digikam diff --git a/libs/models/imagethumbnailmodel.h b/libs/models/imagethumbnailmodel.h deleted file mode 100644 index 366ca65..0000000 --- a/libs/models/imagethumbnailmodel.h +++ /dev/null @@ -1,140 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2009-03-05 - * Description : Qt item model for database entries with support for thumbnail loading - * - * Copyright (C) 2009-2011 by Marcel Wiesweg - * Copyright (C) 2011 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. - * - * ============================================================ */ - -#ifndef IMAGETHUMBNAILMODEL_H -#define IMAGETHUMBNAILMODEL_H - -// Local includes - -#include "imagemodel.h" -#include "thumbnailsize.h" -#include "digikam_export.h" - -namespace Digikam -{ - -class LoadingDescription; -class ThumbnailLoadThread; - -class DIGIKAM_DATABASE_EXPORT ImageThumbnailModel : public ImageModel -{ - Q_OBJECT - -public: - - /** - * An ImageModel that supports thumbnail loading. - * You need to set a ThumbnailLoadThread to enable thumbnail loading. - * Adjust the thumbnail size to your needs. - * Note that setKeepsFilePathCache is enabled per default. - */ - explicit ImageThumbnailModel(QObject* parent); - ~ImageThumbnailModel(); - - /** Enable thumbnail loading and set the thread that shall be used. - * The thumbnail size of this thread will be adjusted. - */ - void setThumbnailLoadThread(ThumbnailLoadThread* thread); - ThumbnailLoadThread* thumbnailLoadThread() const; - - /// Set the thumbnail size to use - void setThumbnailSize(const ThumbnailSize& thumbSize); - - /// If you want to fix a size for preloading, do it here. - void setPreloadThumbnailSize(const ThumbnailSize& thumbSize); - - void setExifRotate(bool rotate); - - /** - * Enable emitting dataChanged() when a thumbnail becomes available. - * The thumbnailAvailable() signal will be emitted in any case. - * Default is true. - */ - void setEmitDataChanged(bool emitSignal); - - /** - * Enable preloading of thumbnails: - * If preloading is enabled, for every entry in the model a thumbnail generation is started. - * Default: false. - */ - void setPreloadThumbnails(bool preload); - - ThumbnailSize thumbnailSize() const; - - /** - * Handles the ThumbnailRole. - * If the pixmap is available, returns it in the QVariant. - * If it still needs to be loaded, returns a null QVariant and emits - * thumbnailAvailable() as soon as it is available. - */ - virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; - - /** - * You can override the current thumbnail size by giving an integer value for ThumbnailRole. - * Set a null QVariant to use the thumbnail size set by setThumbnailSize() again. - * The index given here is ignored for this purpose. - */ - virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::DisplayRole); - -public Q_SLOTS: - - /** Prepare the thumbnail loading for the given indexes - */ - void prepareThumbnails(const QList& indexesToPrepare); - void prepareThumbnails(const QList& indexesToPrepare, const ThumbnailSize& thumbSize); - - /** - * Preload thumbnail for the given infos resp. indexes. - * Note: Use setPreloadThumbnails to automatically preload all entries in the model. - * Note: This only ensures thumbnail generation. It is not guaranteed that pixmaps - * are stored in the cache. For thumbnails that are expect to be drawn immediately, - * include them in prepareThumbnails(). - * Note: Stops preloading of previously added thumbnails. - */ - void preloadThumbnails(const QList&); - void preloadThumbnails(const QList&); - void preloadAllThumbnails(); - -Q_SIGNALS: - - void thumbnailAvailable(const QModelIndex& index, int requestedSize); - void thumbnailFailed(const QModelIndex& index, int requestedSize); - -protected: - - virtual void imageInfosCleared(); - -protected Q_SLOTS: - - void slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb); - -private: - - class ImageThumbnailModelPriv; - ImageThumbnailModelPriv* const d; -}; - -} // namespace Digikam - -#endif /* IMAGETHUMBNAILMODEL_H */ diff --git a/libs/models/imageversionsmodel.cpp b/libs/models/imageversionsmodel.cpp deleted file mode 100644 index e6ba582..0000000 --- a/libs/models/imageversionsmodel.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2010-07-13 - * Description : Model for image versions - * - * Copyright (C) 2010 by Martin Klapetek - * - * 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 "imageversionsmodel.h" - -// KDE includes - -#include - -// Local includes - -#include "digikam_debug.h" -#include "workingwidget.h" - -namespace Digikam -{ - -class ImageVersionsModel::Private -{ -public: - - Private() - { - data = 0; - paintTree = false; - } - - ///Complete paths with filenames and tree level - QList >* data; - ///This is for delegate to paint it as selected - QString currentSelectedImage; - ///If true, the delegate will paint items as a tree - ///if false, it will be painted as a list - bool paintTree; -}; - -ImageVersionsModel::ImageVersionsModel(QObject* parent) - : QAbstractListModel(parent), - d(new Private) -{ - d->data = new QList >; -} - -ImageVersionsModel::~ImageVersionsModel() -{ - //qDeleteAll(d->data); - delete d; -} - -Qt::ItemFlags ImageVersionsModel::flags(const QModelIndex& index) const -{ - if (!index.isValid()) - { - return 0; - } - - return Qt::ItemIsEnabled | Qt::ItemIsSelectable; -} - -QVariant ImageVersionsModel::data(const QModelIndex& index, int role) const -{ - if (!index.isValid()) - { - return QVariant(); - } - - if (role == Qt::DisplayRole && !d->data->isEmpty()) - { - return d->data->at(index.row()).first; - } - else if (role == Qt::UserRole && !d->data->isEmpty()) - { - return d->data->at(index.row()).second; - } - else if (role == Qt::DisplayRole && d->data->isEmpty()) - { - //TODO: make this text Italic - return QVariant(QString(i18n("No image selected"))); - } - - return QVariant(); -} - -int ImageVersionsModel::rowCount(const QModelIndex& parent) const -{ - Q_UNUSED(parent) - return d->data->count(); -} - -void ImageVersionsModel::setupModelData(QList >& data) -{ - beginResetModel(); - - d->data->clear(); - - if (!data.isEmpty()) - { - d->data->append(data); - } - else - { - d->data->append(qMakePair(QString(i18n("This is the original image")), 0)); - } - - endResetModel(); -} - -void ImageVersionsModel::clearModelData() -{ - beginResetModel(); - - if (!d->data->isEmpty()) - { - d->data->clear(); - } - - endResetModel(); -} - -void ImageVersionsModel::slotAnimationStep() -{ - emit dataChanged(createIndex(0, 0), createIndex(rowCount()-1, 1)); -} - -QString ImageVersionsModel::currentSelectedImage() const -{ - return d->currentSelectedImage; -} - -void ImageVersionsModel::setCurrentSelectedImage(const QString& path) -{ - d->currentSelectedImage = path; -} - -QModelIndex ImageVersionsModel::currentSelectedImageIndex() const -{ - return index(listIndexOf(d->currentSelectedImage), 0); -} - -bool ImageVersionsModel::paintTree() const -{ - return d->paintTree; -} - -void ImageVersionsModel::setPaintTree(bool paint) -{ - d->paintTree = paint; -} - -int ImageVersionsModel::listIndexOf(const QString& item) const -{ - for (int i = 0; i < d->data->size(); ++i) - { - if (d->data->at(i).first == item) - { - return i; - } - } - - return -1; -} - -} // namespace Digikam diff --git a/libs/models/imageversionsmodel.h b/libs/models/imageversionsmodel.h deleted file mode 100644 index ed08529..0000000 --- a/libs/models/imageversionsmodel.h +++ /dev/null @@ -1,75 +0,0 @@ -/* ============================================================ - * - * This file is a part of digiKam project - * http://www.digikam.org - * - * Date : 2010-07-13 - * Description : Model for image versions - * - * Copyright (C) 2010 by Martin Klapetek - * - * 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 IMAGEVERSIONSMODEL_H -#define IMAGEVERSIONSMODEL_H - -// Qt includes - -#include -#include - -// Local includes - -#include "digikam_export.h" - -namespace Digikam -{ - -class DIGIKAM_DATABASE_EXPORT ImageVersionsModel : public QAbstractListModel -{ - Q_OBJECT - -public: - - explicit ImageVersionsModel(QObject* parent = 0); - ~ImageVersionsModel(); - - Qt::ItemFlags flags(const QModelIndex& index) const; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; - int rowCount(const QModelIndex& parent = QModelIndex()) const; - - void setupModelData(QList >& data); - void clearModelData(); - - QString currentSelectedImage() const; - void setCurrentSelectedImage(const QString& path); - QModelIndex currentSelectedImageIndex() const; - - bool paintTree() const; - int listIndexOf(const QString& item) const; - -public Q_SLOTS: - - void slotAnimationStep(); - void setPaintTree(bool paint); - -private: - - class Private; - Private* const d; -}; - -} // namespace Digikam - -#endif // IMAGEVERSIONSMODEL_H -- cgit v0.11.2