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