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