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