MythTV master
mythnotificationcenter.cpp
Go to the documentation of this file.
1//
2// mythnotificationcenter.cpp
3// MythTV
4//
5// Created by Jean-Yves Avenard on 25/06/13.
6// Copyright (c) 2013 Bubblestuff Pty Ltd. All rights reserved.
7//
8
9// Qt headers
10#include <QCoreApplication>
11#include <QEvent>
12#include <QObject>
13#include <QThread>
14#include <QTimer>
15
16// MythTV headers
19
20#include "mythmainwindow.h"
23#include "mythpainter.h"
24#include "mythscreenstack.h"
25#include "mythscreentype.h"
26#include "mythuiimage.h"
27#include "mythuiprogressbar.h"
28#include "mythuitext.h"
29
30#define LOC QString("NotificationCenter: ")
31
32static constexpr int8_t HGAP { 5 };
33static constexpr std::chrono::milliseconds DEFAULT_DURATION { 5s };
34
36
38 (QEvent::Type) QEvent::registerEventType();
39
41
43 bool deleteScreen)
44{
45 if (!screen || screen->IsDeleting())
46 return;
47
48 bool poppedFullscreen = screen->IsFullscreen();
49
50 screen->aboutToHide();
51
52 if (m_children.isEmpty())
53 return;
54
55 MythMainWindow *mainwindow = GetMythMainWindow();
56
57 screen->setParent(nullptr);
58 if (allowFade && m_doTransitions && !mainwindow->IsExitingToMain())
59 {
60 screen->SetFullscreen(false);
61 if (deleteScreen)
62 {
63 screen->SetDeleting(true);
64 m_toDelete.push_back(screen);
65 }
66 screen->AdjustAlpha(1, -kFadeVal);
67 }
68 else
69 {
70 for (int i = 0; i < m_children.size(); ++i)
71 {
72 if (m_children.at(i) == screen)
73 {
74 m_children.remove(i);
75 break;
76 }
77 }
78 if (deleteScreen)
79 screen->deleteLater();
80
81 screen = nullptr;
82 }
83
84 m_topScreen = nullptr;
85
87
88 // If we're fading it, we still want to draw it.
89 if (screen && !m_drawOrder.contains(screen))
90 m_drawOrder.push_back(screen);
91
92 if (!m_children.isEmpty())
93 {
94 for (auto *draw : std::as_const(m_drawOrder))
95 {
96 if (draw != screen && !draw->IsDeleting())
97 {
98 m_topScreen = draw;
99 draw->SetAlpha(255);
100 if (poppedFullscreen)
101 draw->aboutToShow();
102 }
103 }
104 }
105
106 if (m_topScreen)
107 {
109 }
110 else
111 {
112 // Screen still needs to be redrawn if we have popped the last screen
113 // off the popup stack, or similar
114 if (mainwindow->GetMainStack())
115 {
116 MythScreenType *mainscreen = mainwindow->GetMainStack()->GetTopScreen();
117 if (mainscreen)
118 mainscreen->SetRedraw();
119 }
120 }
121}
122
124{
125 if (m_children.isEmpty())
126 return nullptr;
127 // The top screen is the only currently displayed first, if there's a
128 // fullscreen notification displayed, it's the last one
129 MythScreenType *top = m_children.front();
130 QVector<MythScreenType *>::const_iterator it = m_children.end() - 1;
131
132 // loop from last to 2nd
133 for (; it != m_children.begin(); --it)
134 {
135 auto *s = qobject_cast<MythNotificationScreen *>(*it);
136 if (!s)
137 {
138 // if for whatever reason it's not a notification on our screen
139 // it will be dropped as we don't know how it appears
140 top = s;
141 continue;
142 }
143 if (s->m_fullscreen)
144 {
145 top = s;
146 break;
147 }
148 }
149 return top;
150}
151
153
155 int id)
156 : MythScreenType(stack, "mythnotification"), m_id(id),
157 m_timer(new QTimer(this))
158{
159 // Set timer if need be
162}
163
165 MythNotification &notification)
166 : MythScreenType(stack, "mythnotification"), m_id(notification.GetId()),
167 m_duration(notification.GetDuration()),
168 m_timer(new QTimer(this))
169{
170 SetNotification(notification);
172}
173
175 const MythNotificationScreen &s)
176 : MythScreenType(stack, "mythnotification"),
177 m_id(s.m_id),
178 m_image(s.m_image),
179 m_imagePath(s.m_imagePath),
180 m_title(s.m_title),
181 m_origin(s.m_origin),
182 m_description(s.m_description),
183 m_extra(s.m_extra),
184 m_duration(s.m_duration),
185 m_progress(s.m_progress),
186 m_progresstext(s.m_progresstext),
187 m_fullscreen(s.m_fullscreen),
188 m_content(s.m_content),
189 m_update(s.m_content), // so all fields are initialised regardless of notification type
190 m_type(s.m_type),
191 m_timer(new QTimer(this)),
192 m_style(s.m_style)
193{
195}
196
198{
199 m_timer->stop();
200 LOG(VB_GUI, LOG_DEBUG, LOC + "MythNotificationScreen dtor");
201 // We can't rely on Exiting() default MythScreenType signal as
202 // by the time it is emitted, the destructor would have already been called
203 // making the members unusable
204 emit ScreenDeleted();
205}
206
208{
209 bool update = false;
210 m_update = kNone;
211
212 m_type = notification.type();
213
218 {
219 m_update |= kImage;
220 update = false;
221 }
223 {
224 update = true;
225 }
226 else
227 {
228 update = false;
229 }
230
231 auto *img = dynamic_cast<MythImageNotification*>(&notification);
232 if (img)
233 {
234 QString path = img->GetImagePath();
235
236 m_update |= kImage;
237
238 if (path.isNull())
239 {
240 UpdateArtwork(img->GetImage());
241 }
242 else
243 {
244 UpdateArtwork(path);
245 }
246 }
247
248 auto *play = dynamic_cast<MythPlaybackNotification*>(&notification);
249 if (play)
250 {
251 UpdatePlayback(play->GetProgress(), play->GetProgressText());
252
254 }
255
256 auto *media = dynamic_cast<MythMediaNotification*>(&notification);
257 if (media && m_imagePath.isEmpty() && m_image.isNull())
258 {
260 }
261
262 if (!notification.GetMetaData().isEmpty())
263 {
264 UpdateMetaData(notification.GetMetaData());
266 }
267 else if (!update)
268 {
269 // A new notification, will always update the metadata field
271 }
272
273 if (!notification.GetStyle().isEmpty())
274 {
275 m_style = notification.GetStyle();
276 m_update |= kStyle;
277 }
278
279 if (!update)
280 {
282 m_fullscreen = notification.GetFullScreen();
283 }
284
285 m_duration = notification.GetDuration();
286 m_visibility = notification.GetVisibility();
287 if (!m_visibility)
288 {
289 // no visibility is all visibility to get around QVariant always making 0 the default
290 m_visibility = ~0;
291 }
292 m_priority = notification.GetPriority();
293
294 // Set timer if need be
296
297 // We need to re-run init
298 m_refresh = true;
299}
300
302{
303 bool foundtheme = false;
304
305 // Load the theme for this screen
306 // The xml file containing the screen definition is airplay-ui.xml in this
307 // example, the name of the screen in the xml is airplaypicture. This
308 // should make sense when you look at the xml below
309
310 QString theme;
311 if (m_fullscreen)
312 {
313 theme = "notification-full";
314 }
315 else if (m_content & kImage)
316 {
317 theme = "notification-image";
318 }
319 else
320 {
321 theme = "notification";
322 }
323
324 QString theme_attempt = theme + (m_style.isEmpty() ? "" : "-" + m_style);
325
326 // See if we have an alternative theme available as defined in the notification
327 foundtheme = LoadWindowFromXML("notification-ui.xml", theme_attempt, this);
328 if (!foundtheme && theme_attempt != theme)
329 {
330 // if not, default to the main one
331 foundtheme = LoadWindowFromXML("notification-ui.xml", theme, this);
332 }
333
334 if (!foundtheme) // If we cannot load the theme for any reason ...
335 return false;
336
337 m_artworkImage = dynamic_cast<MythUIImage*>(GetChild("image"));
338 m_titleText = dynamic_cast<MythUIText*>(GetChild("title"));
339 m_originText = dynamic_cast<MythUIText*>(GetChild("origin"));
340 m_descriptionText = dynamic_cast<MythUIText*>(GetChild("description"));
341 m_extraText = dynamic_cast<MythUIText*>(GetChild("extra"));
342 m_progresstextText = dynamic_cast<MythUIText*>(GetChild("progress_text"));
343 m_progressBar = dynamic_cast<MythUIProgressBar*>(GetChild("progress"));
344 m_errorState = dynamic_cast<MythUIStateType*>(GetChild("errorstate"));
345 m_mediaState = dynamic_cast<MythUIStateType*>(GetChild("mediastate"));
346
348
349 if (m_mediaState && (m_update & kImage))
350 {
351 m_mediaState->DisplayState((m_content & kNoArtwork) ? "noartwork" : "ok");
352 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Create: Set media state to %1").arg((m_content & kNoArtwork) ? "noartwork" : "ok"));
353 }
354
355 // store original position
357 m_created = true;
358
360 {
361 // Visibility will be set automatically during video playback
362 // so can be ignored here
363 SetVisible(false);
364 }
365
366 // We need to re-run init
367 m_refresh = true;
368
369 return true;
370}
371
376{
377 if (!m_refresh) // nothing got changed so far, return
378 return;
379
381
382 if (m_artworkImage && (m_update & kImage))
383 {
384 if (!m_imagePath.isNull())
385 {
386 // We have a path to the image, use it
389 }
390 else if (!m_image.isNull())
391 {
392 // We don't have a path to the image, but the image itself
394 img->Assign(m_image);
396 img->DecrRef();
397 }
398 else
399 {
400 // Will default to displaying whatever placeholder image is defined
401 // in the xml by the themer, means we can show _something_ rather than
402 // a big empty hole. Generally you always want to call Reset() in
403 // these circumstances
405 }
406 }
407
408 if (m_update != kNone)
409 {
410 InfoMap tmap;
411
412 tmap["title"] = m_title;
413 if (m_update & kImage)
414 {
415 tmap["image"] = m_imagePath;
416 }
417 tmap["origin"] = m_origin;
418 tmap["description"] = m_description;
419 tmap["extra"] = m_extra;
420 if (m_update & kDuration)
421 {
422 tmap["progress_text"] = m_progresstext;
423 tmap["progress"] = QString("%1").arg((int)(m_progress * 100));
424 }
425 SetTextFromMap(tmap);
426 }
427
428 if (m_update & kMetaData)
429 {
430 if (m_titleText && m_title.isNull())
431 {
433 }
434 if (m_originText && m_origin.isNull())
435 {
437 }
438 if (m_descriptionText && m_description.isNull())
439 {
441 }
442 if (m_extraText && m_extra.isNull())
443 {
445 }
446 }
447
448 if (m_update & kDuration)
449 {
450 if (m_progresstextText && m_progresstext.isEmpty())
451 {
453 }
454 if (m_progressBar)
455 {
456 if (m_progress >= 0)
457 {
461 }
462 else
463 {
464 // Same as above, calling Reset() allows for a sane, themer defined
465 //default to be displayed
467 }
468 }
469 }
470
471 if (m_progressBar)
472 {
474
475 }
476
478
479 if (m_mediaState && (m_update & kImage))
480 {
481 m_mediaState->DisplayState((m_update & kNoArtwork) ? "noartwork" : "ok");
482 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Init: Set media state to %1").arg((m_update & kNoArtwork) ? "noartwork" : "ok"));
483 }
484
485 // No field will be refreshed the next time unless specified otherwise
486 m_update = kNone;
487
488 if (GetScreenStack() && !m_added)
489 {
490 GetScreenStack()->AddScreen(this);
491 m_added = true;
492 }
493 m_refresh = false;
494}
495
497{
498 if (!m_errorState)
499 return;
500
501 const char *state {nullptr};
502
504 {
505 state = "error";
506 }
508 {
509 state = "warning";
510 }
512 {
513 state = "check";
514 }
516 {
517 state = "busy";
518 }
519 else
520 {
521 state = "ok";
522 }
523 LOG(VB_GUI, LOG_DEBUG, LOC + QString("SetErrorState: Set error state to %1").arg(state));
525}
526
532{
533 m_image = image;
534 // We need to re-run init
535 m_refresh = true;
536}
537
543{
544 m_imagePath = image;
545 // We need to re-run init
546 m_refresh = true;
547}
548
556{
557 QString tmp;
558
559 tmp = data["minm"];
560 if (!(tmp.isNull() && (m_update & kMetaData)))
561 {
562 m_title = tmp;
563 }
564 tmp = data["asar"];
565 if (!(tmp.isNull() && (m_update & kMetaData)))
566 {
567 m_origin = tmp;
568 }
569 tmp = data["asal"];
570 if (!(tmp.isNull() && (m_update & kMetaData)))
571 {
573 }
574 tmp = data["asfm"];
575 if (!(tmp.isNull() && (m_update & kMetaData)))
576 {
577 m_extra = tmp;
578 }
579 // We need to re-run init
580 m_refresh = true;
581}
582
587void MythNotificationScreen::UpdatePlayback(float progress, const QString &text)
588{
590 m_progresstext = text;
591 // We need to re-run init
592 m_refresh = true;
593}
594
599{
600 // check if anything has changed
601 m_refresh = m_id != s.m_id ||
602 m_image != s.m_image ||
604 m_title != s.m_title ||
605 m_origin != s.m_origin ||
607 m_extra != s.m_extra ||
608 m_duration != s.m_duration ||
609 m_progress != s.m_progress ||
611 m_content != s.m_content ||
613 m_expiry != s.m_expiry ||
614 m_index != s.m_index ||
615 m_style != s.m_style ||
617 m_priority != s.m_priority ||
618 m_type != s.m_type
619 ;
620
621 if (m_refresh)
622 {
623 m_id = s.m_id;
624 m_image = s.m_image;
626 m_title = s.m_title;
627 m_origin = s.m_origin;
629 m_extra = s.m_extra;
635 m_expiry = s.m_expiry;
636 m_index = s.m_index;
637 m_style = s.m_style;
640 m_type = s.m_type;
641 }
642
643 m_update = m_content; // so all fields are initialised regardless of notification type
644}
645
651{
652 MythPoint point = m_position;
653 point.setY(m_position.getY().toInt() + ((GetHeight() + HGAP) * m_index));
654
655 if (point == GetPosition())
656 return;
657
658 SetPosition(point);
659 // We need to re-run init
660 m_refresh = true;
661}
662
664{
665 if (set)
666 {
667 m_index = by;
668 }
669 else
670 {
671 m_index += by;
672 }
674}
675
680{
681 if (index != m_index)
682 {
683 m_refresh = true;
684 m_index = index;
685 }
686}
687
689{
690 return GetArea().getHeight().toInt();
691}
692
694{
695 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Screen id %1 \"%2\" expired")
696 .arg(m_id).arg(m_title));
697 // delete screen
698 GetScreenStack()->PopScreen(this, true, true);
699}
700
701void MythNotificationScreen::SetSingleShotTimer(std::chrono::seconds s, bool update)
702{
703 // only registered application can display non-expiring notification
704 if (m_id > 0 && s < 0s)
705 return;
706
707 std::chrono::milliseconds ms = s;
708 ms = ms <= DEFAULT_DURATION ? DEFAULT_DURATION : ms;
709
710 if (!update)
711 {
713 }
714 m_expiry = MythDate::current().addMSecs(ms.count());
715
716 m_timer->stop();
717 m_timer->setSingleShot(true);
718 m_timer->start(ms);
719 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Screen %1 expires at %2")
720 .arg(m_id).arg(m_expiry.toString("mm:ss")));
721
722}
723
724// Public event handling
726{
727 QStringList actions;
728 bool handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions);
729
730 for (int i = 0; i < actions.size() && !handled; i++)
731 {
732 const QString& action = actions[i];
733
734 if (action == "ESCAPE")
735 {
736 if (MythDate::current() < m_creation.addMSecs(MIN_LIFE))
737 return true; // was updated less than 1s ago, ignore
738 }
739 }
740 if (!handled)
741 {
742 handled = MythScreenType::keyPressEvent(event);
743 }
744 return handled;
745}
746
748
750{
752 "mythnotificationcenter",
753 this);
755}
756
758{
759 const bool isGuiThread =
760 QThread::currentThread() == QCoreApplication::instance()->thread();
761
762 if (!isGuiThread)
763 {
764 LOG(VB_GENERAL, LOG_ERR, LOC + "Destructor not called from GUI thread");
765 }
766
767 QMutexLocker lock(&m_lock);
768
772
773 // Delete all outstanding queued notifications
774 for (MythNotification *n : std::as_const(m_notifications))
775 {
776 delete n;
777 }
778 m_notifications.clear();
779
780 delete m_screenStack;
782}
783
789{
790 auto *screen = qobject_cast<MythNotificationScreen*>(sender());
791 if (screen == nullptr)
792 return;
793
794 bool duefordeletion = m_deletedScreens.contains(screen);
795
796 LOG(VB_GUI, LOG_DEBUG, LOC +
797 QString("ScreenDeleted: Entering (%1)").arg(duefordeletion));
798 // Check that screen wasn't about to be deleted
799 if (duefordeletion)
800 {
801 m_deletedScreens.removeAll(screen);
802 }
803
804 int n = m_screens.indexOf(screen);
805 if (n >= 0)
806 {
807 int num = m_screens.removeAll(screen);
808 LOG(VB_GUI, LOG_DEBUG, LOC +
809 QString("%1 screen removed from screens list").arg(num));
811 }
812 else
813 {
814 LOG(VB_GUI, LOG_DEBUG, LOC +
815 QString("Screen[%1] not found in screens list").arg(screen->m_id));
816 }
817
818 // remove the converted equivalent screen if any
819 if (m_converted.contains(screen))
820 {
821 delete m_converted[screen];
822 }
823 m_converted.remove(screen);
824
825 // search if an application had registered for it
826 if (m_registrations.contains(screen->m_id))
827 {
828 if (!duefordeletion)
829 {
830 if (!m_screenStack)
831 {
832 // we're in the middle of being deleted
833 m_registrations.remove(screen->m_id);
834 m_unregistered.remove(screen->m_id);
835 }
836 else
837 {
838 // don't remove the id from the list, as the application is still registered
839 // re-create the screen
840 auto *newscreen =
843 m_registrations[screen->m_id] = newscreen;
844 // Screen was deleted, add it to suspended list
845 m_suspended.append(screen->m_id);
846 LOG(VB_GUI, LOG_DEBUG, LOC +
847 "ScreenDeleted: Suspending registered screen");
848 }
849 }
850 else
851 {
852 LOG(VB_GUI, LOG_DEBUG, LOC +
853 "ScreenDeleted: Deleting registered screen");
854 }
855 }
856}
857
859{
860 m_screenStack = nullptr;
861}
862
863bool NCPrivate::Queue(const MythNotification &notification)
864{
865 QMutexLocker lock(&m_lock);
866
867 int id = notification.GetId();
868 void *parent = notification.GetParent();
869
870 if (id > 0)
871 {
872 // quick sanity check to ensure the right caller is attempting
873 // to register a notification
874 if (!m_registrations.contains(id) || m_clients[id] != parent)
875 {
876 LOG(VB_GENERAL, LOG_DEBUG, LOC +
877 QString("Queue: 0x%1, not registered for id (%2)")
878 .arg((size_t)parent, QT_POINTER_SIZE, 16, QChar('0'))
879 .arg(id));
880 }
881 else
882 {
883 // check if notification card has been suspended, in which case
884 // refuse all notification updates
885 if (m_suspended.contains(id))
886 {
887 if (notification.type() == MythNotification::kUpdate)
888 return false;
889 // got something else than an update, remove it from the
890 // suspended list
891 m_suspended.removeAll(id);
892 }
893 }
894 }
895 auto *tmp = dynamic_cast<MythNotification*>(notification.clone());
896 if (tmp == nullptr)
897 return false;
898 m_notifications.append(tmp);
899
900 // Tell the GUI thread we have new notifications to process
901 QCoreApplication::postEvent(
903
904 return true;
905}
906
908{
909 QMutexLocker lock(&m_lock);
910
912
913 for (MythNotification *n : std::as_const(m_notifications))
914 {
915 int id = n->GetId();
916 bool created = false;
917 MythNotificationScreen *screen = nullptr;
918
919 if (id > 0)
920 {
921 screen = m_registrations[id];
922 }
923 if (!screen)
924 {
925 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Creating screen %1, \"%2\"")
926 .arg(id).arg(n->GetDescription()));
927
928 // We have a registration, but no screen. Create one and display it
929 screen = CreateScreen(n);
930 if (!screen) // Reads screen definition from xml, and constructs screen
931 {
932 LOG(VB_GENERAL, LOG_ERR, LOC +
933 QString("ProcessQueue: couldn't create required screen"));
934 delete n;
935 continue; // something is wrong ; ignore
936 }
937 if (id > 0)
938 {
939 m_registrations[id] = screen;
940 }
941 created = true;
942 }
943 else
944 {
945 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Using screen %1, \"%2\"")
946 .arg(id).arg(screen->m_title));
947 screen->SetNotification(*n);
948 }
949
950 // if the screen got allocated, but did't read theme yet, do it now
951 if (screen && !screen->m_created)
952 {
953 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Reloading screen %1, \"%2\"")
954 .arg(id).arg(screen->m_title));
955
956
957 if (!screen->Create())
958 {
959 delete screen;
960 delete n;
961 continue;
962 }
963 created = true;
964 }
965
966 if (created || !m_screens.contains(screen))
967 {
968 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Inserting screen %1").arg(id));
969
970 int pos = InsertScreen(screen);
971 // adjust vertical positions
973 }
974
975 screen->doInit();
976 delete n;
977 }
978 m_notifications.clear();
979
981}
982
988{
989 MythNotificationScreen *screen = nullptr;
990
991 if (n)
992 {
993 screen = new MythNotificationScreen(m_screenStack, *n);
994 }
995 else
996 {
997 screen = new MythNotificationScreen(m_screenStack, id);
998 }
999
1000 if (!screen->Create()) // Reads screen definition from xml, and constructs screen
1001 {
1002 // If we can't create the screen then we can't display it, so delete
1003 // and abort
1004 delete screen;
1005 return nullptr;
1006 }
1008 return screen;
1009}
1010
1011int NCPrivate::Register(void *from)
1012{
1013 QMutexLocker lock(&m_lock);
1014
1015 if (!from)
1016 return -1;
1017
1018 m_currentId++;
1019 m_registrations.insert(m_currentId, nullptr);
1020 m_clients.insert(m_currentId, from);
1021
1022 return m_currentId;
1023}
1024
1025void NCPrivate::UnRegister(void *from, int id, bool closeimemdiately)
1026{
1027 QMutexLocker lock(&m_lock);
1028
1029 if (!m_registrations.contains(id))
1030 {
1031 LOG(VB_GENERAL, LOG_ERR, LOC +
1032 QString("UnRegister: 0x%1, no such registration (%2)")
1033 .arg((size_t)from, QT_POINTER_SIZE, 16, QChar('0'))
1034 .arg(id));
1035 return;
1036 }
1037
1038 if (m_clients[id] != from)
1039 {
1040 LOG(VB_GENERAL, LOG_ERR, LOC +
1041 QString("UnRegister: 0x%1, not registered for id (%2")
1042 .arg((size_t)from, QT_POINTER_SIZE, 16, QChar('0'))
1043 .arg(id));
1044 }
1045
1046 // queue the de-registration
1047 m_unregistered[id] = closeimemdiately;
1048
1049 m_clients.remove(id);
1050
1051 // Tell the GUI thread we have something to process
1052 QCoreApplication::postEvent(
1054}
1055
1057{
1058 for (auto *registration : std::as_const(m_registrations))
1059 {
1060 if (registration)
1061 {
1062 m_deletedScreens.append(registration);
1063 }
1064 }
1065 m_registrations.clear();
1066}
1067
1069{
1070 // delete all screens waiting to be deleted
1071 while(!m_deletedScreens.isEmpty())
1072 {
1074 // we remove the screen from the list before deleting the screen
1075 // so the MythScreenType::Exiting() signal won't process it a second time
1076 m_deletedScreens.removeLast();
1077 if (m_screenStack == nullptr &&
1079 {
1080 // our screen stack got deleted already and all its children
1081 // would have been marked for deletion during the
1082 // ScreenStack destruction but not yet deleted as the event loop may
1083 // not be running; so we can leave the screen alone.
1084 // However for clarity, call deleteLater()
1085 // as it is safe to call deleteLater more than once
1086 screen->deleteLater();
1087 }
1088 else if (screen->GetScreenStack() == m_screenStack)
1089 {
1090 screen->GetScreenStack()->PopScreen(screen, true, true);
1091 }
1092 else if (screen->GetScreenStack() == nullptr)
1093 {
1094 // this screen was never added to a screen stack, delete it now
1095 delete screen;
1096 }
1097 }
1098}
1099
1101{
1102 bool needdelete = false;
1103
1104 for (auto it = m_unregistered.begin(); it != m_unregistered.end(); ++it)
1105 {
1106 int id = it.key();
1107 bool closeimemdiately = it.value();
1108 MythNotificationScreen *screen = nullptr;
1109
1110 if (m_registrations.contains(id))
1111 {
1112 screen = m_registrations[id];
1113 if (screen != nullptr && !m_suspended.contains(id))
1114 {
1115 // mark the screen for deletion if no timer is set
1116 if (screen->m_duration <= 0s || closeimemdiately)
1117 {
1118 m_deletedScreens.append(screen);
1119 needdelete = true;
1120 }
1121 }
1122 m_registrations.remove(id);
1123 }
1124
1125 if (m_suspended.contains(id))
1126 {
1127 // screen had been suspended, delete suspended screen
1128 delete screen;
1129 m_suspended.removeAll(id);
1130 }
1131 }
1132 m_unregistered.clear();
1133
1134 if (needdelete)
1135 {
1137 }
1138}
1139
1145{
1146 QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1147 QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1148
1149// if (screen->m_id > 0)
1150// {
1151// // we want a permanent screen; add it after the existing one
1152// for (; it != itend; ++it)
1153// {
1154// if ((*it)->m_id <= 0 ||
1155// (*it)->m_id > screen->m_id)
1156// break; // reached the temporary screens
1157// }
1158// // it points to where we want to insert item
1159// }
1160// else
1161 {
1162 it = itend;
1163 }
1164 it = m_screens.insert(it, screen);
1165
1166 return it - m_screens.begin();
1167}
1168
1174{
1175 QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1176 QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1177
1178 for (; it != itend; ++it)
1179 {
1180 if (*it == screen)
1181 break;
1182 }
1183
1184 if (it != itend)
1185 {
1186 it = m_screens.erase(it);
1187 }
1188
1189 return it - m_screens.begin();
1190}
1191
1196{
1197 QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1198 QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1199
1200 int position = 0;
1201
1202 for (; it != itend; ++it)
1203 {
1204 if ((*it)->IsVisible())
1205 {
1206 (*it)->AdjustIndex(position++, true);
1207 }
1208 else
1209 {
1210 (*it)->AdjustIndex(position, true);
1211 }
1212 if ((*it)->m_fullscreen)
1213 {
1214 position = 0;
1215 continue;
1216 }
1217 }
1218}
1219
1220void NCPrivate::GetNotificationScreens(QList<MythScreenType*> &_screens)
1221{
1222 QList<MythScreenType*> list;
1223 QVector<MythScreenType*> screens;
1224
1225 if (!m_screenStack)
1226 return;
1227
1229
1230 QMutexLocker lock(&m_lock);
1231
1232 m_screenStack->GetScreenList(screens);
1233
1234 int position = 0;
1235 for (auto *item : std::as_const(screens))
1236 {
1237 auto *screen = qobject_cast<MythNotificationScreen*>(item);
1238 if (screen)
1239 {
1240 if ((screen->m_visibility & MythNotification::kPlayback) == 0)
1241 continue;
1242
1243 MythNotificationScreen *newscreen = nullptr;
1244
1245 if (!m_converted.contains(screen))
1246 {
1247 // screen hasn't been created, return it
1248 newscreen = new MythNotificationScreen(nullptr, *screen);
1249 // CreateScreen can never fail, no need to test return value
1250 m_converted[screen] = newscreen;
1251 }
1252 else
1253 {
1254 newscreen = m_converted[screen];
1255 // Copy new content in case it has changed
1256 newscreen->UpdateFrom(*screen);
1257 }
1258 newscreen->SetVisible(true);
1259 newscreen->SetIndex(position++);
1260 if (screen->m_fullscreen)
1261 {
1262 position = 0;
1263 }
1264 list.append(newscreen);
1265 }
1266 else
1267 {
1268 list.append(item);
1269 }
1270 }
1271 _screens = list;
1272}
1273
1275{
1276 return m_screens.size();
1277}
1278
1280{
1281 return m_notifications.size();
1282}
1283
1285{
1286 QMutexLocker lock(&m_lock);
1287
1288 if (m_screens.isEmpty())
1289 return false;
1290
1291 // The top screen is the only currently displayed first, if there's a
1292 // fullscreen notification displayed, it's the last one
1293 MythNotificationScreen *top = m_screens.front();
1294 QList<MythNotificationScreen *>::const_iterator it = m_screens.cend() - 1;
1295
1296 // loop from last to 2nd
1297 for (; it != m_screens.cbegin(); --it)
1298 {
1299 MythNotificationScreen *s = *it;
1300
1301 if (s->m_fullscreen)
1302 {
1303 top = s;
1304 break;
1305 }
1306 }
1307
1308 if (MythDate::current() < top->m_creation.addMSecs(MIN_LIFE))
1309 return false;
1310
1311 // simulate time-out
1312 top->ProcessTimer();
1313 return true;
1314}
1315
1317
1319{
1320 return GetNotificationCenter();
1321}
1322
1324 : d(new NCPrivate())
1325{
1326 const bool isGuiThread =
1327 QThread::currentThread() == QCoreApplication::instance()->thread();
1328
1329 if (!isGuiThread)
1330 {
1331 LOG(VB_GENERAL, LOG_ERR, LOC + "Constructor not called from GUI thread");
1332 }
1333}
1334
1336{
1337 const bool isGuiThread =
1338 QThread::currentThread() == QCoreApplication::instance()->thread();
1339
1340 if (!isGuiThread)
1341 {
1342 LOG(VB_GENERAL, LOG_ERR, LOC + "Destructor not called from GUI thread");
1343 }
1344
1345 delete d;
1346 d = nullptr;
1347}
1348
1350{
1351 return d->Queue(notification);
1352}
1353
1355{
1356 const bool isGuiThread =
1357 QThread::currentThread() == QCoreApplication::instance()->thread();
1358
1359 if (!isGuiThread)
1360 {
1361 LOG(VB_GENERAL, LOG_ERR, LOC + "ProcessQueue not called from GUI thread");
1362 return;
1363 }
1364
1365 d->ProcessQueue();
1366}
1367
1369{
1370 return d->Register(from);
1371}
1372
1373void MythNotificationCenter::UnRegister(void *from, int id, bool closeimemdiately)
1374{
1375 d->UnRegister(from, id, closeimemdiately);
1376}
1377
1379{
1380 const auto *s = qobject_cast<const MythNotificationScreen*>(screen);
1381 if (!s)
1382 return {};
1383 return s->m_expiry;
1384}
1385
1387{
1388 const auto *s = qobject_cast<const MythNotificationScreen*>(screen);
1389 if (!s)
1390 return true;
1391 return s->m_created;
1392}
1393
1394void MythNotificationCenter::GetNotificationScreens(QList<MythScreenType*> &_screens)
1395{
1396 d->GetNotificationScreens(_screens);
1397}
1398
1400{
1401 auto *s = qobject_cast<MythNotificationScreen*>(screen);
1402 if (!s)
1403 return;
1404
1405 if (s->m_created)
1406 {
1407 s->doInit();
1408 }
1409}
1410
1412{
1413 return d->DisplayedNotifications();
1414}
1415
1417{
1418 return d->QueuedNotifications();
1419}
1420
1422{
1423 return d->RemoveFirst();
1424}
1425
1426void ShowNotificationError(const QString &msg,
1427 const QString &from,
1428 const QString &detail,
1429 const VNMask visibility,
1430 const MythNotification::Priority priority)
1431{
1432 ShowNotification(true, msg, from, detail,
1433 QString(), QString(), QString(), -1, -1s, false,
1434 visibility, priority);
1435}
1436
1437void ShowNotification(const QString &msg,
1438 const QString &from,
1439 const QString &detail,
1440 const VNMask visibility,
1441 const MythNotification::Priority priority)
1442{
1443 ShowNotification(false, msg, from, detail,
1444 QString(), QString(), QString(), -1, -1s, false,
1445 visibility, priority);
1446}
1447
1449 const QString &msg,
1450 const QString &origin,
1451 const QString &detail,
1452 const QString &image,
1453 const QString &extra,
1454 const QString &progress_text, float progress,
1455 std::chrono::seconds duration,
1456 bool fullscreen,
1457 const VNMask visibility,
1458 const MythNotification::Priority priority,
1459 const QString &style)
1460{
1462 msg, origin, detail, image, extra, progress_text, progress,
1463 duration, fullscreen, visibility, priority, style);
1464}
1465
1466void ShowNotification(MythNotification::Type type,
1467 const QString &msg,
1468 const QString &origin,
1469 const QString &detail,
1470 const QString &image,
1471 const QString &extra,
1472 const QString &progress_text, float progress,
1473 std::chrono::seconds duration,
1474 bool fullscreen,
1475 const VNMask visibility,
1476 const MythNotification::Priority priority,
1477 const QString &style)
1478{
1479 if (!GetNotificationCenter())
1480 return;
1481
1482 MythNotification *n = nullptr;
1483 DMAP data;
1484
1485 data["minm"] = msg;
1486 data["asar"] = origin.isNull() ? QCoreApplication::translate("(Common)",
1487 "MythTV") : origin;
1488 data["asal"] = detail;
1489 data["asfm"] = extra;
1490
1495 {
1496 n = new MythNotification(type, data);
1497 if (duration != 0s &&
1500 {
1501 // default duration for those type of notifications is 10s
1502 duration = 10s;
1503 }
1504 }
1505 else
1506 {
1507 if (!image.isEmpty())
1508 {
1509 if (progress >= 0)
1510 {
1512 image, data,
1513 progress, progress_text);
1514 }
1515 else
1516 {
1517 n = new MythImageNotification(type, image, data);
1518 }
1519 }
1520 else if (progress >= 0)
1521 {
1523 progress, progress_text, data);
1524 }
1525 else
1526 {
1527 n = new MythNotification(type, data);
1528 }
1529 }
1530 n->SetDuration(duration);
1531 n->SetFullScreen(fullscreen);
1532 n->SetPriority(priority);
1533 n->SetVisibility(visibility);
1534 n->SetStyle(style);
1535
1537 delete n;
1538}
QString GetImagePath() const
void Assign(const QImage &img)
Definition: mythimage.cpp:77
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:52
MythScreenStack * GetMainStack()
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
bool IsExitingToMain() const
void ProcessQueue(void)
ProcessQueue will be called by the GUI event handler and will process all queued MythNotifications an...
void UnRegister(void *from, int id, bool closeimemdiately=false)
Unregister the client.
static void UpdateScreen(MythScreenType *screen)
Will call ::doInit() if the screen is a MythNotificationScreen and ::Create() has been called for it ...
int Register(void *from)
An application can register in which case it will be assigned a reusable screen, which can be modifie...
static QDateTime ScreenExpiryTime(const MythScreenType *screen)
Return when the given screen is going to expire will return an invalid QDateTime if screen isn't a My...
static MythNotificationCenter * GetInstance(void)
returns the MythNotificationCenter singleton
static bool ScreenCreated(const MythScreenType *screen)
Return true if ::Create() has been called on screen.
void GetNotificationScreens(QList< MythScreenType * > &screens)
Return the list of notification screens being currently displayed.
int DisplayedNotifications(void) const
Returns number of notifications currently displayed.
int QueuedNotifications(void) const
Returns number of notifications currently queued.
bool RemoveFirst(void)
Will remove the oldest notification from the stack return true if a screen was removed; or false if n...
bool Queue(const MythNotification &notification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
void PopScreen(MythScreenType *screen, bool allowFade=true, bool deleteScreen=true) override
MythScreenType * GetTopScreen(void) const override
void UpdatePlayback(float progress, const QString &text)
Update playback position information.
void UpdateArtwork(const QImage &image)
Update artwork image.
MythNotificationScreen(MythScreenStack *stack, int id=-1)
void UpdateFrom(const MythNotificationScreen &s)
Copy metadata from another notification.
void SetIndex(int index)
set index, without recalculating coordinates
void AdjustIndex(int by, bool set=false)
void SetSingleShotTimer(std::chrono::seconds s, bool update=false)
void SetNotification(MythNotification &notification)
void UpdateMetaData(const DMAP &data)
Read some DMAP tag to extract title, artist, album and file format.
void AdjustYPosition(void)
Update Y position of the screen All children elements will be relocated.
MythNotification::Priority m_priority
void Init(void) override
Update the various fields of a MythNotificationScreen.
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
static const Type kCheck
void SetVisibility(VNMask nVisibility)
Define a bitmask of Visibility.
Priority GetPriority() const
static const Type kNew
static const Type kUpdate
void * GetParent() const
void SetFullScreen(bool FullScreen)
A notification may request to be displayed in full screen, this request may not be fullfilled should ...
static const Type kError
bool GetFullScreen() const
DMAP GetMetaData() const
void SetStyle(const QString &sStyle)
Contains an alternative notification style. Should a style be defined, the Notification Center will a...
MythEvent * clone() const override
static const Type kWarning
std::chrono::seconds GetDuration() const
void SetDuration(std::chrono::seconds Duration)
Contains a duration during which the notification will be displayed for. The duration is informative ...
static const Type kBusy
QString GetStyle() const
VNMask GetVisibility() const
void SetPriority(Priority nPriority)
Reserved for future use, not implemented at this stage.
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
Wrapper around QPoint allowing us to handle percentage and other relative values for positioning in m...
Definition: mythrect.h:89
void setY(const QString &sY)
Definition: mythrect.cpp:540
QString getY(void) const
Definition: mythrect.cpp:566
QString getHeight(void) const
Definition: mythrect.cpp:377
virtual void PopScreen(MythScreenType *screen=nullptr, bool allowFade=true, bool deleteScreen=true)
void GetScreenList(QVector< MythScreenType * > &screens)
virtual void RecalculateDrawOrder(void)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
QVector< MythScreenType * > m_drawOrder
QVector< MythScreenType * > m_children
MythScreenType * m_topScreen
virtual MythScreenType * GetTopScreen(void) const
QVector< MythScreenType * > m_toDelete
Screen in which all other widgets are contained and rendered.
void SetFullscreen(bool full)
void SetDeleting(bool deleting)
virtual void aboutToHide(void)
MythScreenStack * GetScreenStack() const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
bool IsDeleting(void) const
bool IsFullscreen(void) const
virtual void SetTextFromMap(const InfoMap &infoMap)
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:98
bool Load(bool allowLoadInBackground=true, bool forceStat=false)
Load the image(s), wraps ImageLoader::LoadImage()
void SetFilename(const QString &filename)
Must be followed by a call to Load() to load the image.
void SetImage(MythImage *img)
Should not be used unless absolutely necessary since it bypasses the image caching and threaded loade...
void Reset(void) override
Reset the image back to the default defined in the theme.
Progress bar widget.
void SetUsed(int value)
void SetStart(int value)
void SetTotal(int value)
void SetVisible(bool visible) override
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
This widget is used for grouping other widgets for display when a particular named state is called.
bool DisplayState(const QString &name)
All purpose text widget, displays a text string.
Definition: mythuitext.h:29
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
Definition: mythuitext.cpp:65
virtual void SetVisible(bool visible)
virtual MythPainter * GetPainter(void)
void AdjustAlpha(int mode, int alphachange, int minalpha=0, int maxalpha=255)
Definition: mythuitype.cpp:928
void SetRedraw(void)
Definition: mythuitype.cpp:313
virtual MythRect GetArea(void) const
If the object has a minimum area defined, return it, other wise return the default area.
Definition: mythuitype.cpp:885
void SetPosition(int x, int y)
Convenience method, calls SetPosition(const MythPoint&) Override that instead to change functionality...
Definition: mythuitype.cpp:533
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:138
void SetAlpha(int newalpha)
Definition: mythuitype.cpp:942
virtual MythPoint GetPosition(void) const
Definition: mythuitype.cpp:564
int DisplayedNotifications(void) const
Returns number of notifications currently displayed.
QMap< int, bool > m_unregistered
void RefreshScreenPosition(int from=0)
Re-position screens on display.
QList< MythNotificationScreen * > m_deletedScreens
int InsertScreen(MythNotificationScreen *screen)
Insert screen into list of screens.
MythNotificationScreenStack * m_screenStack
int RemoveScreen(MythNotificationScreen *screen)
Remove screen from list of screens.
QMap< int, void * > m_clients
void DeleteUnregistered(void)
QMap< MythNotificationScreen *, MythNotificationScreen * > m_converted
void ScreenStackDeleted(void)
bool Queue(const MythNotification &notification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
bool RemoveFirst(void)
Will remove the oldest notification from the stack return true if a screen was removed; or false if n...
MythNotificationScreen * CreateScreen(MythNotification *notification, int id=-1)
CreateScreen will create a MythNotificationScreen instance.
void DeleteAllRegistrations(void)
void DeleteAllScreens(void)
void GetNotificationScreens(QList< MythScreenType * > &screens)
Return the list of notification screens being currently displayed.
void ScreenDeleted(void)
Remove screen from screens list.
void ProcessQueue(void)
ProcessQueue will be called by the GUI event handler and will process all queued MythNotifications an...
int QueuedNotifications(void) const
Returns number of notifications currently queued.
MythNotificationScreenStack * m_originalScreenStack
void UnRegister(void *from, int id, bool closeimemdiately=false)
Unregister the client.
QMap< int, MythNotificationScreen * > m_registrations
int Register(void *from)
An application can register in which case it will be assigned a reusable screen, which can be modifie...
QList< MythNotificationScreen * > m_screens
QList< MythNotification * > m_notifications
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
static guint32 * tmp
Definition: goom_core.cpp:26
static const iso6937table * d
bool progress
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythNotificationCenter * GetNotificationCenter(void)
MythMainWindow * GetMythMainWindow(void)
unsigned int VNMask
QMap< QString, QString > DMAP
#define LOC
static constexpr std::chrono::milliseconds DEFAULT_DURATION
void ShowNotificationError(const QString &msg, const QString &from, const QString &detail, const VNMask visibility, const MythNotification::Priority priority)
convenience utility to display error message as notification
static constexpr int8_t HGAP
void ShowNotification(const QString &msg, const QString &from, const QString &detail, const VNMask visibility, const MythNotification::Priority priority)
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
def error(message)
Definition: smolt.py:409