1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2004-06-15
7 * Description : digiKam album types
8 *
9 * Copyright (C) 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com>
10 * Copyright (C) 2006-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11 * Copyright (C) 2014-2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
12 *
13 * This program is free software; you can redistribute it
14 * and/or modify it under the terms of the GNU General
15 * Public License as published by the Free Software Foundation;
16 * either version 2, or (at your option)
17 * any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * ============================================================ */
25
26 #include "album.h"
27
28 // KDE includes
29
30 #include <klocalizedstring.h>
31
32 // Local includes
33
34 #include "digikam_debug.h"
35 #include "coredb.h"
36 #include "albummanager.h"
37 #include "collectionmanager.h"
38 #include "coredbaccess.h"
39 #include "coredburl.h"
40 #include "tagscache.h"
41
42 namespace Digikam
43 {
44
Album(Album::Type type,int id,bool root)45 Album::Album(Album::Type type, int id, bool root)
46 : m_root(root),
47 m_usedByLabelsTree(false),
48 m_id(id),
49 m_type(type),
50 m_parent(nullptr)
51 {
52 }
53
~Album()54 Album::~Album()
55 {
56 if (m_parent)
57 {
58 m_parent->removeChild(this);
59 }
60
61 clear();
62 AlbumManager::internalInstance->notifyAlbumDeletion(this);
63 }
64
setParent(Album * const parent)65 void Album::setParent(Album* const parent)
66 {
67 if (parent)
68 {
69 m_parent = parent;
70 parent->insertChild(this);
71 }
72 }
73
parent() const74 Album* Album::parent() const
75 {
76 return m_parent;
77 }
78
firstChild() const79 Album* Album::firstChild() const
80 {
81 if (m_childCache.isEmpty())
82 {
83 return nullptr;
84 }
85
86 return m_childCache.constFirst();
87 }
88
lastChild() const89 Album* Album::lastChild() const
90 {
91 if (m_childCache.isEmpty())
92 {
93 return nullptr;
94 }
95
96 return m_childCache.constLast();
97 }
98
next() const99 Album* Album::next() const
100 {
101 if (!m_parent)
102 {
103 return nullptr;
104 }
105
106 int row = m_parent->m_childCache.indexOf(const_cast<Album*>(this));
107
108 if ((row < 0) || ((row + 1) >= m_parent->m_childCache.count()))
109 {
110 return nullptr;
111 }
112
113 return m_parent->m_childCache.at(row + 1);
114 }
115
prev() const116 Album* Album::prev() const
117 {
118 if (!m_parent)
119 {
120 return nullptr;
121 }
122
123 int row = m_parent->m_childCache.indexOf(const_cast<Album*>(this));
124
125 if (row < 1)
126 {
127 return nullptr;
128 }
129
130 return m_parent->m_childCache.at(row - 1);
131 }
132
childAtRow(int row) const133 Album* Album::childAtRow(int row) const
134 {
135 return m_childCache.value(row, nullptr);
136 }
137
childAlbums(bool recursive)138 AlbumList Album::childAlbums(bool recursive)
139 {
140 AlbumList childList;
141
142 QVector<Album*>::const_iterator it = m_childCache.constBegin();
143
144 while (it != m_childCache.constEnd())
145 {
146 childList << (*it);
147
148 if (recursive)
149 {
150 childList << (*it)->childAlbums(recursive);
151 }
152
153 ++it;
154 }
155
156 return childList;
157 }
158
childAlbumIds(bool recursive)159 QList<int> Album::childAlbumIds(bool recursive)
160 {
161 QList<int> ids;
162
163 AlbumList childList = childAlbums(recursive);
164
165 QListIterator<Album*> it(childList);
166
167 while (it.hasNext())
168 {
169 ids << it.next()->id();
170 }
171
172 return ids;
173 }
174
insertChild(Album * const child)175 void Album::insertChild(Album* const child)
176 {
177 if (!child)
178 {
179 return;
180 }
181
182 m_childCache.append(child);
183 }
184
removeChild(Album * const child)185 void Album::removeChild(Album* const child)
186 {
187 if (!child)
188 {
189 return;
190 }
191
192 m_childCache.removeOne(child);
193 }
194
clear()195 void Album::clear()
196 {
197 while (!m_childCache.isEmpty())
198 {
199 delete m_childCache.takeFirst();
200 }
201 }
202
globalID() const203 int Album::globalID() const
204 {
205 return globalID(m_type, m_id);
206 }
207
globalID(Type type,int id)208 int Album::globalID(Type type, int id)
209 {
210 switch (type)
211 {
212 // Use the upper bits to create unique ids.
213 case (PHYSICAL):
214 return id;
215
216 case (TAG):
217 return (id | (1 << 27));
218
219 case (DATE):
220 return (id | (1 << 28));
221
222 case (SEARCH):
223 return (id | (1 << 29));
224
225 case (FACE):
226 return (id | (1 << 30));
227
228 default:
229 qCDebug(DIGIKAM_GENERAL_LOG) << "Unknown album type";
230 return -1;
231 }
232 }
233
id() const234 int Album::id() const
235 {
236 return m_id;
237 }
238
childCount() const239 int Album::childCount() const
240 {
241 return m_childCache.count();
242 }
243
rowFromAlbum() const244 int Album::rowFromAlbum() const
245 {
246 if (!m_parent)
247 {
248 return 0;
249 }
250
251 int row = m_parent->m_childCache.indexOf(const_cast<Album*>(this));
252
253 return ((row != -1) ? row : 0);
254 }
255
setTitle(const QString & title)256 void Album::setTitle(const QString& title)
257 {
258 m_title = title;
259 }
260
title() const261 QString Album::title() const
262 {
263 return m_title;
264 }
265
type() const266 Album::Type Album::type() const
267 {
268 return m_type;
269 }
270
setExtraData(const void * const key,void * const value)271 void Album::setExtraData(const void* const key, void* const value)
272 {
273 m_extraMap.insert(key, value);
274 }
275
removeExtraData(const void * const key)276 void Album::removeExtraData(const void* const key)
277 {
278 m_extraMap.remove(key);
279 }
280
extraData(const void * const key) const281 void* Album::extraData(const void* const key) const
282 {
283 return m_extraMap.value(key, nullptr);
284 }
285
isRoot() const286 bool Album::isRoot() const
287 {
288 return m_root;
289 }
290
isAncestorOf(Album * const album) const291 bool Album::isAncestorOf(Album* const album) const
292 {
293 bool val = false;
294 Album* a = album;
295
296 while (a && !a->isRoot())
297 {
298 if (a == this)
299 {
300 val = true;
301 break;
302 }
303
304 a = a->parent();
305 }
306
307 return val;
308 }
309
isUsedByLabelsTree() const310 bool Album::isUsedByLabelsTree() const
311 {
312 return m_usedByLabelsTree;
313 }
314
isTrashAlbum() const315 bool Album::isTrashAlbum() const
316 {
317 if ((m_id < -1) && (m_type == PHYSICAL))
318 {
319 return true;
320 }
321
322 return false;
323 }
324
setUsedByLabelsTree(bool isUsed)325 void Album::setUsedByLabelsTree(bool isUsed)
326 {
327 m_usedByLabelsTree = isUsed;
328 }
329
330 // ------------------------------------------------------------------------------
331
332 int PAlbum::m_uniqueTrashId = -2;
333
PAlbum(const QString & title)334 PAlbum::PAlbum(const QString& title)
335 : Album(Album::PHYSICAL, 0, true),
336 m_isAlbumRootAlbum(false),
337 m_albumRootId(-1),
338 m_parentPath(QLatin1Char('/')),
339 m_iconId(0)
340 {
341 setTitle(title);
342 m_path.clear();
343 }
344
PAlbum(int albumRoot,const QString & label)345 PAlbum::PAlbum(int albumRoot, const QString& label)
346 : Album(Album::PHYSICAL, -1, false),
347 m_isAlbumRootAlbum(true),
348 m_albumRootId(albumRoot),
349 m_parentPath(QLatin1Char('/')),
350 m_iconId(0)
351 {
352 // set the id to -1 (line above). AlbumManager may change that later.
353 setTitle(label);
354 m_path.clear();
355 }
356
PAlbum(int albumRoot,const QString & parentPath,const QString & title,int id)357 PAlbum::PAlbum(int albumRoot, const QString& parentPath, const QString& title, int id)
358 : Album(Album::PHYSICAL, id, false),
359 m_isAlbumRootAlbum(false),
360 m_albumRootId(albumRoot),
361 m_path(title),
362 m_parentPath(parentPath + QLatin1Char('/')),
363 m_iconId(0),
364 m_date(QDate::currentDate())
365 {
366 // If path is /holidays/2007, title is only "2007", path is "/holidays"
367 setTitle(title);
368 }
369
PAlbum(const QString & parentPath,int albumRoot)370 PAlbum::PAlbum(const QString& parentPath, int albumRoot)
371 : Album(Album::PHYSICAL, m_uniqueTrashId--, false),
372 m_isAlbumRootAlbum(false),
373 m_albumRootId(albumRoot),
374 m_path(QLatin1String("Trash")),
375 m_parentPath(parentPath + QLatin1Char('/')),
376 m_iconId(0)
377 {
378 setTitle(i18n("Trash"));
379 }
380
~PAlbum()381 PAlbum::~PAlbum()
382 {
383 }
384
isAlbumRoot() const385 bool PAlbum::isAlbumRoot() const
386 {
387 return m_isAlbumRootAlbum;
388 }
389
setCaption(const QString & caption)390 void PAlbum::setCaption(const QString& caption)
391 {
392 m_caption = caption;
393
394 CoreDbAccess access;
395 access.db()->setAlbumCaption(id(), m_caption);
396 }
397
setCategory(const QString & category)398 void PAlbum::setCategory(const QString& category)
399 {
400 m_category = category;
401
402 CoreDbAccess access;
403 access.db()->setAlbumCategory(id(), m_category);
404 }
405
setDate(const QDate & date)406 void PAlbum::setDate(const QDate& date)
407 {
408 m_date = date;
409
410 CoreDbAccess access;
411 access.db()->setAlbumDate(id(), m_date);
412 }
413
albumRootPath() const414 QString PAlbum::albumRootPath() const
415 {
416 return CollectionManager::instance()->albumRootPath(m_albumRootId);
417 }
418
albumRootLabel() const419 QString PAlbum::albumRootLabel() const
420 {
421 return CollectionManager::instance()->albumRootLabel(m_albumRootId);
422 }
423
albumRootId() const424 int PAlbum::albumRootId() const
425 {
426 return m_albumRootId;
427 }
428
caption() const429 QString PAlbum::caption() const
430 {
431 return m_caption;
432 }
433
category() const434 QString PAlbum::category() const
435 {
436 return m_category;
437 }
438
date() const439 QDate PAlbum::date() const
440 {
441 return m_date;
442 }
443
albumPath() const444 QString PAlbum::albumPath() const
445 {
446 return (m_parentPath + m_path);
447 }
448
databaseUrl() const449 CoreDbUrl PAlbum::databaseUrl() const
450 {
451 return CoreDbUrl::fromAlbumAndName(QString(), albumPath(),
452 QUrl::fromLocalFile(albumRootPath()), m_albumRootId);
453 }
454
prettyUrl() const455 QString PAlbum::prettyUrl() const
456 {
457 QString u = i18n("Albums") + QLatin1Char('/') +
458 albumRootLabel() +
459 albumPath();
460
461 if (u.endsWith(QLatin1Char('/')))
462 {
463 u.chop(1);
464 }
465
466 return u;
467 }
468
iconId() const469 qlonglong PAlbum::iconId() const
470 {
471 return m_iconId;
472 }
473
fileUrl() const474 QUrl PAlbum::fileUrl() const
475 {
476 return databaseUrl().fileUrl();
477 }
478
folderPath() const479 QString PAlbum::folderPath() const
480 {
481 return databaseUrl().fileUrl().toLocalFile();
482 }
483
484 // --------------------------------------------------------------------------
485
TAlbum(const QString & title,int id,bool root)486 TAlbum::TAlbum(const QString& title, int id, bool root)
487 : Album(Album::TAG, id, root),
488 m_pid(0),
489 m_iconId(0)
490 {
491 setTitle(title);
492 }
493
~TAlbum()494 TAlbum::~TAlbum()
495 {
496 }
497
tagPath(bool leadingSlash) const498 QString TAlbum::tagPath(bool leadingSlash) const
499 {
500 if (isRoot())
501 {
502 return (leadingSlash ? QLatin1String("/") : QLatin1String(""));
503 }
504
505 QString u;
506
507 if (parent())
508 {
509 u = (static_cast<TAlbum*>(parent()))->tagPath(leadingSlash);
510
511 if (!parent()->isRoot())
512 {
513 u += QLatin1Char('/');
514 }
515 }
516
517 u += title();
518
519 return u;
520 }
521
standardIconName() const522 QString TAlbum::standardIconName() const
523 {
524 return (hasProperty(TagPropertyName::person()) ? QLatin1String("smiley")
525 : QLatin1String("tag"));
526 }
527
prettyUrl() const528 QString TAlbum::prettyUrl() const
529 {
530 return (i18n("Tags") + tagPath(true));
531 }
532
databaseUrl() const533 CoreDbUrl TAlbum::databaseUrl() const
534 {
535 return CoreDbUrl::fromTagIds(tagIDs());
536 }
537
tagIDs() const538 QList<int> TAlbum::tagIDs() const
539 {
540 if (isRoot())
541 {
542 return QList<int>();
543 }
544 else if (parent())
545 {
546 return (static_cast<TAlbum*>(parent())->tagIDs() << id());
547 }
548 else
549 {
550 return (QList<int>() << id());
551 }
552 }
553
icon() const554 QString TAlbum::icon() const
555 {
556 return m_icon;
557 }
558
isInternalTag() const559 bool TAlbum::isInternalTag() const
560 {
561 return TagsCache::instance()->isInternalTag(id());
562 }
563
iconId() const564 qlonglong TAlbum::iconId() const
565 {
566 return m_iconId;
567 }
568
hasProperty(const QString & key) const569 bool TAlbum::hasProperty(const QString& key) const
570 {
571 return TagsCache::instance()->hasProperty(id(), key);
572 }
573
property(const QString & key) const574 QString TAlbum::property(const QString& key) const
575 {
576 return TagsCache::instance()->propertyValue(id(), key);
577 }
578
properties() const579 QMap<QString, QString> TAlbum::properties() const
580 {
581 return TagsCache::instance()->properties(id());
582 }
583
584 // --------------------------------------------------------------------------
585
586 int DAlbum::m_uniqueID = 0;
587
DAlbum(const QDate & date,bool root,Range range)588 DAlbum::DAlbum(const QDate& date, bool root, Range range)
589 : Album(Album::DATE, root ? 0 : ++m_uniqueID, root),
590 m_date(date),
591 m_range(range)
592 {
593 // Set the name of the date album
594
595 QString dateTitle;
596
597 if (m_range == Month)
598 {
599 dateTitle = m_date.toString(QLatin1String("MMMM yyyy"));
600 }
601 else
602 {
603 dateTitle = m_date.toString(QLatin1String("yyyy"));
604 }
605
606 setTitle(dateTitle);
607 }
608
~DAlbum()609 DAlbum::~DAlbum()
610 {
611 }
612
date() const613 QDate DAlbum::date() const
614 {
615 return m_date;
616 }
617
range() const618 DAlbum::Range DAlbum::range() const
619 {
620 return m_range;
621 }
622
databaseUrl() const623 CoreDbUrl DAlbum::databaseUrl() const
624 {
625 if (m_range == Month)
626 {
627 return CoreDbUrl::fromDateForMonth(m_date);
628 }
629
630 return CoreDbUrl::fromDateForYear(m_date);
631 }
632
633 // --------------------------------------------------------------------------
634
SAlbum(const QString & title,int id,bool root)635 SAlbum::SAlbum(const QString& title, int id, bool root)
636 : Album(Album::SEARCH, id, root),
637 m_searchType(DatabaseSearch::UndefinedType)
638 {
639 setTitle(title);
640 }
641
~SAlbum()642 SAlbum::~SAlbum()
643 {
644 }
645
setSearch(DatabaseSearch::Type type,const QString & query)646 void SAlbum::setSearch(DatabaseSearch::Type type, const QString& query)
647 {
648 m_searchType = type;
649 m_query = query;
650 }
651
databaseUrl() const652 CoreDbUrl SAlbum::databaseUrl() const
653 {
654 return CoreDbUrl::searchUrl(id());
655 }
656
query() const657 QString SAlbum::query() const
658 {
659 return m_query;
660 }
661
searchType() const662 DatabaseSearch::Type SAlbum::searchType() const
663 {
664 return m_searchType;
665 }
666
isNormalSearch() const667 bool SAlbum::isNormalSearch() const
668 {
669 switch (m_searchType)
670 {
671 case DatabaseSearch::KeywordSearch:
672 case DatabaseSearch::AdvancedSearch:
673 case DatabaseSearch::LegacyUrlSearch:
674 return true;
675
676 default:
677 return false;
678 }
679 }
680
isAdvancedSearch() const681 bool SAlbum::isAdvancedSearch() const
682 {
683 return (m_searchType == DatabaseSearch::AdvancedSearch);
684 }
685
isKeywordSearch() const686 bool SAlbum::isKeywordSearch() const
687 {
688 return (m_searchType == DatabaseSearch::KeywordSearch);
689 }
690
isTimelineSearch() const691 bool SAlbum::isTimelineSearch() const
692 {
693 return (m_searchType == DatabaseSearch::TimeLineSearch);
694 }
695
isHaarSearch() const696 bool SAlbum::isHaarSearch() const
697 {
698 return (m_searchType == DatabaseSearch::HaarSearch);
699 }
700
isMapSearch() const701 bool SAlbum::isMapSearch() const
702 {
703 return (m_searchType == DatabaseSearch::MapSearch);
704 }
705
isDuplicatesSearch() const706 bool SAlbum::isDuplicatesSearch() const
707 {
708 return (m_searchType == DatabaseSearch::DuplicatesSearch);
709 }
710
isTemporarySearch() const711 bool SAlbum::isTemporarySearch() const
712 {
713 if (isHaarSearch())
714 {
715 return ((title() == getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch))) ||
716 (title() == getTemporaryHaarTitle(DatabaseSearch::HaarSketchSearch));
717 }
718
719 return (title() == getTemporaryTitle(m_searchType));
720 }
721
displayTitle() const722 QString SAlbum::displayTitle() const
723 {
724 if (isTemporarySearch())
725 {
726 switch (m_searchType)
727 {
728 case DatabaseSearch::TimeLineSearch:
729 return i18n("Current Timeline Search");
730
731 case DatabaseSearch::HaarSearch:
732 {
733 if (title() == getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch))
734 {
735 return i18n("Current Fuzzy Image Search");
736 }
737 else if (title() == getTemporaryHaarTitle(DatabaseSearch::HaarSketchSearch))
738 {
739 return i18n("Current Fuzzy Sketch Search");
740 }
741
742 break;
743 }
744
745 case DatabaseSearch::MapSearch:
746 return i18n("Current Map Search");
747
748 case DatabaseSearch::KeywordSearch:
749 case DatabaseSearch::AdvancedSearch:
750 case DatabaseSearch::LegacyUrlSearch:
751 return i18n("Current Search");
752
753 case DatabaseSearch::DuplicatesSearch:
754 return i18n("Current Duplicates Search");
755
756 case DatabaseSearch::UndefinedType:
757 break;
758 }
759 }
760
761 return title();
762 }
763
getTemporaryTitle(DatabaseSearch::Type type,DatabaseSearch::HaarSearchType haarType)764 QString SAlbum::getTemporaryTitle(DatabaseSearch::Type type, DatabaseSearch::HaarSearchType haarType)
765 {
766 switch (type)
767 {
768 case DatabaseSearch::TimeLineSearch:
769 return QLatin1String("_Current_Time_Line_Search_");
770
771 case DatabaseSearch::HaarSearch:
772 return getTemporaryHaarTitle(haarType);
773
774 case DatabaseSearch::MapSearch:
775 return QLatin1String("_Current_Map_Search_");
776
777 case DatabaseSearch::KeywordSearch:
778 case DatabaseSearch::AdvancedSearch:
779 case DatabaseSearch::LegacyUrlSearch:
780 return QLatin1String("_Current_Search_View_Search_");
781
782 case DatabaseSearch::DuplicatesSearch:
783 return QLatin1String("_Current_Duplicates_Search_");
784
785 default:
786 qCDebug(DIGIKAM_GENERAL_LOG) << "Untreated temporary search type " << type;
787 return QLatin1String("_Current_Unknown_Search_");
788 }
789 }
790
getTemporaryHaarTitle(DatabaseSearch::HaarSearchType haarType)791 QString SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarSearchType haarType)
792 {
793 switch (haarType)
794 {
795 case DatabaseSearch::HaarImageSearch:
796 return QLatin1String("_Current_Fuzzy_Image_Search_");
797
798 case DatabaseSearch::HaarSketchSearch:
799 return QLatin1String("_Current_Fuzzy_Sketch_Search_");
800
801 default:
802 qCDebug(DIGIKAM_GENERAL_LOG) << "Untreated temporary haar search type " << haarType;
803 return QLatin1String("_Current_Unknown_Haar_Search_");
804 }
805 }
806
807 // --------------------------------------------------------------------------
808
AlbumIterator(Album * const album)809 AlbumIterator::AlbumIterator(Album* const album)
810 : m_current(album ? album->firstChild() : nullptr),
811 m_root(album)
812 {
813 }
814
~AlbumIterator()815 AlbumIterator::~AlbumIterator()
816 {
817 }
818
operator ++()819 AlbumIterator& AlbumIterator::operator++()
820 {
821 if (!m_current)
822 {
823 return *this;
824 }
825
826 Album* album = m_current->firstChild();
827
828 if (!album)
829 {
830 while ((album = m_current->next()) == nullptr)
831 {
832 m_current = m_current->parent();
833
834 if (m_current == m_root)
835 {
836 // we have reached the root.
837 // that means no more children
838
839 m_current = nullptr;
840 break;
841 }
842
843 if (m_current == nullptr)
844 {
845 break;
846 }
847 }
848 }
849
850 m_current = album;
851
852 return *this;
853 }
854
operator *()855 Album* AlbumIterator::operator*()
856 {
857 return m_current;
858 }
859
current() const860 Album* AlbumIterator::current() const
861 {
862 return m_current;
863 }
864
865 } // namespace Digikam
866