1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2005-04-21
7  * Description : slide show tool using preview of pictures.
8  *
9  * Copyright (C) 2005-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright (C)      2004 by Enrico Ros <eros dot kde at email dot it>
11  * Copyright (C) 2019-2020 by Minh Nghia Duong <minhnghiaduong997 at gmail 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 "slideshowloader.h"
27 #include "digikam_config.h"
28 
29 // Qt includes
30 
31 #include <QMimeDatabase>
32 #include <QApplication>
33 #include <QWheelEvent>
34 #include <QMouseEvent>
35 #include <QPaintEvent>
36 #include <QKeyEvent>
37 #include <QScreen>
38 #include <QWindow>
39 #include <QCursor>
40 #include <QTimer>
41 #include <QColor>
42 #include <QFont>
43 
44 #ifdef HAVE_DBUS
45 #   include <QDBusConnection>
46 #   include <QDBusMessage>
47 #   include <QDBusReply>
48 #endif
49 
50 // KDE includes
51 
52 #include <klocalizedstring.h>
53 
54 // Local includes
55 
56 #include "digikam_debug.h"
57 #include "slidetoolbar.h"
58 #include "slideimage.h"
59 #include "slideerror.h"
60 #include "slideosd.h"
61 #include "slideend.h"
62 
63 #ifdef HAVE_MEDIAPLAYER
64 #   include "slidevideo.h"
65 #endif //HAVE_MEDIAPLAYER
66 
67 using namespace Digikam;
68 
69 namespace DigikamGenericSlideShowPlugin
70 {
71 
72 class Q_DECL_HIDDEN SlideShowLoader::Private
73 {
74 
75 public:
76 
Private()77     explicit Private()
78         : fileIndex         (-1),
79           screenSaverCookie (-1),
80           mouseMoveTimer    (nullptr),
81           imageView         (nullptr),
82 
83 #ifdef HAVE_MEDIAPLAYER
84 
85           videoView         (nullptr),
86 
87 #endif
88 
89           errorView         (nullptr),
90           endView           (nullptr),
91           osd               (nullptr),
92           settings          (nullptr)
93     {
94     }
95 
96     int                    fileIndex;
97     int                    screenSaverCookie;
98 
99     QTimer*                mouseMoveTimer;  ///< To hide cursor when not moved.
100 
101     SlideImage*            imageView;
102 
103 #ifdef HAVE_MEDIAPLAYER
104 
105     SlideVideo*            videoView;
106 
107 #endif
108 
109     SlideError*            errorView;
110     SlideEnd*              endView;
111     SlideOSD*              osd;
112 
113     SlideShowSettings*     settings;
114 
115     QMap<QString, QString> shortcutPrefixes;
116 };
117 
SlideShowLoader(SlideShowSettings * const settings)118 SlideShowLoader::SlideShowLoader(SlideShowSettings* const settings)
119     : QStackedWidget(nullptr),
120       d             (new Private)
121 {
122     d->settings = settings;
123 
124     setAttribute(Qt::WA_DeleteOnClose);
125     setWindowFlags(Qt::FramelessWindowHint);
126     setContextMenuPolicy(Qt::PreventContextMenu);
127     setWindowState(windowState() | Qt::WindowFullScreen);
128 
129     setWindowTitle(i18n("Slideshow"));
130     setObjectName(QLatin1String("Slideshow"));
131     setMouseTracking(true);
132 
133     // ---------------------------------------------------------------
134 
135     d->errorView = new SlideError(this);
136     d->errorView->installEventFilter(this);
137 
138     insertWidget(ErrorView, d->errorView);
139 
140     // ---------------------------------------------------------------
141 
142     d->imageView = new SlideImage(this);
143     d->imageView->setPreviewSettings(d->settings->previewSettings);
144     d->imageView->installEventFilter(this);
145 
146     connect(d->imageView, SIGNAL(signalImageLoaded(bool)),
147             this, SLOT(slotImageLoaded(bool)));
148 
149     insertWidget(ImageView, d->imageView);
150 
151     // ---------------------------------------------------------------
152 
153 #ifdef HAVE_MEDIAPLAYER
154 
155     d->videoView = new SlideVideo(this);
156     d->videoView->setInfoInterface(d->settings->iface);
157     d->videoView->installEventFilter(this);
158 
159     connect(d->videoView, SIGNAL(signalVideoLoaded(bool)),
160             this, SLOT(slotVideoLoaded(bool)));
161 
162     connect(d->videoView, SIGNAL(signalVideoFinished()),
163             this, SLOT(slotVideoFinished()));
164 
165     insertWidget(VideoView, d->videoView);
166 
167 #endif
168 
169     // ---------------------------------------------------------------
170 
171     d->endView = new SlideEnd(this);
172     d->endView->installEventFilter(this);
173 
174     insertWidget(EndView, d->endView);
175 
176     // ---------------------------------------------------------------
177 
178     d->osd = new SlideOSD(d->settings, this);
179     d->osd->installEventFilter(this);
180 
181     // ---------------------------------------------------------------
182 
183     d->mouseMoveTimer = new QTimer(this);
184     d->mouseMoveTimer->setSingleShot(true);
185     d->mouseMoveTimer->setInterval(1000);
186 
187     connect(d->mouseMoveTimer, SIGNAL(timeout()),
188             this, SLOT(slotMouseMoveTimeOut()));
189 
190     // ---------------------------------------------------------------
191 
192     QScreen* screen = qApp->primaryScreen();
193 
194     if (QWidget* const widget = qApp->activeWindow())
195     {
196         if (QWindow* const window = widget->windowHandle())
197         {
198             screen = window->screen();
199         }
200     }
201 
202     const int activeScreenIndex = qMax(qApp->screens().indexOf(screen), 0);
203     const int preferenceScreen  = d->settings->slideScreen;
204     int screenIndex             = 0;
205 
206     if      (preferenceScreen == -2)
207     {
208         screenIndex = activeScreenIndex;
209     }
210     else if (preferenceScreen == -1)
211     {
212         QScreen* const primaryScreen = qApp->primaryScreen();
213         screenIndex                  = qApp->screens().indexOf(primaryScreen);
214     }
215     else if ((preferenceScreen >= 0) && (preferenceScreen < qApp->screens().count()))
216     {
217         screenIndex = preferenceScreen;
218     }
219     else
220     {
221         screenIndex              = activeScreenIndex;
222         d->settings->slideScreen = -2;
223         d->settings->writeToConfig();
224     }
225 
226     slotScreenSelected(screenIndex);
227 
228     // ---------------------------------------------------------------
229 
230     inhibitScreenSaver();
231     slotMouseMoveTimeOut();
232     setCurrentIndex(ImageView);
233 }
234 
~SlideShowLoader()235 SlideShowLoader::~SlideShowLoader()
236 {
237     emit signalLastItemUrl(currentItem());
238 
239     d->mouseMoveTimer->stop();
240 
241     allowScreenSaver();
242 
243     delete d->settings;
244     delete d;
245 }
246 
setCurrentView(SlideShowViewMode view)247 void SlideShowLoader::setCurrentView(SlideShowViewMode view)
248 {
249     switch (view)
250     {
251         case ErrorView:
252         {
253             d->osd->video(false);
254             d->errorView->setCurrentUrl(currentItem());
255 
256             setCurrentIndex(view);
257             d->osd->setCurrentUrl(currentItem());
258             break;
259         }
260 
261         case ImageView:
262         {
263 
264 #ifdef HAVE_MEDIAPLAYER
265 
266             d->videoView->stop();
267             d->osd->video(false);
268 
269 #endif
270 
271             setCurrentIndex(view);
272             d->osd->setCurrentUrl(currentItem());
273             break;
274         }
275 
276         case VideoView:
277         {
278 
279 #ifdef HAVE_MEDIAPLAYER
280 
281             d->osd->video(true);
282             d->osd->pause(false);
283             setCurrentIndex(view);
284             d->osd->setCurrentUrl(currentItem());
285 
286 #endif
287 
288             break;
289         }
290 
291         default : // EndView
292         {
293 
294 #ifdef HAVE_MEDIAPLAYER
295 
296             d->videoView->stop();
297             d->osd->video(false);
298 
299 #endif
300 
301             d->osd->pause(true);
302             setCurrentIndex(view);
303             break;
304         }
305     }
306 }
307 
setCurrentItem(const QUrl & url)308 void SlideShowLoader::setCurrentItem(const QUrl& url)
309 {
310     int index = d->settings->indexOf(url);
311 
312     if (index != -1)
313     {
314         d->fileIndex = index - 1;
315     }
316 }
317 
currentItem() const318 QUrl SlideShowLoader::currentItem() const
319 {
320     return d->settings->fileList.value(d->fileIndex);
321 }
322 
setShortCutPrefixes(const QMap<QString,QString> & prefixes)323 void SlideShowLoader::setShortCutPrefixes(const QMap<QString, QString>& prefixes)
324 {
325     d->shortcutPrefixes = prefixes;
326 }
327 
slotLoadNextItem()328 void SlideShowLoader::slotLoadNextItem()
329 {
330     int num = d->settings->count();
331 
332     if (d->fileIndex == (num - 1))
333     {
334         if (d->settings->loop)
335         {
336             d->fileIndex = -1;
337         }
338     }
339 
340     d->fileIndex++;
341 
342     qCDebug(DIGIKAM_GENERAL_LOG) << "fileIndex: " << d->fileIndex;
343 
344     if (!d->settings->loop)
345     {
346         d->osd->toolBar()->setEnabledPrev(d->fileIndex > 0);
347         d->osd->toolBar()->setEnabledNext(d->fileIndex < (num - 1));
348     }
349 
350     if ((d->fileIndex >= 0) && (d->fileIndex < num))
351     {
352 
353 #ifdef HAVE_MEDIAPLAYER
354 
355         QMimeDatabase mimeDB;
356 
357         if (mimeDB.mimeTypeForFile(currentItem().toLocalFile()).name().startsWith(QLatin1String("video/")))
358         {
359             d->videoView->setCurrentUrl(currentItem());
360             return;
361         }
362 
363 #endif
364 
365         d->imageView->setLoadUrl(currentItem());
366     }
367     else
368     {
369         endOfSlide();
370     }
371 }
372 
slotLoadPrevItem()373 void SlideShowLoader::slotLoadPrevItem()
374 {
375     int num = d->settings->count();
376 
377     if (d->fileIndex == 0)
378     {
379         if (d->settings->loop)
380         {
381             d->fileIndex = num;
382         }
383     }
384 
385     d->fileIndex--;
386 
387     qCDebug(DIGIKAM_GENERAL_LOG) << "fileIndex: " << d->fileIndex;
388 
389     if (!d->settings->loop)
390     {
391         d->osd->toolBar()->setEnabledPrev(d->fileIndex > 0);
392         d->osd->toolBar()->setEnabledNext(d->fileIndex < (num - 1));
393     }
394 
395     if ((d->fileIndex >= 0) && (d->fileIndex < num))
396     {
397 
398 #ifdef HAVE_MEDIAPLAYER
399 
400         QMimeDatabase mimeDB;
401 
402         if (mimeDB.mimeTypeForFile(currentItem().toLocalFile())
403                                    .name().startsWith(QLatin1String("video/")))
404         {
405             d->videoView->setCurrentUrl(currentItem());
406             return;
407         }
408 
409 #endif
410 
411         d->imageView->setLoadUrl(currentItem());
412     }
413     else
414     {
415         endOfSlide();
416     }
417 }
418 
slotImageLoaded(bool loaded)419 void SlideShowLoader::slotImageLoaded(bool loaded)
420 {
421     if (loaded)
422     {
423         setCurrentView(ImageView);
424 
425         if (d->fileIndex != -1)
426         {
427             if (!d->osd->isPaused())
428             {
429                 d->osd->pause(false);
430             }
431 
432             preloadNextItem();
433         }
434     }
435     else
436     {
437 
438 #ifdef HAVE_MEDIAPLAYER
439 
440         // Try to load only GIF Images
441 
442         QMimeDatabase mimeDB;
443 
444         if (mimeDB.mimeTypeForFile(currentItem().toLocalFile())
445                                    .name() == QLatin1String("image/gif"))
446         {
447             d->videoView->setCurrentUrl(currentItem());
448         }
449 
450 #else
451 
452         preloadNextItem();
453 
454 #endif
455 
456     }
457 
458     d->osd->setLoadingReady(true);
459 }
460 
slotRemoveImageFromList()461 void SlideShowLoader::slotRemoveImageFromList()
462 {
463     QUrl url = currentItem();
464 
465     // Delete or move to trash by url
466 
467     d->settings->iface->deleteImage(url);
468 
469     // Delete from list of slide show
470 
471     d->settings->fileList.removeOne(url);
472 
473     slotLoadNextItem();
474 }
475 
slotVideoLoaded(bool loaded)476 void SlideShowLoader::slotVideoLoaded(bool loaded)
477 {
478     if (loaded)
479     {
480         setCurrentView(VideoView);
481     }
482     else
483     {
484         // Failed to load item
485 
486         setCurrentView(ErrorView);
487 
488         if (d->fileIndex != -1)
489         {
490             if (!d->osd->isPaused())
491             {
492                 d->osd->pause(false);
493             }
494         }
495     }
496 
497     preloadNextItem();
498 }
499 
slotVideoFinished()500 void SlideShowLoader::slotVideoFinished()
501 {
502     if (d->fileIndex != -1)
503     {
504         d->osd->video(false);
505         slotLoadNextItem();
506     }
507 }
508 
endOfSlide()509 void SlideShowLoader::endOfSlide()
510 {
511     setCurrentView(EndView);
512     d->fileIndex = -1;
513     d->osd->toolBar()->setEnabledPlay(false);
514     d->osd->toolBar()->setEnabledNext(false);
515     d->osd->toolBar()->setEnabledPrev(false);
516 }
517 
preloadNextItem()518 void SlideShowLoader::preloadNextItem()
519 {
520     int index = d->fileIndex + 1;
521     int num   = d->settings->count();
522 
523     if (index >= num)
524     {
525         if (d->settings->loop)
526         {
527             index = 0;
528         }
529     }
530 
531     if (index < num)
532     {
533         QUrl nextItem = d->settings->fileList.value(index);
534 
535 #ifdef HAVE_MEDIAPLAYER
536 
537         QMimeDatabase mimeDB;
538 
539         if (mimeDB.mimeTypeForFile(nextItem.toLocalFile())
540                                    .name().startsWith(QLatin1String("video/")))
541         {
542             return;
543         }
544 
545 #endif
546 
547         d->imageView->setPreloadUrl(nextItem);
548     }
549 }
550 
wheelEvent(QWheelEvent * e)551 void SlideShowLoader::wheelEvent(QWheelEvent* e)
552 {
553     d->osd->toolBar()->closeConfigurationDialog();
554 
555     if (e->angleDelta().y() < 0)
556     {
557         d->osd->pause(true);
558         slotLoadNextItem();
559     }
560 
561     if (e->angleDelta().y() > 0)
562     {
563         if (d->fileIndex == -1)
564         {
565             // EndView => backward.
566 
567             d->fileIndex = d->settings->count();
568         }
569 
570         d->osd->pause(true);
571         slotLoadPrevItem();
572     }
573 }
574 
mousePressEvent(QMouseEvent * e)575 void SlideShowLoader::mousePressEvent(QMouseEvent* e)
576 {
577     d->osd->toolBar()->closeConfigurationDialog();
578 
579     if      (e->button() == Qt::LeftButton)
580     {
581         if (d->fileIndex == -1)
582         {
583             // EndView => close Slideshow view.
584 
585             close();
586 
587             return;
588         }
589 
590         d->osd->pause(true);
591         slotLoadNextItem();
592     }
593     else if (e->button() == Qt::RightButton)
594     {
595         if (d->fileIndex == -1)
596         {
597             // EndView => backward.
598 
599             d->fileIndex = d->settings->count();
600         }
601 
602         d->osd->pause(true);
603         slotLoadPrevItem();
604     }
605 }
606 
keyPressEvent(QKeyEvent * e)607 void SlideShowLoader::keyPressEvent(QKeyEvent* e)
608 {
609     if (!e)
610     {
611         return;
612     }
613 
614     if (e->key() == Qt::Key_F4)
615     {
616         d->osd->toggleProperties();
617         return;
618     }
619 
620     d->osd->toolBar()->keyPressEvent(e);
621 }
622 
eventFilter(QObject * obj,QEvent * ev)623 bool SlideShowLoader::eventFilter(QObject* obj, QEvent* ev)
624 {
625     if (ev->type() == QEvent::MouseMove)
626     {
627         setCursor(QCursor(Qt::ArrowCursor));
628 
629 #ifdef HAVE_MEDIAPLAYER
630 
631         d->videoView->showIndicator(true);
632 
633 #endif
634 
635         d->mouseMoveTimer->start();
636         return false;
637     }
638 
639     // pass the event on to the parent class
640 
641     return QWidget::eventFilter(obj, ev);
642 }
643 
slotMouseMoveTimeOut()644 void SlideShowLoader::slotMouseMoveTimeOut()
645 {
646     if (!d->osd->isUnderMouse())
647     {
648         setCursor(QCursor(Qt::BlankCursor));
649     }
650 
651 #ifdef HAVE_MEDIAPLAYER
652 
653     d->videoView->showIndicator(false);
654 
655 #endif
656 
657 }
658 
659 /**
660  * Inspired from Okular's presentation widget
661  * TODO: Add OSX and Windows support
662  */
inhibitScreenSaver()663 void SlideShowLoader::inhibitScreenSaver()
664 {
665 
666 #ifdef HAVE_DBUS
667 
668     QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.ScreenSaver"),
669                                                           QLatin1String("/ScreenSaver"),
670                                                           QLatin1String("org.freedesktop.ScreenSaver"),
671                                                           QLatin1String("Inhibit"));
672     message << QLatin1String("digiKam");
673     message << i18nc("Reason for inhibiting the screensaver activation, when the presentation mode is active", "Giving a slideshow");
674 
675     QDBusReply<uint> reply = QDBusConnection::sessionBus().call(message);
676 
677     if (reply.isValid())
678     {
679         d->screenSaverCookie = reply.value();
680     }
681 
682 #endif
683 
684 }
685 
allowScreenSaver()686 void SlideShowLoader::allowScreenSaver()
687 {
688 
689 #ifdef HAVE_DBUS
690 
691     if (d->screenSaverCookie != -1)
692     {
693         QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.ScreenSaver"),
694                                                               QLatin1String("/ScreenSaver"),
695                                                               QLatin1String("org.freedesktop.ScreenSaver"),
696                                                               QLatin1String("UnInhibit"));
697         message << (uint)d->screenSaverCookie;
698         QDBusConnection::sessionBus().send(message);
699     }
700 
701 #endif
702 
703 }
704 
slotAssignRating(int rating)705 void SlideShowLoader::slotAssignRating(int rating)
706 {
707     DInfoInterface::DInfoMap info;
708     info.insert(QLatin1String("rating"), rating);
709 
710     d->settings->iface->setItemInfo(currentItem(), info);
711 
712     dispatchCurrentInfoChange(currentItem());
713 }
714 
slotAssignColorLabel(int color)715 void SlideShowLoader::slotAssignColorLabel(int color)
716 {
717     DInfoInterface::DInfoMap info;
718     info.insert(QLatin1String("colorlabel"), color);
719 
720     d->settings->iface->setItemInfo(currentItem(), info);
721 
722     dispatchCurrentInfoChange(currentItem());
723 }
724 
slotAssignPickLabel(int pick)725 void SlideShowLoader::slotAssignPickLabel(int pick)
726 {
727     DInfoInterface::DInfoMap info;
728     info.insert(QLatin1String("picklabel"), pick);
729 
730     d->settings->iface->setItemInfo(currentItem(), info);
731 
732     dispatchCurrentInfoChange(currentItem());
733 }
734 
slotToggleTag(int tag)735 void SlideShowLoader::slotToggleTag(int tag)
736 {
737     DInfoInterface::DInfoMap info;
738     info.insert(QLatin1String("tag"), tag);
739 
740     d->settings->iface->setItemInfo(currentItem(), info);
741 
742     dispatchCurrentInfoChange(currentItem());
743 }
744 
slotHandleShortcut(const QString & shortcut,int val)745 void SlideShowLoader::slotHandleShortcut(const QString& shortcut, int val)
746 {
747     //qCDebug(DIGIKAM_GENERAL_LOG) << "SlideShowLoader::slotHandleShortcut";
748 
749     if (d->shortcutPrefixes.contains(QLatin1String("rating")) &&
750         shortcut.startsWith(d->shortcutPrefixes[QLatin1String("rating")]))
751     {
752         slotAssignRating(val);
753 
754         return;
755     }
756 
757     if (d->shortcutPrefixes.contains(QLatin1String("colorlabel")) &&
758         shortcut.startsWith(d->shortcutPrefixes[QLatin1String("colorlabel")]))
759     {
760         slotAssignColorLabel(val);
761 
762         return;
763     }
764 
765     if (d->shortcutPrefixes.contains(QLatin1String("picklabel")) &&
766         shortcut.startsWith(d->shortcutPrefixes[QLatin1String("picklabel")]))
767     {
768         slotAssignPickLabel(val);
769 
770         return;
771     }
772 
773     if (d->shortcutPrefixes.contains(QLatin1String("tag")) &&
774         shortcut.startsWith(d->shortcutPrefixes[QLatin1String("tag")]))
775     {
776         slotToggleTag(val);
777 
778         return;
779     }
780 
781     qCWarning(DIGIKAM_GENERAL_LOG) << "Shortcut is not yet supported in SlideShowLoader::slotHandleShortcut():" << shortcut;
782 }
783 
dispatchCurrentInfoChange(const QUrl & url)784 void SlideShowLoader::dispatchCurrentInfoChange(const QUrl& url)
785 {
786     if (currentItem() == url)
787     {
788         d->osd->setCurrentUrl(currentItem());
789     }
790 }
791 
slotPause()792 void SlideShowLoader::slotPause()
793 {
794 
795 #ifdef HAVE_MEDIAPLAYER
796 
797     if (currentIndex() == VideoView)
798     {
799         d->videoView->pause(true);
800     }
801     else
802 
803 #endif
804 
805     {
806         d->osd->pause(true);
807     }
808 }
809 
slotPlay()810 void SlideShowLoader::slotPlay()
811 {
812 #ifdef HAVE_MEDIAPLAYER
813 
814     if (currentIndex() == VideoView)
815     {
816         d->videoView->pause(false);
817     }
818     else
819 
820 #endif
821 
822     {
823         d->osd->pause(false);
824     }
825 }
826 
slotScreenSelected(int screen)827 void SlideShowLoader::slotScreenSelected(int screen)
828 {
829     if (screen >= qApp->screens().count())
830     {
831         return;
832     }
833 
834     QRect deskRect = qApp->screens().at(screen)->geometry();
835 
836     setWindowState(windowState() & ~Qt::WindowFullScreen);
837 
838     move(deskRect.x(), deskRect.y());
839     resize(deskRect.width(), deskRect.height());
840 
841     setWindowState(windowState() | Qt::WindowFullScreen);
842 
843     // update OSD position
844 
845     if (d->fileIndex != -1)
846     {
847         qApp->processEvents();
848         d->osd->setCurrentUrl(currentItem());
849     }
850 
851     qCDebug(DIGIKAM_GENERAL_LOG) << "Slideshow: move to screen: " << screen
852                                  << " :: " << deskRect;
853 }
854 
855 } // namespace DigikamGenericSlideShowPlugin
856