File indexing completed on 2024-05-12 15:59:53

0001 /*
0002  * SPDX-FileCopyrightText: 2018 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 #include "KisTagFilterResourceProxyModel.h"
0007 
0008 #include <QDebug>
0009 
0010 #include <KisResourceModelProvider.h>
0011 #include <KisResourceModel.h>
0012 #include <KisTagResourceModel.h>
0013 #include <KisTagModel.h>
0014 
0015 #include <kis_debug.h>
0016 #include <KisResourceSearchBoxFilter.h>
0017 
0018 struct KisTagFilterResourceProxyModel::Private
0019 {
0020     Private()
0021         : filter(new KisResourceSearchBoxFilter())
0022     {
0023     }
0024 
0025     QString resourceType;
0026 
0027     KisResourceModel *resourceModel {0}; // This is the source model if we are _not_ filtering by tag
0028     KisTagResourceModel *tagResourceModel {0}; // This is the source model if we are filtering by tag
0029 
0030     QScopedPointer<KisResourceSearchBoxFilter> filter;
0031     bool filteringWithinCurrentTag {false};
0032 
0033     QMap<QString, QVariant> metaDataMapFilter;
0034     KisTagSP currentTagFilter;
0035     KoResourceSP currentResourceFilter;
0036 
0037     int storageId {-1};
0038     bool useStorageIdFilter {false};
0039 
0040 };
0041 
0042 KisTagFilterResourceProxyModel::KisTagFilterResourceProxyModel(const QString &resourceType, QObject *parent)
0043     : QSortFilterProxyModel(parent)
0044     , d(new Private)
0045 {
0046     d->resourceType = resourceType;
0047     d->resourceModel = new KisResourceModel(resourceType);
0048     d->tagResourceModel = new KisTagResourceModel(resourceType);
0049 
0050     setSourceModel(d->resourceModel);
0051 }
0052 
0053 KisTagFilterResourceProxyModel::~KisTagFilterResourceProxyModel()
0054 {
0055     delete d->resourceModel;
0056     delete d->tagResourceModel;
0057     delete d;
0058 }
0059 
0060 void KisTagFilterResourceProxyModel::setResourceFilter(ResourceFilter filter)
0061 {
0062     emit beforeFilterChanges();
0063     d->resourceModel->setResourceFilter(filter);
0064     d->tagResourceModel->setResourceFilter(filter);
0065     invalidateFilter();
0066     emit afterFilterChanged();
0067 }
0068 
0069 void KisTagFilterResourceProxyModel::setStorageFilter(StorageFilter filter)
0070 {
0071     emit beforeFilterChanges();
0072     d->resourceModel->setStorageFilter(filter);
0073     d->tagResourceModel->setStorageFilter(filter);
0074     invalidateFilter();
0075     emit afterFilterChanged();
0076 }
0077 
0078 void KisTagFilterResourceProxyModel::setResourceModel(KisResourceModel *resourceModel)
0079 {
0080     d->resourceModel = resourceModel;
0081 }
0082 
0083 KoResourceSP KisTagFilterResourceProxyModel::resourceForIndex(QModelIndex index) const
0084 {
0085     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0086     if (source) {
0087         return source->resourceForIndex(mapToSource(index));
0088     }
0089     return 0;
0090 }
0091 
0092 QModelIndex KisTagFilterResourceProxyModel::indexForResource(KoResourceSP resource) const
0093 {
0094     if (!resource || !resource->valid() || resource->resourceId() < 0) return QModelIndex();
0095 
0096     for (int i = 0; i < rowCount(); ++i)  {
0097         QModelIndex idx = index(i, 0);
0098         Q_ASSERT(idx.isValid());
0099         if (idx.data() == resource->resourceId()) {
0100             return idx;
0101         }
0102     }
0103     return QModelIndex();
0104 }
0105 
0106 QModelIndex KisTagFilterResourceProxyModel::indexForResourceId(int resourceId) const
0107 {
0108     if (resourceId < 0) return QModelIndex();
0109     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0110     if (source) {
0111         return mapFromSource(source->indexForResourceId(resourceId));
0112     }
0113     return QModelIndex();
0114 }
0115 
0116 bool KisTagFilterResourceProxyModel::setResourceActive(const QModelIndex &index, bool value)
0117 {
0118     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0119     if (source) {
0120         return source->setResourceActive(mapToSource(index), value);
0121     }
0122     return false;
0123 }
0124 
0125 KoResourceSP KisTagFilterResourceProxyModel::importResourceFile(const QString &filename, const bool allowOverwrite, const QString &storageId)
0126 {
0127     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0128     KoResourceSP res;
0129     if (source) {
0130         res = source->importResourceFile(filename, allowOverwrite, storageId);
0131     }
0132     return res;
0133 }
0134 
0135 KoResourceSP KisTagFilterResourceProxyModel::importResource(const QString &filename, QIODevice *device, const bool allowOverwrite, const QString &storageId)
0136 {
0137     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0138     KoResourceSP res;
0139     if (source) {
0140         res = source->importResource(filename, device, allowOverwrite, storageId);
0141     }
0142     return res;
0143 }
0144 
0145 bool KisTagFilterResourceProxyModel::importWillOverwriteResource(const QString &fileName, const QString &storageLocation) const
0146 {
0147     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0148     return source && source->importWillOverwriteResource(fileName, storageLocation);
0149 }
0150 
0151 bool KisTagFilterResourceProxyModel::exportResource(KoResourceSP resource, QIODevice *device)
0152 {
0153     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0154     bool res = false;
0155     if (source) {
0156         res = source->exportResource(resource, device);
0157     }
0158     return res;
0159 }
0160 
0161 bool KisTagFilterResourceProxyModel::addResource(KoResourceSP resource, const QString &storageId)
0162 {
0163     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0164     if (source) {
0165         return source->addResource(resource, storageId);
0166     }
0167     return false;
0168 }
0169 
0170 bool KisTagFilterResourceProxyModel::updateResource(KoResourceSP resource)
0171 {
0172     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0173     if (source) {
0174         return source->updateResource(resource);
0175     }
0176     return false;
0177 }
0178 
0179 bool KisTagFilterResourceProxyModel::reloadResource(KoResourceSP resource)
0180 {
0181     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0182     if (source) {
0183         return source->reloadResource(resource);
0184     }
0185     return false;
0186 }
0187 
0188 bool KisTagFilterResourceProxyModel::renameResource(KoResourceSP resource, const QString &name)
0189 {
0190     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0191     if (source) {
0192         return source->renameResource(resource, name);
0193     }
0194     return false;
0195 }
0196 
0197 bool KisTagFilterResourceProxyModel::setResourceMetaData(KoResourceSP resource, QMap<QString, QVariant> metadata)
0198 {
0199     KisAbstractResourceModel *source = dynamic_cast<KisAbstractResourceModel*>(sourceModel());
0200     if (source) {
0201         return source->setResourceMetaData(resource, metadata);
0202     }
0203     return false;
0204 }
0205 
0206 void KisTagFilterResourceProxyModel::setMetaDataFilter(QMap<QString, QVariant> metaDataMap)
0207 {
0208     emit beforeFilterChanges();
0209     d->metaDataMapFilter = metaDataMap;
0210     invalidateFilter();
0211     emit afterFilterChanged();
0212 }
0213 
0214 void KisTagFilterResourceProxyModel::setTagFilter(const KisTagSP tag)
0215 {
0216     d->currentTagFilter = tag;
0217     updateTagFilter();
0218 }
0219 
0220 void KisTagFilterResourceProxyModel::setStorageFilter(bool useFilter, int storageId)
0221 {
0222     emit beforeFilterChanges();
0223     d->useStorageIdFilter = useFilter;
0224     if (useFilter) {
0225         d->storageId = storageId;
0226     }
0227     invalidateFilter();
0228     emit afterFilterChanged();
0229 }
0230 
0231 void KisTagFilterResourceProxyModel::updateTagFilter()
0232 {
0233     emit beforeFilterChanges();
0234     const bool ignoreTagFiltering =
0235         !d->filteringWithinCurrentTag && !d->filter->isEmpty();
0236 
0237     QAbstractItemModel *desiredModel = 0;
0238 
0239     if (d->currentResourceFilter) {
0240         QVector<KisTagSP> filter;
0241 
0242         if (d->currentTagFilter &&
0243             !ignoreTagFiltering &&
0244             d->currentTagFilter->url() != KisAllTagsModel::urlAll() &&
0245             d->currentTagFilter->url() != KisAllTagsModel::urlAllUntagged()) {
0246 
0247             filter << d->currentTagFilter;
0248         } else {
0249             // combination with for untagged resources in not implemented
0250             // in KisTagResourceModel
0251             KIS_SAFE_ASSERT_RECOVER_NOOP(!d->currentTagFilter ||
0252                                          d->currentTagFilter->url() != KisAllTagsModel::urlAllUntagged());
0253         }
0254 
0255         d->tagResourceModel->setTagsFilter(filter);
0256         d->tagResourceModel->setResourcesFilter({d->currentResourceFilter});
0257         desiredModel = d->tagResourceModel;
0258 
0259     } else {
0260         d->tagResourceModel->setResourcesFilter(QVector<KoResourceSP>());
0261         if (ignoreTagFiltering ||
0262                 !d->currentTagFilter ||
0263                 d->currentTagFilter->url() == KisAllTagsModel::urlAll()) {
0264 
0265             d->tagResourceModel->setTagsFilter(QVector<KisTagSP>());
0266             desiredModel = d->resourceModel;
0267             d->resourceModel->showOnlyUntaggedResources(false);
0268         }
0269         else {
0270             if (d->currentTagFilter->url() == KisAllTagsModel::urlAllUntagged()) {
0271                 desiredModel = d->resourceModel;
0272                 d->resourceModel->showOnlyUntaggedResources(true);
0273             }
0274             else {
0275                 desiredModel = d->tagResourceModel;
0276                 d->tagResourceModel->setTagsFilter(QVector<KisTagSP>() << d->currentTagFilter);
0277             }
0278         }
0279     }
0280 
0281     // TODO: when model changes the current selection in the
0282     //       view disappears. We should try to keep it somehow.
0283     if (sourceModel() != desiredModel) {
0284         setSourceModel(desiredModel);
0285     }
0286 
0287     invalidateFilter();
0288     emit afterFilterChanged();
0289 }
0290 
0291 void KisTagFilterResourceProxyModel::setResourceFilter(const KoResourceSP resource)
0292 {
0293     d->currentResourceFilter = resource;
0294     updateTagFilter();
0295 }
0296 
0297 void KisTagFilterResourceProxyModel::setSearchText(const QString& searchText)
0298 {
0299     d->filter->setFilter(searchText);
0300     updateTagFilter();
0301 }
0302 
0303 void KisTagFilterResourceProxyModel::setFilterInCurrentTag(bool filterInCurrentTag)
0304 {
0305     d->filteringWithinCurrentTag = filterInCurrentTag;
0306     updateTagFilter();
0307 }
0308 
0309 bool KisTagFilterResourceProxyModel::tagResources(const KisTagSP tag, const QVector<int> &resourceIds)
0310 {
0311     return d->tagResourceModel->tagResources(tag, resourceIds);
0312 }
0313 
0314 bool KisTagFilterResourceProxyModel::untagResources(const KisTagSP tag, const QVector<int> &resourceIds)
0315 {
0316     return d->tagResourceModel->untagResources(tag, resourceIds);
0317 }
0318 
0319 int KisTagFilterResourceProxyModel::isResourceTagged(const KisTagSP tag, const int resourceId)
0320 {
0321     return d->tagResourceModel->isResourceTagged(tag, resourceId);
0322 }
0323 
0324 bool KisTagFilterResourceProxyModel::filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const
0325 {
0326     return true;
0327 }
0328 
0329 bool KisTagFilterResourceProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
0330 {
0331     // if both filters are empty, just accept everything
0332     if (d->filter->isEmpty() && d->metaDataMapFilter.isEmpty() && !d->useStorageIdFilter) {
0333         return true;
0334     }
0335 
0336     // If there's a tag set to filter on, we use the tagResourceModel, so that already filters for the tag
0337     // Here, we only have to filter by the search string.
0338     QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
0339 
0340     if (!idx.isValid()) {
0341         return false;
0342     }
0343 
0344     // checking the storage filter
0345     if (d->useStorageIdFilter) {
0346         int storageId = sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::StorageId).toInt();
0347         if (storageId != d->storageId) {
0348             return false;
0349         }
0350     }
0351 
0352     bool metaDataMatches = true;
0353     QMap<QString, QVariant> resourceMetaData = sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::MetaData).toMap();
0354     Q_FOREACH(const QString &key, d->metaDataMapFilter.keys()) {
0355         if (resourceMetaData.contains(key)) {
0356             metaDataMatches = (resourceMetaData[key] == d->metaDataMapFilter[key]);
0357             if (!metaDataMatches) {
0358                 return false;
0359             }
0360         }
0361     }
0362 
0363     QString resourceName = sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::Name).toString();
0364     if (sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::ResourceType).toString() == ResourceType::PaintOpPresets) {
0365         resourceName = resourceName.replace("_", " ");
0366     }
0367     QStringList resourceTags = sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::Tags).toStringList();
0368     bool resourceNameMatches = d->filter->matchesResource(resourceName, resourceTags);
0369 
0370 
0371     return (resourceNameMatches && metaDataMatches);
0372 }
0373 
0374 bool KisTagFilterResourceProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
0375 {
0376     QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisAbstractResourceModel::Name).toString();
0377     QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisAbstractResourceModel::Name).toString();
0378     return nameLeft.toLower() < nameRight.toLower();
0379 }
0380