Krita Source Code Documentation
Loading...
Searching...
No Matches
KisTagResourceModel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2023 L. E. Segovia <amy@amyspark.me>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
8
9#include <QFont>
10#include <QSqlError>
11#include <QSqlQuery>
12#include <QSqlDatabase>
13#include <QMap>
14
15#include <KisResourceLocator.h>
16#include <KisResourceModel.h>
19#include <KisStorageModel.h>
20#include <kis_assert.h>
21
28
29
30KisAllTagResourceModel::KisAllTagResourceModel(const QString &resourceType, QObject *parent)
31 : QAbstractTableModel(parent)
32 , d(new Private())
33{
34 d->resourceType = resourceType;
35 resetQuery();
36
37 connect(KisResourceLocator::instance(), SIGNAL(storageAdded(const QString&)), this, SLOT(addStorage(const QString&)));
38 connect(KisResourceLocator::instance(), SIGNAL(storageRemoved(const QString&)), this, SLOT(removeStorage(const QString&)));
39 connect(KisStorageModel::instance(), SIGNAL(storageEnabled(const QString&)), this, SLOT(addStorage(const QString&)));
40 connect(KisStorageModel::instance(), SIGNAL(storageDisabled(const QString&)), this, SLOT(removeStorage(const QString&)));
41 connect(KisResourceLocator::instance(), SIGNAL(resourceActiveStateChanged(const QString&, int)), this, SLOT(slotResourceActiveStateChanged(const QString&, int)));
42
48}
49
54
55int KisAllTagResourceModel::rowCount(const QModelIndex &parent) const
56{
57 if (parent.isValid()) {
58 return 0;
59 }
60
61 if (d->cachedRowCount < 0) {
62 QSqlQuery q;
63 const bool r = q.prepare("SELECT COUNT(DISTINCT resource_tags.tag_id || resources.name || resources.filename || resources.md5sum)\n"
64 "FROM resource_tags\n"
65 ", resources\n"
66 ", resource_types\n"
67 "WHERE resource_tags.resource_id = resources.id\n"
68 "AND resources.resource_type_id = resource_types.id\n"
69 "AND resource_types.name = :resource_type\n"
70 "AND resource_tags.active = 1\n");
71
72 if (!r) {
73 qWarning() << "Could not execute resource/tags rowcount query" << q.lastError();
74 return 0;
75 }
76
77 q.bindValue(":resource_type", d->resourceType);
78
79 if (!q.exec()) {
80 qWarning() << "Could not execute resource/tags rowcount query" << q.lastError();
81 return 0;
82 }
83
84 q.first();
85
86 const_cast<KisAllTagResourceModel*>(this)->d->cachedRowCount = q.value(0).toInt();
87 }
88 return d->cachedRowCount;
89}
90
91int KisAllTagResourceModel::columnCount(const QModelIndex &parent) const
92{
93 if (parent.isValid()) {
94 return 0;
95 }
96
97 return d->columnCount;
98}
99
100QVariant KisAllTagResourceModel::data(const QModelIndex &index, int role) const
101{
102 QVariant v;
103
104 if (!index.isValid()) { return v; }
105 if (index.row() > rowCount()) { return v; }
106 if (index.column() > d->columnCount) { return v;}
107
108 bool pos = const_cast<KisAllTagResourceModel*>(this)->d->query.seek(index.row());
109 if (!pos) {return v;}
110
111 if (role < Qt::UserRole + TagId && index.column() < TagId) {
112 return KisResourceQueryMapper::variantFromResourceQuery(d->query, index.column(), role, true);
113 }
114
115 if (role == Qt::FontRole) {
116 return QFont();
117 }
118
119 if (index.column() >= TagId) {
120 // trick to get the correct value without writing everything again
121 // this is used for example in case of sorting
122 role = Qt::UserRole + index.column();
123 }
124
125 // These are not shown, but needed for the filter
126 switch(role) {
127 case Qt::UserRole + TagId:
128 {
129 v = d->query.value("tag_id");
130 break;
131 }
132 case Qt::UserRole + ResourceId:
133 {
134 v = d->query.value("resource_id");
135 break;
136 }
137 case Qt::UserRole + Tag:
138 {
139 KisTagSP tag = KisResourceLocator::instance()->tagForUrl(d->query.value("tag_url").toString(), d->resourceType);
140 v = QVariant::fromValue(tag);
141 break;
142 }
143 case Qt::UserRole + Resource:
144 {
145 v = QVariant::fromValue(KisResourceLocator::instance()->resourceForId(d->query.value("resource_id").toInt()));
146 break;
147 }
148 case Qt::UserRole + ResourceActive:
149 {
150 v = d->query.value("resource_active");
151 break;
152 }
153 case Qt::UserRole + TagActive:
154 {
155 v = d->query.value("tag_active");
156 break;
157 }
158 case Qt::UserRole + ResourceStorageActive:
159 {
160 v = d->query.value("resource_storage_active");
161 break;
162 }
163 case Qt::UserRole + ResourceName:
164 {
165 v = d->query.value("resource_name");
166 break;
167 }
168 case Qt::UserRole + TagName:
169 {
170 v = d->query.value("translated_name");
171 if (v.isNull()) {
172 v = d->query.value("tag_name");
173 }
174 break;
175 }
176
177 default:
178 ;
179 }
180 return v;
181}
182
184{
185 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(tag && tag->valid() && tag->id() >= 0, false);
186
187 // notes for performance:
188 // the only two costly parts are:
189 // - executing the query constructed by createQuery()
190 // - running endInsertRows() (because it updates all the views and filter proxies etc...)
191
192 QVector<int> resourceIdsToAdd;
193 QVector<int> resourceIdsToUpdate;
194
195 // looks expensive but actually isn't
196 for (int i = 0; i < resourceIds.count(); i++) {
197 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(resourceIds[i] >= 0, false);
198 int taggedState = isResourceTagged(tag, resourceIds[i]);
199 switch (taggedState) {
200 case -1:
201 // never tagged
202 resourceIdsToAdd.append(resourceIds[i]);
203 break;
204 case 0:
205 // tagged but then untagged
206 resourceIdsToUpdate.append(resourceIds[i]);
207 break;
208 case 1:
209 // it means the resource is already tagged; do nothing
210 break;
211 }
212 }
213
214 if (resourceIdsToAdd.isEmpty() && resourceIdsToUpdate.isEmpty()) {
215 // Is already tagged, let's do nothing
216 return true;
217 }
218
219 int howManyTimesBeginInsertedCalled = 0;
220
221 if (resourceIdsToUpdate.count() > 0) {
222
223 QSqlQuery allIndices;
224 if (!allIndices.prepare(createQuery(false, true))) {
225 qWarning() << "Could not prepare tagResource-allIndices query" << allIndices.lastError();
226 }
227
228 allIndices.bindValue(":resource_type", d->resourceType);
229 allIndices.bindValue(":language", KisTag::currentLocale());
230
231 if (!allIndices.exec()) {
232 qWarning() << "Could not execute tagResource-allIndices query" << allIndices.lastError();
233 }
234
235 int activesRowId = -1;
236 int lastActiveRowId = -1;
237
238 // needed for beginInsertRows calculations
239 QMap<int, int> resourcesCountForLastActiveRowId;
240
241 while (allIndices.next()) {
242 bool isActive = allIndices.value("resource_tags_pair_active").toBool();
243 if (isActive) {
244 activesRowId++;
245 lastActiveRowId = activesRowId;
246 } else {
247 bool variantSuccess = true;
248 int rowTagId = allIndices.value("tag_id").toInt(&variantSuccess);
249 KIS_SAFE_ASSERT_RECOVER(variantSuccess) { rowTagId = -1; }
250 int rowResourceId = allIndices.value("resource_id").toInt(&variantSuccess);
251 KIS_SAFE_ASSERT_RECOVER(variantSuccess) { rowResourceId = -1; }
252 if (rowTagId == tag->id() && resourceIdsToUpdate.contains(rowResourceId)) {
253 if (!resourcesCountForLastActiveRowId.contains(lastActiveRowId)) {
254 resourcesCountForLastActiveRowId[lastActiveRowId] = 1;
255 } else {
256 resourcesCountForLastActiveRowId[lastActiveRowId] = resourcesCountForLastActiveRowId[lastActiveRowId] + 1;
257 }
258 }
259 }
260 }
261
262 Q_FOREACH(const int key, resourcesCountForLastActiveRowId.keys()) {
263 // when having multiple beginInsertRows:
264 // let's say you have this model:
265 // 0 A
266 // 1 A
267 // 2 A
268 // <- put 2 Bs here
269 // 3 A
270 // <- put one B here
271 // 4 A
272 // and you want to add the two Bs and then add another B
273 // then the signals should be:
274 // beginRemoveRows(3, 4) <- new indices of the two Bs
275 // beginRemoveRows(4, 4) <- new index of the one B ignoring the action before
276
277
278 beginInsertRows(QModelIndex(), key + 1, key + resourcesCountForLastActiveRowId[key]);
279 howManyTimesBeginInsertedCalled++;
280 }
281
282 // Resource was tagged, then untagged. Tag again;
283 QSqlQuery q;
284
285 if (!q.prepare("UPDATE resource_tags\n"
286 "SET active = 1\n"
287 "WHERE resource_id = :resource_id\n"
288 "AND tag_id = :tag_id")) {
289
290
291 qWarning() << "Could not prepare update resource_tags to active statement" << q.lastError();
292
293 return false;
294 }
295
296 QVariantList resourceIdsVariants;
297 QVariantList tagIdVariants;
298
299 for (int i = 0; i < resourceIdsToUpdate.count(); i++) {
300 resourceIdsVariants << QVariant(resourceIdsToUpdate[i]);
301 tagIdVariants << QVariant(tag->id());
302 }
303
304 q.bindValue(":resource_id", resourceIdsVariants);
305 q.bindValue(":tag_id", tagIdVariants);
306
307 if (!q.execBatch()) {
308 qWarning() << "Could not execute update resource_tags to active statement" << q.lastError();
309 for (int i = 0; i < howManyTimesBeginInsertedCalled; i++) {
310 endInsertRows();
311 }
312 return false;
313 }
314
315 }
316
317 if (resourceIdsToAdd.count() > 0) {
318
319 beginInsertRows(QModelIndex(), rowCount(), rowCount() + resourceIdsToAdd.count() - 1);
320 howManyTimesBeginInsertedCalled++;
321
322 // Resource was never tagged before, insert it. The active column is DEFAULT 1
323 QSqlQuery q;
324
325
326 QString values;
327 for (int i = 0; i < resourceIdsToAdd.count(); i++) {
328 if (i > 0) {
329 values.append(", ");
330 }
331 values.append("(?, ?, ?)");
332 }
333
334 if (!q.prepare(QString("INSERT INTO resource_tags\n"
335 "(resource_id, tag_id, active)\n"
336 "VALUES ") + values + QString(";\n"))) {
337 qWarning() << "Could not prepare insert into resource tags statement" << q.lastError();
338 for (int i = 0; i < howManyTimesBeginInsertedCalled; i++) {
339 endInsertRows();
340 }
341 return false;
342 }
343
344 for (int i = 0; i < resourceIdsToAdd.count(); i++) {
345 q.addBindValue(resourceIdsToAdd[i]);
346 q.addBindValue(tag->id());
347 q.addBindValue(true);
348
349 }
350
351 if (!q.exec()) {
352 qWarning() << "Could not execute insert into resource tags statement" << q.boundValues() << q.lastError();
353 for (int i = 0; i < howManyTimesBeginInsertedCalled; i++) {
354 endInsertRows();
355 }
356 return false;
357 }
358 }
359
360 resetQuery();
361
362 for (int i = 0; i < howManyTimesBeginInsertedCalled; i++) {
363 endInsertRows();
364 }
365
366 return true;
367}
368
370{
371 if (!tag || !tag->valid()) return false;
372 if (!d->query.isSelect()) return false;
373 if (rowCount() < 1) return false;
374
375 int beginRemoveRowsCount = 0;
376
377 QSqlQuery q;
378
379 if (!q.prepare("UPDATE resource_tags\n"
380 "SET active = 0\n"
381 "WHERE tag_id = :tag_id\n"
382 "AND resource_id = :resource_id")) {
383 qWarning() << "Could not prepare untagResource-update query" << q.lastError();
384 return false;
385 }
386
387 QSqlQuery allIndices;
388 if (!allIndices.prepare(createQuery(true, true))) {
389 qWarning() << "Could not prepare untagResource-allIndices query " << allIndices.lastError();
390 }
391
392 allIndices.bindValue(":resource_type", d->resourceType);
393 allIndices.bindValue(":language", KisTag::currentLocale());
394
395 if (!allIndices.exec()) {
396 qCritical() << "Could not exec untagResource-allIndices query " << allIndices.lastError();
397 }
398
399 int activesRowId = -1;
400 int lastActiveRowId = -1;
401
402 // needed for beginInsertRows indices calculations
403 QMap<int, int> resourcesCountForLastActiveRowId;
404
405 while (allIndices.next()) {
406 bool variantSuccess = true;
407
408 bool isActive = true; // all of them are active!
409 KIS_SAFE_ASSERT_RECOVER(variantSuccess) { isActive = false; }
410
411 int rowTagId = allIndices.value("tag_id").toInt(&variantSuccess);
412 KIS_SAFE_ASSERT_RECOVER(variantSuccess) { rowTagId = -1; }
413 int rowResourceId = allIndices.value("resource_id").toInt(&variantSuccess);
414 KIS_SAFE_ASSERT_RECOVER(variantSuccess) { rowResourceId = -1; }
415
416 bool willStayActive = isActive && (rowTagId != tag->id() || !resourceIds.contains(rowResourceId));
417 activesRowId++;
418 if (willStayActive) {
419 lastActiveRowId = activesRowId;
420 } else if (isActive) {
421 // means we're removing it
422 if (!resourcesCountForLastActiveRowId.contains(lastActiveRowId)) {
423 resourcesCountForLastActiveRowId[lastActiveRowId] = 0;
424 }
425 resourcesCountForLastActiveRowId[lastActiveRowId]++;
426 }
427 }
428
429 Q_FOREACH(const int key, resourcesCountForLastActiveRowId.keys()) {
430 // when having multiple beginRemoveRows:
431 // let's say you have this model:
432 // 0 A
433 // 1 A
434 // 2 A
435 // 3 *B*
436 // 4 *B*
437 // 5 A
438 // 6 *B*
439 // 7 A
440 // and you want to remove all Bs
441 // then the signals should be:
442 // beginRemoveRows(3, 4) <- first two indices in the obvious way
443 // beginRemoveRows(4, 4) <- next index as if the first action was already done
444 // (so `5 A` already became `3 A`, so the *B* is `4 B`, not `6 B`)
445
446 beginRemoveRows(QModelIndex(), key + 1, key + resourcesCountForLastActiveRowId[key]);
447 beginRemoveRowsCount++;
448 }
449
450 QSqlDatabase::database().transaction();
451 for (int i = 0; i < resourceIds.count(); i++) {
452 int resourceId = resourceIds[i];
453
454 if (resourceId < 0) continue;
455 if (isResourceTagged(tag, resourceId) < 1) continue;
456
457 q.bindValue(":tag_id", tag->id());
458 q.bindValue(":resource_id", resourceId);
459
460 if (!q.exec()) {
461 qWarning() << "Could not execute untagResource-update query" << q.lastError() << q.boundValues();
462 for (int i = 0; i < beginRemoveRowsCount; i++) {
463 endRemoveRows();
464 }
465 QSqlDatabase::database().rollback();
466 return false;
467 }
468 }
469 QSqlDatabase::database().commit();
470
471 if (beginRemoveRowsCount > 0) {
472 resetQuery();
473 for (int i = 0; i < beginRemoveRowsCount; i++) {
474 endRemoveRows();
475 }
476 }
477
478 return true;
479}
480
481int KisAllTagResourceModel::isResourceTagged(const KisTagSP tag, const int resourceId)
482{
483 QSqlQuery query;
484 bool r = query.prepare("SELECT resource_tags.active\n"
485 "FROM resource_tags\n"
486 "WHERE resource_tags.resource_id = :resource_id\n"
487 "AND resource_tags.tag_id = :tag_id\n");
488
489 if (!r) {
490 qWarning() << "Could not prepare bool KisAllTagResourceModel::checkResourceTaggedState query" << query.lastError();
491 return false;
492 }
493
494 query.bindValue(":resource_id", resourceId);
495 query.bindValue(":tag_id", tag->id());
496
497 if (!query.exec()) {
498 qWarning() << "Could not execute is resource tagged with a specific tag query" << query.boundValues() << query.lastError();
499 return false;
500 }
501
502 r = query.first();
503 if (!r) {
504 // Resource was not tagged
505 return -1;
506 }
507
508 return query.value(0).toInt() > 0;
509}
510
511void KisAllTagResourceModel::addStorage(const QString &location)
512{
513 Q_UNUSED(location);
514 beginInsertRows(QModelIndex(), rowCount(), rowCount());
515 resetQuery();
516 endInsertRows();
517}
518
519void KisAllTagResourceModel::removeStorage(const QString &location)
520{
521 Q_UNUSED(location);
522 beginRemoveRows(QModelIndex(), rowCount(), rowCount());
523 resetQuery();
524 endRemoveRows();
525}
526
527void KisAllTagResourceModel::slotResourceActiveStateChanged(const QString &resourceType, int resourceId)
528{
529 if (resourceType != d->resourceType) return;
530 if (resourceId < 0) return;
531
532 resetQuery();
533
536 QVector<QModelIndex> indexes;
537
538 for (int i = 0; i < rowCount(); ++i) {
539 const QModelIndex idx = this->index(i, 0);
540 KIS_ASSERT_RECOVER(idx.isValid()) { continue; }
541
542 if (idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceId).toInt() == resourceId) {
543 indexes << idx;
544 }
545 }
546
547 Q_FOREACH(const QModelIndex &index, indexes) {
548 Q_EMIT dataChanged(index, index, {Qt::CheckStateRole, Qt::UserRole + KisAllTagResourceModel::ResourceActive});
549 }
550}
551
552QString KisAllTagResourceModel::createQuery(bool onlyActive, bool returnADbIndexToo)
553{
554 QString query = QString("WITH initial_selection AS (\n"
555 " SELECT tags.id\n"
556 " , resources.name\n"
557 " , resources.filename\n"
558 " , resources.md5sum\n"
559 " , resource_types.id as resource_type_id\n"
560 " , resource_types.name as resource_type_name\n"
561 " , min(resources.id) as resource_id\n"
562 ) + (returnADbIndexToo ? QString(", resource_tags.id as resource_tags_row_id\n") : QString("")) + QString( // include r_t row id
563 ) + (onlyActive ? QString("") : QString(", resource_tags.active as resource_tags_pair_active\n")) + QString( // include r_t row active info
564 " FROM resource_types\n"
565 " JOIN resource_tags\n ON resource_tags.resource_id = resources.id\n"
566 ) + (onlyActive ? QString(" AND resource_tags.active = 1\n") : QString("")) + QString( // make sure only active tags are used
567 " JOIN resources ON resources.resource_type_id = resource_types.id\n"
568 " JOIN tags ON tags.id = resource_tags.tag_id\n"
569 " AND tags.resource_type_id = resource_types.id\n"
570 " WHERE resource_types.name = :resource_type\n"
571 " GROUP BY tags.id\n"
572 " , resources.name\n"
573 " , resources.filename\n"
574 " , resources.md5sum\n"
575 " , resource_types.id\n"
576 " ORDER BY resource_tags.id\n"
577 ")\n"
578 "SELECT \n"
579 " initial_selection.id as tag_id\n"
580 ", initial_selection.name as resource_name\n"
581 ", initial_selection.filename as resource_filename\n"
582 ", initial_selection.md5sum as resource_md5sum\n"
583 ", initial_selection.resource_id as resource_id\n"
584 ", tags.url as tag_url"
585 ", tags.active as tag_active"
586 ", tags.name as tag_name"
587 ", tags.comment as tag_comment"
588 ", resources.status as resource_active\n"
589 ", resources.tooltip as resource_tooltip\n"
590 ", resources.status as resource_active\n"
591 ", resources.storage_id as storage_id\n"
592 ", storages.active as resource_storage_active\n"
593 ", storages.location as location\n"
594 ", tag_translations.name as translated_name\n"
595 ", tag_translations.comment as translated_comment\n"
596 ", initial_selection.resource_type_name as resource_type\n"
597 ) + (returnADbIndexToo ? QString(", initial_selection.resource_tags_row_id as resource_tags_row_id\n") : QString("")) + QString(
598 ) + (onlyActive ? QString("") : QString(", initial_selection.resource_tags_pair_active as resource_tags_pair_active\n")) + QString(
599 "FROM initial_selection\n"
600 "JOIN tags ON tags.id = initial_selection.id\n"
601 " AND tags.resource_type_id = initial_selection.resource_type_id\n"
602 "JOIN resources ON resources.id = resource_id\n"
603 "JOIN storages ON storages.id = resources.storage_id\n"
604 "LEFT JOIN tag_translations ON tag_translations.tag_id = initial_selection.id\n"
605 " AND tag_translations.language = :language\n");
606
607 return query;
608}
609
610// Keep in sync with KisAllResourcesModel::headerData for sections 1-16
611// then KisAllTagResourceModel::data for 16-25
612QVariant KisAllTagResourceModel::headerData(int section, Qt::Orientation orientation, int role) const
613{
614 if (orientation != Qt::Horizontal) {
615 return {};
616 }
617 if (role != Qt::DisplayRole) {
618 return {};
619 }
620
621 switch (section) {
623 return i18n("Id");
625 return i18n("Storage ID");
627 return i18n("Name");
629 return i18n("File Name");
631 return i18n("Tooltip");
633 return i18n("Image");
635 return i18n("Status");
637 return i18n("Location");
639 return i18n("Resource Type");
641 return i18n("Active");
643 return i18n("Storage Active");
645 return i18n("md5sum");
647 return i18n("Tags");
649 return i18n("Large Thumbnail");
651 return i18n("Dirty");
653 return i18n("Metadata");
654 default:
655 role = Qt::UserRole + section;
656 break;
657 }
658
659 switch (role) {
660 case Qt::UserRole + KisAllTagResourceModel::TagId:
661 return i18n("Tag ID");
662 case Qt::UserRole + KisAllTagResourceModel::ResourceId:
663 return i18n("Resource ID");
664 case Qt::UserRole + KisAllTagResourceModel::Tag:
665 return i18n("Tag Url");
666 case Qt::UserRole + KisAllTagResourceModel::Resource:
667 return i18n("Resource");
668 case Qt::UserRole + ResourceActive:
669 return i18n("Resource Active");
670 case Qt::UserRole + TagActive:
671 return i18n("Tag Active");
672 case Qt::UserRole + ResourceStorageActive:
673 return i18n("Storage Active");
674 case Qt::UserRole + ResourceName:
675 return i18n("Resource Name");
676 case Qt::UserRole + TagName:
677 return i18n("Tag File Name");
678 default:
679 break;
680 }
681
682 return {};
683}
684
685// As above, keep in sync.
686QHash<int, QByteArray> KisAllTagResourceModel::roleNames() const
687{
688 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
689 roles[Qt::UserRole + KisAbstractResourceModel::Id] = "id";
690 roles[Qt::UserRole + KisAbstractResourceModel::StorageId] = "storageId";
691 roles[Qt::UserRole + KisAbstractResourceModel::Name] = "name";
692 roles[Qt::UserRole + KisAbstractResourceModel::Filename] = "filename";
693 //roles[Qt::UserRole + Tooltip] = "tooltip";
694 roles[Qt::UserRole + KisAbstractResourceModel::Thumbnail] = "thumbnail";
695 roles[Qt::UserRole + KisAbstractResourceModel::Status] = "status";
696 roles[Qt::UserRole + KisAbstractResourceModel::Location] = "location";
697 roles[Qt::UserRole + KisAbstractResourceModel::ResourceType] = "resourcetype";
698 roles[Qt::UserRole + KisAbstractResourceModel::MD5] = "md5";
699 roles[Qt::UserRole + KisAbstractResourceModel::Tags] = "tags";
700 roles[Qt::UserRole + KisAbstractResourceModel::LargeThumbnail] = "largethumbnail";
701 roles[Qt::UserRole + KisAbstractResourceModel::Dirty] = "dirty";
702 roles[Qt::UserRole + KisAbstractResourceModel::MetaData] = "metadata";
703 roles[Qt::UserRole + KisAbstractResourceModel::ResourceActive] = "resourceactive";
704 roles[Qt::UserRole + KisAbstractResourceModel::StorageActive] = "storageactive";
705 roles[Qt::UserRole + KisAbstractResourceModel::BrokenStatus] = "brokenstatus";
706 roles[Qt::UserRole + KisAbstractResourceModel::BrokenStatusMessage] = "brokenstatusmessage";
707
708 return roles;
709}
710
712{
713 d->query.clear();
714}
715
717{
718 bool r = d->query.prepare(createQuery(true));
719
720 if (!r) {
721 qWarning() << "Could not prepare KisAllTagResourcesModel query" << d->query.lastError();
722 }
723
724 d->query.bindValue(":resource_type", d->resourceType);
725 d->query.bindValue(":language", KisTag::currentLocale());
726
727 r = d->query.exec();
728
729 if (!r) {
730 qWarning() << "Could not execute KisAllTagResourcesModel query" << d->query.lastError();
731 }
732
733 d->cachedRowCount = -1;
734
735 return r;
736}
737
738
748
749
750KisTagResourceModel::KisTagResourceModel(const QString &resourceType, QObject *parent)
751 : QSortFilterProxyModel(parent)
752 , d(new Private())
753{
754 d->resourceType = resourceType;
756 setSourceModel(d->sourceModel);
757
758 connect(KisResourceLocator::instance(), SIGNAL(storageAdded(const QString &)), this, SLOT(storageChanged(const QString &)));
759 connect(KisResourceLocator::instance(), SIGNAL(storageRemoved(const QString &)), this, SLOT(storageChanged(const QString &)));
760 connect(KisStorageModel::instance(), SIGNAL(storageEnabled(const QString &)), this, SLOT(storageChanged(const QString &)));
761 connect(KisStorageModel::instance(), SIGNAL(storageDisabled(const QString &)), this, SLOT(storageChanged(const QString &)));
762}
763
768
770{
771 d->tagFilter = filter;
772 invalidateFilter();
773}
774
776{
777 d->resourceFilter = filter;
778 invalidateFilter();
779}
780
782{
783 d->storageFilter = filter;
784 invalidateFilter();
785}
786
787bool KisTagResourceModel::tagResources(const KisTagSP tag, const QVector<int> &resourceIds)
788{
789 bool r = d->sourceModel->tagResources(tag, resourceIds);
790 return r;
791}
792
794{
795 return d->sourceModel->untagResources(tag, resourceIds);
796}
797
798int KisTagResourceModel::isResourceTagged(const KisTagSP tag, const int resourceId)
799{
800 return d->sourceModel->isResourceTagged(tag, resourceId);
801}
802
804{
805 d->tagIds = tagIds;
806 invalidateFilter();
807}
808
810{
811 d->resourceIds = resourceIds;
812 invalidateFilter();
813}
814
816{
817 d->tagIds.clear();
818 Q_FOREACH(const KisTagSP tag, tags) {
819 if (tag && tag->valid() && tag->id() > -1) {
820 d->tagIds << tag->id();
821 }
822 }
823 invalidateFilter();
824}
825
827{
828 d->resourceIds.clear();
829 Q_FOREACH(const KoResourceSP resource, resources) {
830 if (resource->valid() && resource->resourceId() > -1) {
831 d->resourceIds << resource->resourceId();
832 }
833 }
834 invalidateFilter();
835}
836
837bool KisTagResourceModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
838{
839 QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
840 if (!idx.isValid()) return false;
841
842 int tagId = idx.data(Qt::UserRole + KisAllTagResourceModel::TagId).toInt();
843 int resourceId = idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceId).toInt();
844 bool tagActive = idx.data(Qt::UserRole + KisAllTagResourceModel::TagActive).toBool();
845 bool resourceActive = idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceActive).toBool();
846 bool resourceStorageActive = idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceStorageActive).toBool();
847
849 return ((d->tagIds.contains(tagId) || d->tagIds.isEmpty()) &&
850 (d->resourceIds.contains(resourceId) || d->resourceIds.isEmpty()));
851 }
852
853 if ((d->tagFilter == ShowActiveTags && !tagActive)
854 || (d->tagFilter == ShowInactiveTags && tagActive)) {
855 return false;
856 }
857
858 if ((d->resourceFilter == ShowActiveResources && !resourceActive)
859 || (d->resourceFilter == ShowInactiveResources && resourceActive)) {
860 return false;
861 }
862
863 if ((d->storageFilter == ShowActiveStorages && !resourceStorageActive)
864 || (d->storageFilter == ShowInactiveStorages && resourceStorageActive)) {
865 return false;
866 }
867
868 return ((d->tagIds.contains(tagId) || d->tagIds.isEmpty())
869 && (d->resourceIds.contains(resourceId) || d->resourceIds.isEmpty()));
870}
871
872bool KisTagResourceModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
873{
874 QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisAllTagResourceModel::ResourceName).toString();
875 QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisAllTagResourceModel::ResourceName).toString();
876 return nameLeft.toLower() < nameRight.toLower();
877}
878
879void KisTagResourceModel::storageChanged(const QString &location)
880{
881 Q_UNUSED(location);
882 invalidateFilter();
883}
884
886{
887 int id = data(index, Qt::UserRole + KisAllTagResourceModel::ResourceId).toInt();
888 if (id < 1) return nullptr;
890 return res;
891}
892
894{
895 if (!resource || !resource->valid() || resource->resourceId() < 0) return QModelIndex();
896
897 for (int i = 0; i < rowCount(); ++i) {
898 QModelIndex idx = index(i, Qt::UserRole + KisAllTagResourceModel::ResourceId);
899 Q_ASSERT(idx.isValid());
900 if (idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceId).toInt() == resource->resourceId()) {
901 return idx;
902 }
903 }
904 return QModelIndex();
905}
906
907QModelIndex KisTagResourceModel::indexForResourceId(int resourceId) const
908{
909 if (resourceId < 0) return QModelIndex();
910 for (int i = 0; i < rowCount(); ++i) {
911 QModelIndex idx = index(i, Qt::UserRole + KisAllTagResourceModel::ResourceId);
912 Q_ASSERT(idx.isValid());
913 if (idx.data(Qt::UserRole + KisAllTagResourceModel::ResourceId).toInt() == resourceId) {
914 return idx;
915 }
916 }
917 return QModelIndex();
918}
919
920bool KisTagResourceModel::setResourceActive(const QModelIndex &index, bool value)
921{
922 KisResourceModel resourceModel(d->resourceType);
923 QModelIndex idx = resourceModel.indexForResource(resourceForIndex(index));
924 return resourceModel.setResourceActive(idx, value);
925}
926
927KoResourceSP KisTagResourceModel::importResourceFile(const QString &filename, const bool allowOverwrite, const QString &storageId)
928{
929 // Since we're importing the resource, there's no reason to add rows to the tags::resources table,
930 // because the resource is untagged.
931 KisResourceModel resourceModel(d->resourceType);
932 return resourceModel.importResourceFile(filename, allowOverwrite, storageId);
933}
934
935KoResourceSP KisTagResourceModel::importResource(const QString &filename, QIODevice *device, const bool allowOverwrite, const QString &storageId)
936{
937 // Since we're importing the resource, there's no reason to add rows to the tags::resources table,
938 // because the resource is untagged.
939 KisResourceModel resourceModel(d->resourceType);
940 return resourceModel.importResource(filename, device, allowOverwrite, storageId);
941}
942
943bool KisTagResourceModel::importWillOverwriteResource(const QString &fileName, const QString &storageLocation) const
944{
945 KisResourceModel resourceModel(d->resourceType);
946 return resourceModel.importWillOverwriteResource(fileName, storageLocation);
947}
948
949bool KisTagResourceModel::exportResource(KoResourceSP resource, QIODevice *device)
950{
951 KisResourceModel resourceModel(d->resourceType);
952 return resourceModel.exportResource(resource, device);
953}
954
955bool KisTagResourceModel::addResource(KoResourceSP resource, const QString &storageId)
956{
957 // Since we're importing the resource, there's no reason to add rows to the tags::resources table,
958 // because the resource is untagged.
959 KisResourceModel resourceModel(d->resourceType);
960 return resourceModel.addResource(resource, storageId);
961}
962
964{
965 // Since we're importing the resource, there's no reason to add rows to the tags::resources table,
966 // because the resource is untagged.
967 KisResourceModel resourceModel(d->resourceType);
968 return resourceModel.addResourceDeduplicateFileName(resource, storageId);
969}
970
971
973{
974 KisResourceModel resourceModel(d->resourceType);
975 bool r = resourceModel.updateResource(resource);
976 if (r) {
977 QModelIndex index = indexForResource(resource);
978 if (index.isValid()) {
979 Q_EMIT dataChanged(index, index, {Qt::EditRole});
980 }
981 }
982 return r;
983}
984
986{
987 KisResourceModel resourceModel(d->resourceType);
988 bool r = resourceModel.reloadResource(resource);
989 if (r) {
990 QModelIndex index = indexForResource(resource);
991 if (index.isValid()) {
992 Q_EMIT dataChanged(index, index, {Qt::EditRole});
993 }
994 }
995 return r;
996}
997
998bool KisTagResourceModel::renameResource(KoResourceSP resource, const QString &name)
999{
1000 KisResourceModel resourceModel(d->resourceType);
1001 bool r = resourceModel.renameResource(resource, name);
1002 if (r) {
1003 QModelIndex index = indexForResource(resource);
1004 if (index.isValid()) {
1005 Q_EMIT dataChanged(index, index, {Qt::EditRole});
1006 }
1007 }
1008 return r;
1009}
1010
1011bool KisTagResourceModel::setResourceMetaData(KoResourceSP resource, QMap<QString, QVariant> metadata)
1012{
1013 KisResourceModel resourceModel(d->resourceType);
1014 return resourceModel.setResourceMetaData(resource, metadata);
1015}
1016
1017QHash<int, QByteArray> KisTagResourceModel::roleNames() const
1018{
1019 if (sourceModel()) {
1020 return sourceModel()->roleNames();
1021 }
1022 return QAbstractItemModel::roleNames();
1023}
1024
1025QVariant KisTagResourceModel::headerData(int section, Qt::Orientation orientation, int role) const
1026{
1027 return d->sourceModel->headerData(section, orientation, role);
1028}
float value(const T *src, size_t ch)
qreal v
@ Status
Whether the resource is active. Duplicate of ResourceActive.
@ ResourceActive
Whether the current resource is active.
@ Dirty
A dirty resource is one that has been modified locally but not saved.
@ LargeThumbnail
A larger thumbnail for displaying in a tooltip. 200x200 or so.
@ MetaData
MetaData is a map of key, value pairs that is associated with this resource.
@ BrokenStatus
Whether the resource is broken (bool)
@ StorageActive
Whether the current resource's storage is active.
KisAllTagResourceModel(const QString &resourceType, QObject *parent=0)
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
void removeStorage(const QString &location)
QHash< int, QByteArray > roleNames() const override
int rowCount(const QModelIndex &parent=QModelIndex()) const override
int isResourceTagged(const KisTagSP tag, const int resourceId) override
isResourceTagged
void slotResourceActiveStateChanged(const QString &resourceType, int resourceId)
int columnCount(const QModelIndex &parent=QModelIndex()) const override
void addStorage(const QString &location)
QString createQuery(bool onlyAchieve=true, bool returnADbIndexToo=false)
bool tagResources(const KisTagSP tag, const QVector< int > &resourceIds) override
bool untagResources(const KisTagSP tag, const QVector< int > &resourceId) override
QVariant data(const QModelIndex &index, int role) const override
Note: only role is significant, column is not.
KisTagSP tagForUrl(const QString &tagUrl, const QString resourceType)
tagForUrl create a tag from the database
KoResourceSP resourceForId(int resourceId)
resourceForId returns the resource with the given id, or 0 if no such resource exists....
static KisResourceLocator * instance()
static KisAllTagResourceModel * tagResourceModel(const QString &resourceType)
The KisResourceModel class provides the main access to resources. It is possible to filter the resour...
bool setResourceMetaData(KoResourceSP resource, QMap< QString, QVariant > metadata) override
setResourceMetaData
KoResourceSP importResourceFile(const QString &filename, const bool allowOverwrite, const QString &storageId=QString("")) override
importResourceFile
bool importWillOverwriteResource(const QString &fileName, const QString &storageLocation=QString()) const override
importWillOverwriteResource checks is importing a resource with this filename will overwrite anything
bool reloadResource(KoResourceSP resource) override
reloadResource
bool renameResource(KoResourceSP resource, const QString &name) override
renameResource name the given resource. The resource will have its name field reset,...
bool addResourceDeduplicateFileName(KoResourceSP resource, const QString &storageId=QString("")) override
addResource adds the given resource to the database and storage. If the resource already exists in th...
bool exportResource(KoResourceSP resource, QIODevice *device) override
exportResource exports a resource into a QIODevice
bool updateResource(KoResourceSP resource) override
updateResource creates a new version of the resource in the storage and in the database....
bool addResource(KoResourceSP resource, const QString &storageId=QString("")) override
addResource adds the given resource to the database and storage. If the resource already exists in th...
QModelIndex indexForResource(KoResourceSP resource) const override
indexFromResource
bool setResourceActive(const QModelIndex &index, bool value) override
setResourceActive changes 'active' state of the resource
KoResourceSP importResource(const QString &filename, QIODevice *device, const bool allowOverwrite, const QString &storageId=QString("")) override
importResource imports a resource from a QIODevice
static QVariant variantFromResourceQuery(const QSqlQuery &query, int column, int role, bool useResourcePrefix)
variantFromResourceQuery returns a QVariant for the given column and or role
static KisStorageModel * instance()
bool setResourceMetaData(KoResourceSP resource, QMap< QString, QVariant > metadata) override
setResourceMetaData
void setResourceFilter(ResourceFilter filter) override
QModelIndex indexForResourceId(int resourceId) const override
indexFromResource
bool untagResources(const KisTagSP tag, const QVector< int > &resourceIds) override
bool setResourceActive(const QModelIndex &index, bool value) override
setResourceActive changes 'active' state of the resource
KoResourceSP importResource(const QString &filename, QIODevice *device, const bool allowOverwrite, const QString &storageId=QString()) override
importResource imports a resource from a QIODevice
bool addResource(KoResourceSP resource, const QString &storageId) override
addResource adds the given resource to the database and storage. If the resource already exists in th...
void storageChanged(const QString &location)
void setTagFilter(TagFilter filter)
bool addResourceDeduplicateFileName(KoResourceSP resource, const QString &storageId) override
addResource adds the given resource to the database and storage. If the resource already exists in th...
KisTagResourceModel(const QString &resourceType, QObject *parent=0)
bool tagResources(const KisTagSP tag, const QVector< int > &resourceIds) override
void setTagsFilter(const QVector< int > tagIds)
QModelIndex indexForResource(KoResourceSP resource) const override
indexFromResource
void setResourcesFilter(const QVector< int > resourceIds)
bool renameResource(KoResourceSP resource, const QString &name) override
renameResource name the given resource. The resource will have its name field reset,...
KoResourceSP importResourceFile(const QString &filename, const bool allowOverwrite, const QString &storageId=QString()) override
importResourceFile
bool reloadResource(KoResourceSP resource) override
reloadResource
int isResourceTagged(const KisTagSP tag, const int resourceId) override
isResourceTagged
bool updateResource(KoResourceSP resource) override
updateResource creates a new version of the resource in the storage and in the database....
QHash< int, QByteArray > roleNames() const override
bool importWillOverwriteResource(const QString &fileName, const QString &storageLocation) const override
importWillOverwriteResource checks is importing a resource with this filename will overwrite anything
KoResourceSP resourceForIndex(QModelIndex index) const override
resourceForIndex returns a properly versioned and id'ed resource object
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
bool exportResource(KoResourceSP resource, QIODevice *device) override
exportResource exports a resource into a QIODevice
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
void setStorageFilter(StorageFilter filter) override
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override
static QString currentLocale()
Definition KisTag.cpp:84
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
KisAllTagResourceModel * sourceModel