1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2007-06-05
7  * Description : Thumbnail loading
8  *
9  * Copyright (C) 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10  * Copyright (C) 2005-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11  * Copyright (C) 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 "thumbnailloadthread_p.h"
27 
28 namespace Digikam
29 {
30 
Q_GLOBAL_STATIC(ThumbnailLoadThreadStaticPriv,static_d)31 Q_GLOBAL_STATIC(ThumbnailLoadThreadStaticPriv, static_d)
32 Q_GLOBAL_STATIC(ThumbnailLoadThread,           defaultObject)
33 Q_GLOBAL_STATIC(ThumbnailLoadThread,           defaultIconViewObject)
34 
35 // --- Creating loading descriptions ---
36 
37 LoadingDescription ThumbnailLoadThread::Private::createLoadingDescription(const ThumbnailIdentifier& identifier,
38                                                                           int size,
39                                                                           bool setLastDescription)
40 {
41     size = thumbnailSizeForPixmapSize(size);
42 
43     LoadingDescription description(identifier.filePath, PreviewSettings(), size,
44                                    LoadingDescription::NoColorConversion,
45                                    LoadingDescription::PreviewParameters::Thumbnail);
46     description.previewParameters.storageReference = identifier.id;
47 
48     if (IccSettings::instance()->useManagedPreviews())
49     {
50         description.postProcessingParameters.colorManagement = LoadingDescription::ConvertForDisplay;
51         description.postProcessingParameters.setProfile(static_d->profile);
52     }
53 
54     if (setLastDescription)
55     {
56         lastDescriptions.clear();
57         lastDescriptions << description;
58     }
59 
60     return description;
61 }
62 
createLoadingDescription(const ThumbnailIdentifier & identifier,int size,const QRect & detailRect,bool setLastDescription)63 LoadingDescription ThumbnailLoadThread::Private::createLoadingDescription(const ThumbnailIdentifier& identifier,
64                                                                           int size,
65                                                                           const QRect& detailRect,
66                                                                           bool setLastDescription)
67 {
68     size                                          = thumbnailSizeForPixmapSize(size);
69 
70     LoadingDescription description(identifier.filePath, PreviewSettings(), size,
71                                    LoadingDescription::NoColorConversion,
72                                    LoadingDescription::PreviewParameters::DetailThumbnail);
73 
74     description.previewParameters.storageReference = identifier.id;
75     description.previewParameters.extraParameter   = detailRect;
76 
77     if (IccSettings::instance()->useManagedPreviews())
78     {
79         description.postProcessingParameters.colorManagement = LoadingDescription::ConvertForDisplay;
80         description.postProcessingParameters.setProfile(static_d->profile);
81     }
82 
83     if (setLastDescription)
84     {
85         lastDescriptions.clear();
86         lastDescriptions << description;
87     }
88 
89     return description;
90 }
91 
92 // ---------------------------------------------------------------------------
93 
ThumbnailLoadThread(QObject * const parent)94 ThumbnailLoadThread::ThumbnailLoadThread(QObject* const parent)
95     : ManagedLoadSaveThread(parent),
96       d                    (new Private)
97 {
98     static_d->firstThreadCreated = true;
99     d->creator                   = new ThumbnailCreator(static_d->storageMethod);
100 
101     if (static_d->provider)
102     {
103         d->creator->setThumbnailInfoProvider(static_d->provider);
104     }
105 
106     d->creator->setOnlyLargeThumbnails(true);
107     d->creator->setRemoveAlphaChannel(true);
108 
109     connect(this, SIGNAL(thumbnailsAvailable()),
110             this, SLOT(slotThumbnailsAvailable()));
111 }
112 
~ThumbnailLoadThread()113 ThumbnailLoadThread::~ThumbnailLoadThread()
114 {
115     shutDown();
116 
117     delete d->creator;
118     delete d;
119 }
120 
defaultIconViewThread()121 ThumbnailLoadThread* ThumbnailLoadThread::defaultIconViewThread()
122 {
123     return defaultIconViewObject;
124 }
125 
defaultThread()126 ThumbnailLoadThread* ThumbnailLoadThread::defaultThread()
127 {
128     return defaultObject;
129 }
130 
cleanUp()131 void ThumbnailLoadThread::cleanUp()
132 {
133     // NOTE: Nothing to do with Qt5 and Q_GLOBAL_STATIC. Qt clean up all automatically at end of application instance.
134     // But stopping all running tasks to prevent a crash at end.
135 
136     defaultIconViewThread()->stopAllTasks();
137     defaultThread()->stopAllTasks();
138 
139     defaultIconViewThread()->wait();
140     defaultThread()->wait();
141 }
142 
initializeThumbnailDatabase(const DbEngineParameters & params,ThumbnailInfoProvider * const provider)143 void ThumbnailLoadThread::initializeThumbnailDatabase(const DbEngineParameters& params, ThumbnailInfoProvider* const provider)
144 {
145     if (static_d->firstThreadCreated)
146     {
147         qCDebug(DIGIKAM_GENERAL_LOG) << "Call initializeThumbnailDatabase at application start. "
148                                         "There are already thumbnail loading threads created, "
149                                         "and these will not be switched to use the database. ";
150     }
151 
152     ThumbsDbAccess::setParameters(params);
153 
154     if (ThumbsDbAccess::checkReadyForUse(nullptr))
155     {
156         qCDebug(DIGIKAM_GENERAL_LOG) << "Thumbnails database ready for use";
157         static_d->storageMethod = ThumbnailCreator::ThumbnailDatabase;
158         static_d->provider      = provider;
159     }
160     else
161     {
162         QMessageBox::information(qApp->activeWindow(), i18n("Failed to initialize thumbnails database"),
163                                  i18n("Error message: %1", ThumbsDbAccess().lastError()));
164     }
165 }
166 
setDisplayingWidget(QWidget * const widget)167 void ThumbnailLoadThread::setDisplayingWidget(QWidget* const widget)
168 {
169     static_d->profile = IccManager::displayProfile(widget);
170 }
171 
setThumbnailSize(int size,bool forFace)172 void ThumbnailLoadThread::setThumbnailSize(int size, bool forFace)
173 {
174     d->size = size;
175 
176     if (forFace)
177     {
178         d->creator->setThumbnailSize(size);
179     }
180 }
181 
maximumThumbnailSize()182 int ThumbnailLoadThread::maximumThumbnailSize()
183 {
184     return ThumbnailSize::maxThumbsSize();
185 }
186 
maximumThumbnailPixmapSize(bool highlight)187 int ThumbnailLoadThread::maximumThumbnailPixmapSize(bool highlight)
188 {
189     if (highlight)
190     {
191         return ThumbnailSize::maxThumbsSize();
192     }
193     else
194     {
195         return ThumbnailSize::maxThumbsSize() + 2;    // see slotThumbnailLoaded
196     }
197 }
198 
setSendSurrogatePixmap(bool send)199 void ThumbnailLoadThread::setSendSurrogatePixmap(bool send)
200 {
201     d->sendSurrogate = send;
202 }
203 
setPixmapRequested(bool wantPixmap)204 void ThumbnailLoadThread::setPixmapRequested(bool wantPixmap)
205 {
206     d->wantPixmap = wantPixmap;
207 }
208 
setHighlightPixmap(bool highlight)209 void ThumbnailLoadThread::setHighlightPixmap(bool highlight)
210 {
211     d->highlight = highlight;
212 }
213 
thumbnailCreator() const214 ThumbnailCreator* ThumbnailLoadThread::thumbnailCreator() const
215 {
216     return d->creator;
217 }
218 
thumbnailToPixmapSize(int size) const219 int ThumbnailLoadThread::thumbnailToPixmapSize(int size) const
220 {
221     return d->pixmapSizeForThumbnailSize(size);
222 }
223 
thumbnailToPixmapSize(bool withHighlight,int size)224 int ThumbnailLoadThread::thumbnailToPixmapSize(bool withHighlight, int size)
225 {
226     if (withHighlight && (size >= 10))
227     {
228         return (size + 2);
229     }
230 
231     return size;
232 }
233 
pixmapToThumbnailSize(int size) const234 int ThumbnailLoadThread::pixmapToThumbnailSize(int size) const
235 {
236     return d->thumbnailSizeForPixmapSize(size);
237 }
238 
find(const ThumbnailIdentifier & identifier,int size,QPixmap * retPixmap,bool emitSignal,const QRect & detailRect)239 bool ThumbnailLoadThread::find(const ThumbnailIdentifier& identifier,
240                                int size,
241                                QPixmap* retPixmap,
242                                bool emitSignal,
243                                const QRect& detailRect)
244 {
245     const QPixmap* pix = nullptr;
246     LoadingDescription description;
247 
248     if (detailRect.isNull())
249     {
250         description = d->createLoadingDescription(identifier, size);
251     }
252     else
253     {
254         description = d->createLoadingDescription(identifier, size, detailRect);
255     }
256 
257     QString cacheKey = description.cacheKey();
258 
259     {
260         LoadingCache* const cache = LoadingCache::cache();
261         LoadingCache::CacheLock lock(cache);
262         pix                       = cache->retrieveThumbnailPixmap(cacheKey);
263     }
264 
265     if (pix)
266     {
267         if (retPixmap)
268         {
269             *retPixmap = *pix;
270         }
271 
272         if (emitSignal)
273         {
274             load(description);
275             emit signalThumbnailLoaded(description, QPixmap(*pix));
276         }
277 
278         return true;
279     }
280 
281     {
282         // If there is a result waiting for conversion to pixmap, return false - pixmap will come shortly
283 
284         QMutexLocker lock(&d->resultsMutex);
285 
286         if (d->collectedResults.contains(cacheKey))
287         {
288             return false;
289         }
290     }
291 
292     load(description);
293 
294     return false;
295 }
296 
297 // --- Normal thumbnails ---
298 
find(const ThumbnailIdentifier & identifier,QPixmap & retPixmap,int size)299 bool ThumbnailLoadThread::find(const ThumbnailIdentifier& identifier, QPixmap& retPixmap, int size)
300 {
301     return find(identifier, size, &retPixmap, false, QRect());
302 }
303 
find(const ThumbnailIdentifier & identifier,QPixmap & retPixmap)304 bool ThumbnailLoadThread::find(const ThumbnailIdentifier& identifier, QPixmap& retPixmap)
305 {
306     return find(identifier, retPixmap, d->size);
307 }
308 
find(const ThumbnailIdentifier & identifier)309 void ThumbnailLoadThread::find(const ThumbnailIdentifier& identifier)
310 {
311     find(identifier, d->size);
312 }
313 
find(const ThumbnailIdentifier & identifier,int size)314 void ThumbnailLoadThread::find(const ThumbnailIdentifier& identifier, int size)
315 {
316     find(identifier, size, nullptr, true, QRect());
317 }
318 
findGroup(QList<ThumbnailIdentifier> & identifiers)319 void ThumbnailLoadThread::findGroup(QList<ThumbnailIdentifier>& identifiers)
320 {
321     findGroup(identifiers, d->size);
322 }
323 
findGroup(QList<ThumbnailIdentifier> & identifiers,int size)324 void ThumbnailLoadThread::findGroup(QList<ThumbnailIdentifier>& identifiers, int size)
325 {
326     if (!checkSize(size))
327     {
328         return;
329     }
330 
331     QList<LoadingDescription> descriptions = d->makeDescriptions(identifiers, size);
332     ManagedLoadSaveThread::prependThumbnailGroup(descriptions);
333 }
334 
335 // --- Detail thumbnails ---
336 
find(const ThumbnailIdentifier & identifier,const QRect & rect,QPixmap & pixmap)337 bool ThumbnailLoadThread::find(const ThumbnailIdentifier& identifier, const QRect& rect, QPixmap& pixmap)
338 {
339     return find(identifier, rect, pixmap, d->size);
340 }
341 
find(const ThumbnailIdentifier & identifier,const QRect & rect,QPixmap & pixmap,int size)342 bool ThumbnailLoadThread::find(const ThumbnailIdentifier& identifier, const QRect& rect, QPixmap& pixmap, int size)
343 {
344     return find(identifier, size, &pixmap, false, rect);
345 }
346 
find(const ThumbnailIdentifier & identifier,const QRect & rect)347 void ThumbnailLoadThread::find(const ThumbnailIdentifier& identifier, const QRect& rect)
348 {
349     find(identifier, rect, d->size);
350 }
351 
find(const ThumbnailIdentifier & identifier,const QRect & rect,int size)352 void ThumbnailLoadThread::find(const ThumbnailIdentifier& identifier, const QRect& rect, int size)
353 {
354     find(identifier, size, nullptr, true, rect);
355 }
356 
findGroup(const QList<QPair<ThumbnailIdentifier,QRect>> & idsAndRects)357 void ThumbnailLoadThread::findGroup(const QList<QPair<ThumbnailIdentifier, QRect> >& idsAndRects)
358 {
359     findGroup(idsAndRects, d->size);
360 }
361 
findGroup(const QList<QPair<ThumbnailIdentifier,QRect>> & idsAndRects,int size)362 void ThumbnailLoadThread::findGroup(const QList<QPair<ThumbnailIdentifier, QRect> >& idsAndRects, int size)
363 {
364     if (!checkSize(size))
365     {
366         return;
367     }
368 
369     QList<LoadingDescription> descriptions = d->makeDescriptions(idsAndRects, size);
370     ManagedLoadSaveThread::prependThumbnailGroup(descriptions);
371 }
372 
373 // --- Preloading ---
374 
preload(const ThumbnailIdentifier & identifier)375 void ThumbnailLoadThread::preload(const ThumbnailIdentifier& identifier)
376 {
377     preload(identifier, d->size);
378 }
379 
preload(const ThumbnailIdentifier & identifier,int size)380 void ThumbnailLoadThread::preload(const ThumbnailIdentifier& identifier, int size)
381 {
382     LoadingDescription description = d->createLoadingDescription(identifier, size);
383 
384     if (d->checkDescription(description))
385     {
386         load(description, true);
387     }
388 }
389 
preloadGroup(QList<ThumbnailIdentifier> & identifiers)390 void ThumbnailLoadThread::preloadGroup(QList<ThumbnailIdentifier>& identifiers)
391 {
392     preloadGroup(identifiers, d->size);
393 }
394 
preloadGroup(QList<ThumbnailIdentifier> & identifiers,int size)395 void ThumbnailLoadThread::preloadGroup(QList<ThumbnailIdentifier>& identifiers, int size)
396 {
397     if (!checkSize(size))
398     {
399         return;
400     }
401 
402     QList<LoadingDescription> descriptions = d->makeDescriptions(identifiers, size);
403     ManagedLoadSaveThread::preloadThumbnailGroup(descriptions);
404 }
405 
pregenerateGroup(const QList<ThumbnailIdentifier> & identifiers)406 void ThumbnailLoadThread::pregenerateGroup(const QList<ThumbnailIdentifier>& identifiers)
407 {
408     pregenerateGroup(identifiers, d->size);
409 }
410 
pregenerateGroup(const QList<ThumbnailIdentifier> & identifiers,int size)411 void ThumbnailLoadThread::pregenerateGroup(const QList<ThumbnailIdentifier>& identifiers, int size)
412 {
413     if (!checkSize(size))
414     {
415         return;
416     }
417 
418     QList<LoadingDescription> descriptions = d->makeDescriptions(identifiers, size);
419 
420     for (int i = 0 ; i < descriptions.size() ; ++i)
421     {
422         descriptions[i].previewParameters.flags |= LoadingDescription::PreviewParameters::OnlyPregenerate;
423     }
424 
425     ManagedLoadSaveThread::preloadThumbnailGroup(descriptions);
426 }
427 
428 // --- Basic load() ---
429 
load(const LoadingDescription & desc)430 void ThumbnailLoadThread::load(const LoadingDescription& desc)
431 {
432     load(desc, false);
433 }
434 
load(const LoadingDescription & description,bool preload)435 void ThumbnailLoadThread::load(const LoadingDescription& description, bool preload)
436 {
437     if (!checkSize(description.previewParameters.size))
438     {
439         return;
440     }
441 
442     if (preload)
443     {
444         ManagedLoadSaveThread::preloadThumbnail(description);
445     }
446     else
447     {
448         ManagedLoadSaveThread::loadThumbnail(description);
449     }
450 }
451 
lastDescriptions() const452 QList<LoadingDescription> ThumbnailLoadThread::lastDescriptions() const
453 {
454     return d->lastDescriptions;
455 }
456 
checkSize(int size)457 bool ThumbnailLoadThread::checkSize(int size)
458 {
459     size             = d->thumbnailSizeForPixmapSize(size);
460     double ratio     = qApp->devicePixelRatio();
461     int maxThumbSize = (ratio > 1.0) ? ThumbnailSize::MAX
462                                      : ThumbnailSize::maxThumbsSize();
463 
464     if      (size <= 0)
465     {
466         qCDebug(DIGIKAM_GENERAL_LOG) << "ThumbnailLoadThread::load: No thumbnail size specified. Refusing to load thumbnail.";
467         return false;
468     }
469     else if (size > maxThumbSize)
470     {
471         qCDebug(DIGIKAM_GENERAL_LOG) << "ThumbnailLoadThread::load: Thumbnail size " << size
472                                      << " is larger than " << maxThumbSize << ". Refusing to load.";
473         return false;
474     }
475 
476     return true;
477 }
478 
479 // --- Receiving ---
480 
481 /**
482  * virtual method overridden from LoadSaveNotifier, implemented first by LoadSaveThread
483  * called by ThumbnailTask from working thread
484  */
thumbnailLoaded(const LoadingDescription & loadingDescription,const QImage & img)485 void ThumbnailLoadThread::thumbnailLoaded(const LoadingDescription& loadingDescription, const QImage& img)
486 {
487     // call parent to send signalThumbnailLoaded(LoadingDescription, QImage) - signal is part of public API
488 
489     ManagedLoadSaveThread::thumbnailLoaded(loadingDescription, img);
490 
491     if (!d->wantPixmap)
492     {
493         return;
494     }
495 
496     // Store result in our list and fire one signal
497     // This means there can be several results per pixmap,
498     // to speed up cases where inter-thread communication is the limiting factor
499 
500     QMutexLocker lock(&d->resultsMutex);
501     d->collectedResults.insert(loadingDescription.cacheKey(), ThumbnailResult(loadingDescription, img));
502 
503     // only sent signal when flag indicates there is no signal on the way currently
504 
505     if (!d->notifiedForResults)
506     {
507         d->notifiedForResults = true;
508         emit thumbnailsAvailable();
509     }
510 }
511 
slotThumbnailsAvailable()512 void ThumbnailLoadThread::slotThumbnailsAvailable()
513 {
514     // harvest collected results safely into our thread
515 
516     QList<ThumbnailResult> results;
517     {
518         QMutexLocker lock(&d->resultsMutex);
519         results               = d->collectedResults.values();
520         d->collectedResults.clear();
521 
522         // reset flag so that for next result, the signal is sent again
523 
524         d->notifiedForResults = false;
525     }
526 
527     foreach (const ThumbnailResult& result, results)
528     {
529         slotThumbnailLoaded(result.loadingDescription, result.image);
530     }
531 }
532 
slotThumbnailLoaded(const LoadingDescription & description,const QImage & thumb)533 void ThumbnailLoadThread::slotThumbnailLoaded(const LoadingDescription& description, const QImage& thumb)
534 {
535     QPixmap pix;
536 
537     if (thumb.isNull())
538     {
539         pix = surrogatePixmap(description);
540     }
541     else
542     {
543         int w = thumb.width();
544         int h = thumb.height();
545 
546         // highlight only when requested and when thumbnail
547         // width and height are greater than 10
548 
549         if (d->highlight && ((w >= 10) && (h >= 10)))
550         {
551             pix = QPixmap(w + 2, h + 2);
552             QPainter p(&pix);
553             p.setPen(QPen(Qt::black, 1));
554             p.drawRect(0, 0, w + 1, h + 1);
555             p.drawImage(1, 1, thumb);
556         }
557         else
558         {
559             pix = QPixmap::fromImage(thumb);
560         }
561     }
562 
563     // put into cache
564 
565     if (!pix.isNull())
566     {
567         LoadingCache* const cache = LoadingCache::cache();
568         LoadingCache::CacheLock lock(cache);
569         cache->putThumbnail(description.cacheKey(), pix, description.filePath);
570     }
571 
572     emit signalThumbnailLoaded(description, pix);
573 }
574 
surrogatePixmap(const LoadingDescription & description)575 QPixmap ThumbnailLoadThread::surrogatePixmap(const LoadingDescription& description)
576 {
577     if (!d->sendSurrogate)
578     {
579         return QPixmap();
580     }
581 
582     QPixmap pix;
583 
584     QMimeType mimeType = QMimeDatabase().mimeTypeForFile(description.filePath);
585 
586     if (mimeType.isValid())
587     {
588         pix = QIcon::fromTheme(mimeType.genericIconName()).pixmap(128);
589     }
590 
591     if (pix.isNull())
592     {
593         pix = QIcon::fromTheme(QLatin1String("application-x-zerosize")).pixmap(128);
594     }
595 
596     if (pix.isNull())
597     {
598         // give up
599         return QPixmap();
600     }
601 
602     // Resize icon to the right size depending of current settings.
603 
604     QSize size(pix.size());
605     size.scale(description.previewParameters.size, description.previewParameters.size, Qt::KeepAspectRatio);
606 
607     if (!pix.isNull() && (size.width() < pix.width()) && (size.height() < pix.height()))
608     {
609         // only scale down
610         // do not scale up, looks bad
611 
612         pix = pix.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
613     }
614 
615     return pix;
616 }
617 
618 // --- Utilities ---
619 
storeDetailThumbnail(const QString & filePath,const QRect & detailRect,const QImage & image,bool isFace)620 void ThumbnailLoadThread::storeDetailThumbnail(const QString& filePath, const QRect& detailRect, const QImage& image, bool isFace)
621 {
622     Q_UNUSED(isFace);
623     d->creator->storeDetailThumbnail(filePath, detailRect, image);
624 }
625 
storedSize() const626 int ThumbnailLoadThread::storedSize() const
627 {
628     return d->creator->storedSize();
629 }
630 
deleteThumbnail(const QString & filePath)631 void ThumbnailLoadThread::deleteThumbnail(const QString& filePath)
632 {
633     {
634         LoadingCache* const cache = LoadingCache::cache();
635         LoadingCache::CacheLock lock(cache);
636         QStringList possibleKeys  = LoadingDescription::possibleThumbnailCacheKeys(filePath);
637 
638         foreach (const QString& cacheKey, possibleKeys)
639         {
640             cache->removeThumbnail(cacheKey);
641         }
642     }
643 
644     ThumbnailCreator creator(static_d->storageMethod);
645 
646     if (static_d->provider)
647     {
648         creator.setThumbnailInfoProvider(static_d->provider);
649     }
650 
651     creator.deleteThumbnailsFromDisk(filePath);
652 }
653 
ThumbnailImageCatcher(QObject * const parent)654 ThumbnailImageCatcher::ThumbnailImageCatcher(QObject* const parent)
655     : QObject(parent),
656       d      (new Private)
657 {
658 }
659 
ThumbnailImageCatcher(ThumbnailLoadThread * const thread,QObject * const parent)660 ThumbnailImageCatcher::ThumbnailImageCatcher(ThumbnailLoadThread* const thread, QObject* const parent)
661     : QObject(parent),
662       d      (new Private)
663 {
664     setThumbnailLoadThread(thread);
665 }
666 
~ThumbnailImageCatcher()667 ThumbnailImageCatcher::~ThumbnailImageCatcher()
668 {
669     delete d;
670 }
671 
thread() const672 ThumbnailLoadThread* ThumbnailImageCatcher::thread() const
673 {
674     return d->thread;
675 }
676 
setThumbnailLoadThread(ThumbnailLoadThread * const thread)677 void ThumbnailImageCatcher::setThumbnailLoadThread(ThumbnailLoadThread* const thread)
678 {
679     if (d->thread == thread)
680     {
681         return;
682     }
683 
684     d->state = Private::Inactive;
685 
686     if (d->thread)
687     {
688         disconnect(d->thread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QImage)),
689                    this, SLOT(slotThumbnailLoaded(LoadingDescription,QImage)));
690     }
691 
692     d->thread = thread;
693 
694     {
695         QMutexLocker lock(&d->mutex);
696         d->reset();
697     }
698 
699     if (d->thread)
700     {
701         connect(d->thread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QImage)),
702                 this, SLOT(slotThumbnailLoaded(LoadingDescription,QImage)),
703                 Qt::DirectConnection);
704     }
705 }
706 
setActive(bool active)707 void ThumbnailImageCatcher::setActive(bool active)
708 {
709     if (d->active == active)
710     {
711         return;
712     }
713 
714     if (!active)
715     {
716         cancel();
717     }
718 
719     QMutexLocker lock(&d->mutex);
720     d->active = active;
721     d->reset();
722 }
723 
cancel()724 void ThumbnailImageCatcher::cancel()
725 {
726     QMutexLocker lock(&d->mutex);
727 
728     if (d->state == Private::Waiting)
729     {
730         d->state = Private::Quitting;
731         d->condVar.wakeOne();
732     }
733 }
734 
slotThumbnailLoaded(const LoadingDescription & description,const QImage & image)735 void ThumbnailImageCatcher::slotThumbnailLoaded(const LoadingDescription& description, const QImage& image)
736 {
737     // We are in the thumbnail thread here, DirectConnection!
738 
739     QMutexLocker lock(&d->mutex);
740 
741     switch (d->state)
742     {
743         case Private::Inactive:
744         {
745             break;
746         }
747 
748         case Private::Accepting:
749         {
750             d->intermediate << Private::CatcherResult(description, image);
751             break;
752         }
753 
754         case Private::Waiting:
755         {
756             d->harvest(description, image);
757             break;
758         }
759 
760         case Private::Quitting:
761         {
762             break;
763         }
764     }
765 }
766 
enqueue()767 int ThumbnailImageCatcher::enqueue()
768 {
769     QList<LoadingDescription> descriptions = d->thread->lastDescriptions();
770 
771     QMutexLocker lock(&d->mutex);
772 
773     foreach (const LoadingDescription& description, descriptions)
774     {
775         d->tasks << Private::CatcherResult(description);
776     }
777 
778     return descriptions.size();
779 }
780 
waitForThumbnails()781 QList<QImage> ThumbnailImageCatcher::waitForThumbnails()
782 {
783     if (!d->thread || d->tasks.isEmpty() || !d->active)
784     {
785         return QList<QImage>();
786     }
787 
788     QMutexLocker lock(&d->mutex);
789     d->state = Private::Waiting;
790 
791     // first, handle results received between request and calling this method
792 
793     foreach (const Private::CatcherResult& result, d->intermediate)
794     {
795         d->harvest(result.description, result.image);
796     }
797 
798     d->intermediate.clear();
799 
800     // Now wait for the rest to arrive. If already finished, state will be Quitting
801 
802     while (d->state == Private::Waiting)
803     {
804         d->condVar.wait(&d->mutex);
805     }
806 
807     QList<QImage> result;
808 
809     foreach (const Private::CatcherResult& task, d->tasks)
810     {
811         result << task.image;
812     }
813 
814     d->reset();
815 
816     return result;
817 }
818 
819 } // namespace Digikam
820