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

0001 /*
0002  * SPDX-FileCopyrightText: 2020 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 #include "KisTagResourceModel.h"
0007 
0008 #include <QtSql>
0009 #include <QMap>
0010 
0011 #include <KisResourceLocator.h>
0012 #include <KisResourceModel.h>
0013 #include <KisResourceModelProvider.h>
0014 #include <KisResourceQueryMapper.h>
0015 #include <KisStorageModel.h>
0016 #include <kis_assert.h>
0017 
0018 struct KisAllTagResourceModel::Private {
0019     QString resourceType;
0020     QSqlQuery query;
0021     int columnCount { TagName + 1 };
0022     int cachedRowCount {-1};
0023 };
0024 
0025 
0026 KisAllTagResourceModel::KisAllTagResourceModel(const QString &resourceType, QObject *parent)
0027     : QAbstractTableModel(parent)
0028     , d(new Private())
0029 {
0030     d->resourceType = resourceType;
0031     resetQuery();
0032 
0033     connect(KisResourceLocator::instance(), SIGNAL(storageAdded(const QString&)), this, SLOT(addStorage(const QString&)));
0034     connect(KisResourceLocator::instance(), SIGNAL(storageRemoved(const QString&)), this, SLOT(removeStorage(const QString&)));
0035     connect(KisStorageModel::instance(), SIGNAL(storageEnabled(const QString&)), this, SLOT(addStorage(const QString&)));
0036     connect(KisStorageModel::instance(), SIGNAL(storageDisabled(const QString&)), this, SLOT(removeStorage(const QString&)));
0037     connect(KisResourceLocator::instance(), SIGNAL(resourceActiveStateChanged(const QString&, int)), this, SLOT(slotResourceActiveStateChanged(const QString&, int)));
0038 
0039     /**
0040      * TODO: connect to beginExternalResourceImport() and beginExternalResourceRemove
0041      *       as well. It seems to work without them somehow, but I guess it is just a
0042      *       coincidence or UB
0043      */
0044 }
0045 
0046 KisAllTagResourceModel::~KisAllTagResourceModel()
0047 {
0048     delete d;
0049 }
0050 
0051 int KisAllTagResourceModel::rowCount(const QModelIndex &parent) const
0052 {
0053     if (parent.isValid()) {
0054         return 0;
0055     }
0056 
0057     if (d->cachedRowCount < 0) {
0058         QSqlQuery q;
0059         q.prepare("SELECT COUNT(DISTINCT resource_tags.tag_id || resources.name || resources.filename || resources.md5sum)\n"
0060                   "FROM   resource_tags\n"
0061                   ",      resources\n"
0062                   ",      resource_types\n"
0063                   "WHERE  resource_tags.resource_id = resources.id\n"
0064                   "AND    resources.resource_type_id = resource_types.id\n"
0065                   "AND    resource_types.name = :resource_type\n"
0066                   "AND    resource_tags.active = 1\n");
0067 
0068         q.bindValue(":resource_type", d->resourceType);
0069 
0070         if (!q.exec()) {
0071             qWarning() << "Could not execute resource/tags rowcount query" << q.lastError();
0072         }
0073 
0074         q.first();
0075 
0076         const_cast<KisAllTagResourceModel*>(this)->d->cachedRowCount = q.value(0).toInt();
0077     }
0078     return d->cachedRowCount;
0079 }
0080 
0081 int KisAllTagResourceModel::columnCount(const QModelIndex &parent) const
0082 {
0083     if (parent.isValid()) {
0084         return 0;
0085     }
0086 
0087     return d->columnCount;
0088 }
0089 
0090 QVariant KisAllTagResourceModel::data(const QModelIndex &index, int role) const
0091 {
0092     QVariant v;
0093 
0094     if (!index.isValid()) { return v; }
0095     if (index.row() > rowCount()) { return v; }
0096     if (index.column() > d->columnCount) { return v;}
0097 
0098     bool pos = const_cast<KisAllTagResourceModel*>(this)->d->query.seek(index.row());
0099     if (!pos) {return v;}
0100 
0101     if (role < Qt::UserRole + TagId && index.column() < TagId) {
0102         v = KisResourceQueryMapper::variantFromResourceQuery(d->query, index.column(), role, true);
0103     }
0104 
0105     if (index.column() >= TagId) {
0106         // trick to get the correct value without writing everything again
0107         // this is used for example in case of sorting
0108         role = Qt::UserRole + index.column();
0109     }
0110 
0111     // These are not shown, but needed for the filter
0112     switch(role) {
0113     case Qt::UserRole + TagId:
0114     {
0115         v = d->query.value("tag_id");
0116         break;
0117     }
0118     case Qt::UserRole + ResourceId:
0119     {
0120         v = d->query.value("resource_id");
0121         break;
0122     }
0123     case Qt::UserRole + Tag:
0124     {
0125         KisTagSP tag = KisResourceLocator::instance()->tagForUrl(d->query.value("tag_url").toString(), d->resourceType);
0126         v = QVariant::fromValue(tag);
0127         break;
0128     }
0129     case Qt::UserRole + Resource:
0130     {
0131         v = QVariant::fromValue(KisResourceLocator::instance()->resourceForId(d->query.value("resource_id").toInt()));
0132         break;
0133     }
0134     case Qt::UserRole + ResourceActive:
0135     {
0136         v = d->query.value("resource_active");
0137         break;
0138     }
0139     case Qt::UserRole + TagActive:
0140     {
0141         v = d->query.value("tag_active");
0142         break;
0143     }
0144     case Qt::UserRole + ResourceStorageActive:
0145     {
0146         v = d->query.value("resource_storage_active");
0147         break;
0148     }
0149     case Qt::UserRole + ResourceName:
0150     {
0151         v = d->query.value("resource_name");
0152         break;
0153     }
0154     case Qt::UserRole + TagName:
0155     {
0156         v = d->query.value("translated_name");
0157         if (v.isNull()) {
0158             v = d->query.value("tag_name");
0159         }
0160         break;
0161     }
0162 
0163     default:
0164         ;
0165     }
0166     return v;
0167 }
0168 
0169 bool KisAllTagResourceModel::tagResources(const KisTagSP tag, const QVector<int>& resourceIds)
0170 {
0171     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(tag && tag->valid() && tag->id() >= 0, false);
0172 
0173     // notes for performance:
0174     // the only two costly parts are:
0175     // - executing the query constructed by createQuery()
0176     // - running endInsertRows() (because it updates all the views and filter proxies etc...)
0177 
0178     QVector<int> resourceIdsToAdd;
0179     QVector<int> resourceIdsToUpdate;
0180 
0181     // looks expensive but actually isn't
0182     for (int i = 0; i < resourceIds.count(); i++) {
0183         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(resourceIds[i] >= 0, false);
0184         int taggedState = isResourceTagged(tag, resourceIds[i]);
0185         switch (taggedState) {
0186         case -1:
0187             // never tagged
0188             resourceIdsToAdd.append(resourceIds[i]);
0189             break;
0190         case 0:
0191             // tagged but then untagged
0192             resourceIdsToUpdate.append(resourceIds[i]);
0193             break;
0194         case 1:
0195             // it means the resource is already tagged; do nothing
0196             break;
0197         }
0198     }
0199 
0200     if (resourceIdsToAdd.isEmpty() && resourceIdsToUpdate.isEmpty()) {
0201         // Is already tagged, let's do nothing
0202         return true;
0203     }
0204 
0205     int howManyTimesBeginInsertedCalled = 0;
0206 
0207     if (resourceIdsToUpdate.count() > 0) {
0208 
0209         QSqlQuery allIndices;
0210         if (!allIndices.prepare(createQuery(false, true))) {
0211             qWarning() << "Could not prepare tagResource-allIndices query" << allIndices.lastError();
0212         }
0213 
0214         allIndices.bindValue(":resource_type", d->resourceType);
0215         allIndices.bindValue(":language", KisTag::currentLocale());
0216 
0217         if (!allIndices.exec()) {
0218             qWarning() << "Could not execute tagResource-allIndices query" << allIndices.lastError();
0219         }
0220 
0221         int activesRowId = -1;
0222         int lastActiveRowId = -1;
0223 
0224         // needed for beginInsertRows calculations
0225         QMap<int, int> resourcesCountForLastActiveRowId;
0226 
0227         int idd = 0;
0228 
0229         while (allIndices.next()) {
0230             idd++;
0231             bool isActive = allIndices.value("resource_tags_pair_active").toBool();
0232             if (isActive) {
0233                 activesRowId++;
0234                 lastActiveRowId = activesRowId;
0235             } else {
0236                 bool variantSuccess = true;
0237                 int rowTagId = allIndices.value("tag_id").toInt(&variantSuccess);
0238                 KIS_SAFE_ASSERT_RECOVER(variantSuccess) { rowTagId = -1; }
0239                 int rowResourceId = allIndices.value("resource_id").toInt(&variantSuccess);
0240                 KIS_SAFE_ASSERT_RECOVER(variantSuccess) { rowResourceId = -1; }
0241                 if (rowTagId == tag->id() && resourceIdsToUpdate.contains(rowResourceId)) {
0242                     if (!resourcesCountForLastActiveRowId.contains(lastActiveRowId)) {
0243                         resourcesCountForLastActiveRowId[lastActiveRowId] = 1;
0244                     } else {
0245                         resourcesCountForLastActiveRowId[lastActiveRowId] = resourcesCountForLastActiveRowId[lastActiveRowId] + 1;
0246                     }
0247                 }
0248             }
0249          }
0250 
0251         Q_FOREACH(const int key, resourcesCountForLastActiveRowId.keys()) {
0252             // when having multiple beginInsertRows:
0253             // let's say you have this model:
0254             // 0  A
0255             // 1  A
0256             // 2  A
0257             //  <- put 2 Bs here
0258             // 3  A
0259             //  <- put one B here
0260             // 4  A
0261             // and you want to add the two Bs and then add another B
0262             // then the signals should be:
0263             // beginRemoveRows(3, 4) <- new indices of the two Bs
0264             // beginRemoveRows(4, 4) <- new index of the one B ignoring the action before
0265 
0266 
0267             beginInsertRows(QModelIndex(), key + 1, key + resourcesCountForLastActiveRowId[key]);
0268             howManyTimesBeginInsertedCalled++;
0269         }
0270 
0271         // Resource was tagged, then untagged. Tag again;
0272         QSqlQuery q;
0273 
0274         if (!q.prepare("UPDATE resource_tags\n"
0275                        "SET    active = 1\n"
0276                        "WHERE  resource_id = :resource_id\n"
0277                        "AND    tag_id      = :tag_id")) {
0278 
0279 
0280             qWarning() << "Could not prepare update resource_tags to active statement" << q.lastError();
0281 
0282             return false;
0283         }
0284 
0285         QVariantList resourceIdsVariants;
0286         QVariantList tagIdVariants;
0287 
0288         for (int i = 0; i < resourceIdsToUpdate.count(); i++) {
0289             resourceIdsVariants << QVariant(resourceIdsToUpdate[i]);
0290             tagIdVariants << QVariant(tag->id());
0291         }
0292 
0293         q.bindValue(":resource_id", resourceIdsVariants);
0294         q.bindValue(":tag_id", tagIdVariants);
0295 
0296         if (!q.execBatch()) {
0297             qWarning() << "Could not execute update resource_tags to active statement" << q.lastError();
0298             for (int i = 0; i < howManyTimesBeginInsertedCalled; i++) {
0299                 endInsertRows();
0300             }
0301             return false;
0302         }
0303 
0304     }
0305 
0306     if (resourceIdsToAdd.count() > 0) {
0307 
0308         beginInsertRows(QModelIndex(), rowCount(), rowCount() + resourceIdsToAdd.count() - 1);
0309         howManyTimesBeginInsertedCalled++;
0310 
0311         // Resource was never tagged before, insert it. The active column is DEFAULT 1
0312         QSqlQuery q;
0313 
0314 
0315         QString values;
0316         for (int i = 0; i < resourceIdsToAdd.count(); i++) {
0317             if (i > 0) {
0318                 values.append(", ");
0319             }
0320             values.append("(?, ?, ?)");
0321         }
0322 
0323         if (!q.prepare(QString("INSERT INTO resource_tags\n"
0324                        "(resource_id, tag_id, active)\n"
0325                        "VALUES ") + values + QString(";\n"))) {
0326             qWarning() << "Could not prepare insert into resource tags statement" << q.lastError();
0327             for (int i = 0; i < howManyTimesBeginInsertedCalled; i++) {
0328                 endInsertRows();
0329             }
0330             return false;
0331         }
0332 
0333         for (int i = 0; i < resourceIdsToAdd.count(); i++) {
0334             q.addBindValue(resourceIdsToAdd[i]);
0335             q.addBindValue(tag->id());
0336             q.addBindValue(true);
0337 
0338         }
0339 
0340         if (!q.exec()) {
0341             qWarning() << "Could not execute insert into resource tags statement" << q.boundValues() << q.lastError();
0342             for (int i = 0; i < howManyTimesBeginInsertedCalled; i++) {
0343                 endInsertRows();
0344             }
0345             return false;
0346         }
0347     }
0348 
0349     resetQuery();
0350 
0351     for (int i = 0; i < howManyTimesBeginInsertedCalled; i++) {
0352         endInsertRows();
0353     }
0354 
0355     return true;
0356 }
0357 
0358 bool KisAllTagResourceModel::untagResources(const KisTagSP tag, const QVector<int> &resourceIds)
0359 {
0360     if (!tag || !tag->valid()) return false;
0361     if (!d->query.isSelect()) return false;
0362     if (rowCount() < 1) return false;
0363 
0364     int beginRemoveRowsCount = 0;
0365 
0366     QSqlQuery q;
0367 
0368     if (!q.prepare("UPDATE resource_tags\n"
0369                    "SET    active      = 0\n"
0370                    "WHERE  tag_id      = :tag_id\n"
0371                    "AND    resource_id = :resource_id")) {
0372         qWarning() << "Could not prepare untagResource-update query" << q.lastError();
0373         return false;
0374     }
0375 
0376     QSqlQuery allIndices;
0377     if (!allIndices.prepare(createQuery(true, true))) {
0378         qWarning() << "Coult not prepare untagResource-allIndices query " << allIndices.lastError();
0379     }
0380 
0381     allIndices.bindValue(":resource_type", d->resourceType);
0382     allIndices.bindValue(":language", KisTag::currentLocale());
0383 
0384     if (!allIndices.exec()) {
0385         qCritical() << "Could not exec untagResource-allIndices query " << allIndices.lastError();
0386     }
0387 
0388     int activesRowId = -1;
0389     int lastActiveRowId = -1;
0390 
0391     // needed for beginInsertRows indices calculations
0392     QMap<int, int> resourcesCountForLastActiveRowId;
0393 
0394     int idd = 0;
0395 
0396     while (allIndices.next()) {
0397         idd++;
0398         bool variantSuccess = true;
0399 
0400         bool isActive = true; // all of them are active!
0401         KIS_SAFE_ASSERT_RECOVER(variantSuccess) { isActive = false; }
0402 
0403         int rowTagId = allIndices.value("tag_id").toInt(&variantSuccess);
0404         KIS_SAFE_ASSERT_RECOVER(variantSuccess) { rowTagId = -1; }
0405         int rowResourceId = allIndices.value("resource_id").toInt(&variantSuccess);
0406         KIS_SAFE_ASSERT_RECOVER(variantSuccess) { rowResourceId = -1; }
0407 
0408         bool willStayActive = isActive && (rowTagId != tag->id() || !resourceIds.contains(rowResourceId));
0409         activesRowId++;
0410         if (willStayActive) {
0411             lastActiveRowId = activesRowId;
0412         } else if (isActive) {
0413             // means we're removing it
0414             if (!resourcesCountForLastActiveRowId.contains(lastActiveRowId)) {
0415                 resourcesCountForLastActiveRowId[lastActiveRowId] = 0;
0416             }
0417             resourcesCountForLastActiveRowId[lastActiveRowId]++;
0418         }
0419     }
0420 
0421     Q_FOREACH(const int key, resourcesCountForLastActiveRowId.keys()) {
0422         // when having multiple beginRemoveRows:
0423         // let's say you have this model:
0424         // 0  A
0425         // 1  A
0426         // 2  A
0427         // 3  *B*
0428         // 4  *B*
0429         // 5  A
0430         // 6  *B*
0431         // 7  A
0432         // and you want to remove all Bs
0433         // then the signals should be:
0434         // beginRemoveRows(3, 4) <- first two indices in the obvious way
0435         // beginRemoveRows(4, 4) <- next index as if the first action was already done
0436         //  (so `5  A` already became `3  A`, so the *B* is `4  B`, not `6  B`)
0437 
0438         beginRemoveRows(QModelIndex(), key + 1, key + resourcesCountForLastActiveRowId[key]);
0439         beginRemoveRowsCount++;
0440     }
0441 
0442     QSqlDatabase::database().transaction();
0443     for (int i = 0; i < resourceIds.count(); i++) {
0444         int resourceId = resourceIds[i];
0445 
0446         if (resourceId < 0) continue;
0447         if (isResourceTagged(tag, resourceId) < 1) continue;
0448 
0449         q.bindValue(":tag_id", tag->id());
0450         q.bindValue(":resource_id", resourceId);
0451 
0452         if (!q.exec()) {
0453             qWarning() << "Could not execute untagResource-update query" << q.lastError() << q.boundValues();
0454             for (int i = 0; i < beginRemoveRowsCount; i++) {
0455                 endRemoveRows();
0456             }
0457             QSqlDatabase::database().rollback();
0458             return false;
0459         }
0460     }
0461     QSqlDatabase::database().commit();
0462 
0463     if (beginRemoveRowsCount > 0) {
0464         resetQuery();
0465         for (int i = 0; i < beginRemoveRowsCount; i++) {
0466             endRemoveRows();
0467         }
0468     }
0469 
0470     return true;
0471 }
0472 
0473 int KisAllTagResourceModel::isResourceTagged(const KisTagSP tag, const int resourceId)
0474 {
0475     QSqlQuery query;
0476     bool r = query.prepare("SELECT resource_tags.active\n"
0477                            "FROM   resource_tags\n"
0478                            "WHERE  resource_tags.resource_id = :resource_id\n"
0479                            "AND    resource_tags.tag_id = :tag_id\n");
0480 
0481     if (!r) {
0482         qWarning() << "Could not prepare bool KisAllTagResourceModel::checkResourceTaggedState query" << query.lastError();
0483         return false;
0484     }
0485 
0486     query.bindValue(":resource_id", resourceId);
0487     query.bindValue(":tag_id", tag->id());
0488 
0489     if (!query.exec()) {
0490         qWarning() << "Could not execute is resource tagged with a specific tag query" << query.boundValues() << query.lastError();
0491         return false;
0492     }
0493 
0494     r = query.first();
0495     if (!r) {
0496         // Resource was not tagged
0497         return -1;
0498     }
0499 
0500     return query.value(0).toInt() > 0;
0501 }
0502 
0503 void KisAllTagResourceModel::addStorage(const QString &location)
0504 {
0505     Q_UNUSED(location);
0506     beginInsertRows(QModelIndex(), rowCount(), rowCount());
0507     resetQuery();
0508     endInsertRows();
0509 }
0510 
0511 void KisAllTagResourceModel::removeStorage(const QString &location)
0512 {
0513     Q_UNUSED(location);
0514     beginRemoveRows(QModelIndex(), rowCount(), rowCount());
0515     resetQuery();
0516     endRemoveRows();
0517 }
0518 
0519 void KisAllTagResourceModel::slotResourceActiveStateChanged(const QString &resourceType, int resourceId)
0520 {
0521     if (resourceType != d->resourceType) return;
0522     if (resourceId < 0) return;
0523 
0524     resetQuery();
0525 
0526     /// The model has multiple rows for every resource, one row per tag,
0527     /// so we need to notify about the changes in all the tags
0528     QVector<QModelIndex> indexes;
0529 
0530     for (int i = 0; i < rowCount(); ++i)  {
0531         const QModelIndex idx = this->index(i, 0);
0532         KIS_ASSERT_RECOVER(idx.isValid()) { continue; }
0533 
0534         if (idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceId).toInt() == resourceId) {
0535             indexes << idx;
0536         }
0537     }
0538 
0539     Q_FOREACH(const QModelIndex &index, indexes) {
0540         Q_EMIT dataChanged(index, index, {Qt::CheckStateRole, Qt::UserRole + KisAllTagResourceModel::ResourceActive});
0541     }
0542 }
0543 
0544 QString KisAllTagResourceModel::createQuery(bool onlyActive, bool returnADbIndexToo)
0545 {
0546     QString query = QString("WITH initial_selection AS (\n"
0547                             "    SELECT   tags.id\n"
0548                             "    ,        resources.name\n"
0549                             "    ,        resources.filename\n"
0550                             "    ,        resources.md5sum\n"
0551                             "    ,        resource_types.id            as    resource_type_id\n"
0552                             "    ,        resource_types.name          as    resource_type_name\n"
0553                             "    ,        min(resources.id)            as    resource_id\n"
0554                             ) + (returnADbIndexToo ? QString(", resource_tags.id   as   resource_tags_row_id\n") : QString("")) + QString( // include r_t row id
0555                             ) + (onlyActive ? QString("") : QString(", resource_tags.active   as   resource_tags_pair_active\n")) + QString( // include r_t row active info
0556                             "    FROM     resource_types\n"
0557                             "    JOIN     resource_tags\n   ON       resource_tags.resource_id    = resources.id\n"
0558                             ) + (onlyActive ? QString("    AND       resource_tags.active         = 1\n") : QString("")) + QString( // make sure only active tags are used
0559                             "    JOIN     resources         ON       resources.resource_type_id   = resource_types.id\n"
0560                             "    JOIN     tags              ON       tags.id                      = resource_tags.tag_id\n"
0561                             "                              AND       tags.resource_type_id        = resource_types.id\n"
0562                             "    WHERE    resource_types.name          = :resource_type\n"
0563                             "    GROUP BY tags.id\n"
0564                             "    ,        resources.name\n"
0565                             "    ,        resources.filename\n"
0566                             "    ,        resources.md5sum\n"
0567                             "    ,        resource_types.id\n"
0568                             "    ORDER BY resource_tags.id\n"
0569                             ")\n"
0570                             "SELECT \n"
0571                             "       initial_selection.id           as tag_id\n"
0572                             ",      initial_selection.name         as resource_name\n"
0573                             ",      initial_selection.filename     as resource_filename\n"
0574                             ",      initial_selection.md5sum       as resource_md5sum\n"
0575                             ",      initial_selection.resource_id  as resource_id\n"
0576                             ",      tags.url                       as tag_url"
0577                             ",      tags.active                    as tag_active"
0578                             ",      tags.name                      as tag_name"
0579                             ",      tags.comment                   as tag_comment"
0580                             ",      resources.status               as resource_active\n"
0581                             ",      resources.tooltip              as resource_tooltip\n"
0582                             ",      resources.status               as resource_active\n"
0583                             ",      resources.storage_id           as storage_id\n"
0584                             ",      storages.active                as resource_storage_active\n"
0585                             ",      storages.location              as location\n"
0586                             ",      tag_translations.name          as translated_name\n"
0587                             ",      tag_translations.comment       as translated_comment\n"
0588                             ",      initial_selection.resource_type_name as resource_type\n"
0589                             ) + (returnADbIndexToo ? QString(", initial_selection.resource_tags_row_id   as   resource_tags_row_id\n") : QString("")) + QString(
0590                             ) + (onlyActive ? QString("") : QString(", initial_selection.resource_tags_pair_active   as   resource_tags_pair_active\n")) + QString(
0591                             "FROM      initial_selection\n"
0592                             "JOIN      tags               ON   tags.id                     = initial_selection.id\n"
0593                             "                            AND   tags.resource_type_id       = initial_selection.resource_type_id\n"
0594                             "JOIN      resources          ON   resources.id                = resource_id\n"
0595                             "JOIN      storages           ON   storages.id                 = resources.storage_id\n"
0596                             "LEFT JOIN tag_translations   ON   tag_translations.tag_id     = initial_selection.id\n"
0597                             "                            AND   tag_translations.language   = :language\n");
0598 
0599     return query;
0600 
0601 
0602 }
0603 
0604 bool KisAllTagResourceModel::resetQuery()
0605 {
0606     bool r = d->query.prepare(createQuery(true));
0607 
0608     if (!r) {
0609         qWarning() << "Could not prepare KisAllTagResourcesModel query" << d->query.lastError();
0610     }
0611 
0612     d->query.bindValue(":resource_type", d->resourceType);
0613     d->query.bindValue(":language", KisTag::currentLocale());
0614 
0615     r = d->query.exec();
0616 
0617     if (!r) {
0618         qWarning() << "Could not execute KisAllTagResourcesModel query" << d->query.lastError();
0619     }
0620 
0621     d->cachedRowCount = -1;
0622 
0623     return r;
0624 }
0625 
0626 
0627 struct KisTagResourceModel::Private {
0628     QString resourceType;
0629     KisAllTagResourceModel *sourceModel {0};
0630     QVector<int> tagIds;
0631     QVector<int> resourceIds;
0632     TagFilter tagFilter {ShowActiveTags};
0633     StorageFilter storageFilter {ShowActiveStorages};
0634     ResourceFilter resourceFilter {ShowActiveResources};
0635 };
0636 
0637 
0638 KisTagResourceModel::KisTagResourceModel(const QString &resourceType, QObject *parent)
0639     : QSortFilterProxyModel(parent)
0640     , d(new Private())
0641 {
0642     d->resourceType = resourceType;
0643     d->sourceModel = KisResourceModelProvider::tagResourceModel(resourceType);
0644     setSourceModel(d->sourceModel);
0645 
0646     connect(KisResourceLocator::instance(), SIGNAL(storageAdded(const QString &)), this, SLOT(storageChanged(const QString &)));
0647     connect(KisResourceLocator::instance(), SIGNAL(storageRemoved(const QString &)), this, SLOT(storageChanged(const QString &)));
0648     connect(KisStorageModel::instance(), SIGNAL(storageEnabled(const QString &)), this, SLOT(storageChanged(const QString &)));
0649     connect(KisStorageModel::instance(), SIGNAL(storageDisabled(const QString &)), this, SLOT(storageChanged(const QString &)));
0650 }
0651 
0652 KisTagResourceModel::~KisTagResourceModel()
0653 {
0654     delete d;
0655 }
0656 
0657 void KisTagResourceModel::setTagFilter(KisTagResourceModel::TagFilter filter)
0658 {
0659     d->tagFilter = filter;
0660     invalidateFilter();
0661 }
0662 
0663 void KisTagResourceModel::setResourceFilter(KisTagResourceModel::ResourceFilter filter)
0664 {
0665     d->resourceFilter = filter;
0666     invalidateFilter();
0667 }
0668 
0669 void KisTagResourceModel::setStorageFilter(KisTagResourceModel::StorageFilter filter)
0670 {
0671     d->storageFilter = filter;
0672     invalidateFilter();
0673 }
0674 
0675 bool KisTagResourceModel::tagResources(const KisTagSP tag, const QVector<int> &resourceIds)
0676 {
0677     bool r = d->sourceModel->tagResources(tag, resourceIds);
0678     return r;
0679 }
0680 
0681 bool KisTagResourceModel::untagResources(const KisTagSP tag, const QVector<int> &resourceIds)
0682 {
0683     return d->sourceModel->untagResources(tag, resourceIds);
0684 }
0685 
0686 int KisTagResourceModel::isResourceTagged(const KisTagSP tag, const int resourceId)
0687 {
0688     return d->sourceModel->isResourceTagged(tag, resourceId);
0689 }
0690 
0691 void KisTagResourceModel::setTagsFilter(const QVector<int> tagIds)
0692 {
0693     d->tagIds = tagIds;
0694     invalidateFilter();
0695 }
0696 
0697 void KisTagResourceModel::setResourcesFilter(const QVector<int> resourceIds)
0698 {
0699     d->resourceIds = resourceIds;
0700     invalidateFilter();
0701 }
0702 
0703 void KisTagResourceModel::setTagsFilter(const QVector<KisTagSP> tags)
0704 {
0705     d->tagIds.clear();
0706     Q_FOREACH(const KisTagSP tag, tags) {
0707         if (tag && tag->valid() && tag->id() > -1) {
0708             d->tagIds << tag->id();
0709         }
0710     }
0711     invalidateFilter();
0712 }
0713 
0714 void KisTagResourceModel::setResourcesFilter(const QVector<KoResourceSP> resources)
0715 {
0716     d->resourceIds.clear();
0717     Q_FOREACH(const KoResourceSP resource, resources) {
0718         if (resource->valid() && resource->resourceId() > -1) {
0719             d->resourceIds << resource->resourceId();
0720         }
0721     }
0722     invalidateFilter();
0723 }
0724 
0725 bool KisTagResourceModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
0726 {
0727     QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
0728     if (!idx.isValid()) return false;
0729 
0730     int tagId = idx.data(Qt::UserRole + KisAllTagResourceModel::TagId).toInt();
0731     int resourceId = idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceId).toInt();
0732     bool tagActive = idx.data(Qt::UserRole + KisAllTagResourceModel::TagActive).toBool();
0733     bool resourceActive = idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceActive).toBool();
0734     bool resourceStorageActive = idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceStorageActive).toBool();
0735 
0736     if (d->tagFilter == ShowAllTags && d->resourceFilter == ShowAllResources && d->storageFilter == ShowAllStorages) {
0737         return ((d->tagIds.contains(tagId) || d->tagIds.isEmpty()) &&
0738                 (d->resourceIds.contains(resourceId) || d->resourceIds.isEmpty()));
0739     }
0740 
0741     if ((d->tagFilter == ShowActiveTags && !tagActive)
0742             || (d->tagFilter == ShowInactiveTags && tagActive)) {
0743         return false;
0744     }
0745 
0746     if ((d->resourceFilter == ShowActiveResources && !resourceActive)
0747             || (d->resourceFilter == ShowInactiveResources && resourceActive)) {
0748         return false;
0749     }
0750 
0751     if ((d->storageFilter == ShowActiveStorages && !resourceStorageActive)
0752             || (d->storageFilter == ShowInactiveStorages && resourceStorageActive)) {
0753         return false;
0754     }
0755 
0756     return ((d->tagIds.contains(tagId) || d->tagIds.isEmpty())
0757             && (d->resourceIds.contains(resourceId) || d->resourceIds.isEmpty()));
0758 }
0759 
0760 bool KisTagResourceModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
0761 {
0762     QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisAllTagResourceModel::ResourceName).toString();
0763     QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisAllTagResourceModel::ResourceName).toString();
0764     return nameLeft.toLower() < nameRight.toLower();
0765 }
0766 
0767 void KisTagResourceModel::storageChanged(const QString &location)
0768 {
0769     Q_UNUSED(location);
0770     invalidateFilter();
0771 }
0772 
0773 KoResourceSP KisTagResourceModel::resourceForIndex(QModelIndex index) const
0774 {
0775     int id = data(index, Qt::UserRole + KisAllTagResourceModel::ResourceId).toInt();
0776     if (id < 1)  return nullptr;
0777     KoResourceSP res = KisResourceLocator::instance()->resourceForId(id);
0778     return res;
0779 }
0780 
0781 QModelIndex KisTagResourceModel::indexForResource(KoResourceSP resource) const
0782 {
0783     if (!resource || !resource->valid() || resource->resourceId() < 0) return QModelIndex();
0784 
0785     for (int i = 0; i < rowCount(); ++i)  {
0786         QModelIndex idx = index(i, Qt::UserRole + KisAllTagResourceModel::ResourceId);
0787         Q_ASSERT(idx.isValid());
0788         if (idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceId).toInt() == resource->resourceId()) {
0789             return idx;
0790         }
0791     }
0792     return QModelIndex();
0793 }
0794 
0795 QModelIndex KisTagResourceModel::indexForResourceId(int resourceId) const
0796 {
0797     if (resourceId < 0) return QModelIndex();
0798     for (int i = 0; i < rowCount(); ++i)  {
0799         QModelIndex idx = index(i, Qt::UserRole + KisAllTagResourceModel::ResourceId);
0800         Q_ASSERT(idx.isValid());
0801         if (idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceId).toInt() == resourceId) {
0802             return idx;
0803         }
0804     }
0805     return QModelIndex();
0806 }
0807 
0808 bool KisTagResourceModel::setResourceActive(const QModelIndex &index, bool value)
0809 {
0810     KisResourceModel resourceModel(d->resourceType);
0811     QModelIndex idx = resourceModel.indexForResource(resourceForIndex(index));
0812     return resourceModel.setResourceActive(idx, value);
0813 }
0814 
0815 KoResourceSP KisTagResourceModel::importResourceFile(const QString &filename, const bool allowOverwrite, const QString &storageId)
0816 {
0817     // Since we're importing the resource, there's no reason to add rows to the tags::resources table,
0818     // because the resource is untagged.
0819     KisResourceModel resourceModel(d->resourceType);
0820     return resourceModel.importResourceFile(filename, allowOverwrite, storageId);
0821 }
0822 
0823 KoResourceSP KisTagResourceModel::importResource(const QString &filename, QIODevice *device, const bool allowOverwrite, const QString &storageId)
0824 {
0825     // Since we're importing the resource, there's no reason to add rows to the tags::resources table,
0826     // because the resource is untagged.
0827     KisResourceModel resourceModel(d->resourceType);
0828     return resourceModel.importResource(filename, device, allowOverwrite, storageId);
0829 }
0830 
0831 bool KisTagResourceModel::importWillOverwriteResource(const QString &fileName, const QString &storageLocation) const
0832 {
0833     KisResourceModel resourceModel(d->resourceType);
0834     return resourceModel.importWillOverwriteResource(fileName, storageLocation);
0835 }
0836 
0837 bool KisTagResourceModel::exportResource(KoResourceSP resource, QIODevice *device)
0838 {
0839     KisResourceModel resourceModel(d->resourceType);
0840     return resourceModel.exportResource(resource, device);
0841 }
0842 
0843 bool KisTagResourceModel::addResource(KoResourceSP resource, const QString &storageId)
0844 {
0845     // Since we're importing the resource, there's no reason to add rows to the tags::resources table,
0846     // because the resource is untagged.
0847     KisResourceModel resourceModel(d->resourceType);
0848     return resourceModel.addResource(resource, storageId);
0849 }
0850 
0851 bool KisTagResourceModel::updateResource(KoResourceSP resource)
0852 {
0853     KisResourceModel resourceModel(d->resourceType);
0854     bool r = resourceModel.updateResource(resource);
0855     if (r) {
0856         QModelIndex index = indexForResource(resource);
0857         if (index.isValid()) {
0858             emit dataChanged(index, index, {Qt::EditRole});
0859         }
0860     }
0861     return r;
0862 }
0863 
0864 bool KisTagResourceModel::reloadResource(KoResourceSP resource)
0865 {
0866     KisResourceModel resourceModel(d->resourceType);
0867     bool r = resourceModel.reloadResource(resource);
0868     if (r) {
0869         QModelIndex index = indexForResource(resource);
0870         if (index.isValid()) {
0871             emit dataChanged(index, index, {Qt::EditRole});
0872         }
0873     }
0874     return r;
0875 }
0876 
0877 bool KisTagResourceModel::renameResource(KoResourceSP resource, const QString &name)
0878 {
0879     KisResourceModel resourceModel(d->resourceType);
0880     bool r = resourceModel.renameResource(resource, name);
0881     if (r) {
0882         QModelIndex index = indexForResource(resource);
0883         if (index.isValid()) {
0884             emit dataChanged(index, index, {Qt::EditRole});
0885         }
0886     }
0887     return r;
0888 }
0889 
0890 bool KisTagResourceModel::setResourceMetaData(KoResourceSP resource, QMap<QString, QVariant> metadata)
0891 {
0892     KisResourceModel resourceModel(d->resourceType);
0893     return resourceModel.setResourceMetaData(resource, metadata);
0894 }