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 }