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 it = m_DrawOrder.begin(); it != m_DrawOrder.end(); ++it)
96  {
97  if (*it != screen && !(*it)->IsDeleting())
98  {
99  m_topScreen = (*it);
100  (*it)->SetAlpha(255);
101  if (poppedFullscreen)
102  (*it)->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  foreach(MythNotification *n, m_notifications)
777  {
778  delete n;
779  }
780  m_notifications.clear();
781 
782  delete m_screenStack;
784 }
785 
791 {
792  auto *screen = static_cast<MythNotificationScreen*>(sender());
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 =
842  connect(newscreen, SIGNAL(ScreenDeleted()), this, SLOT(ScreenDeleted()));
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 
863 bool NCPrivate::Queue(const MythNotification &notification)
864 {
865  QMutexLocker lock(&m_lock);
866 
867  int id = notification.GetId();
868  void *parent = notification.GetParent();
869 
870  auto *tmp = static_cast<MythNotification*>(notification.clone());
871  if (id > 0)
872  {
873  // quick sanity check to ensure the right caller is attempting
874  // to register a notification
875  if (!m_registrations.contains(id) || m_clients[id] != parent)
876  {
877  LOG(VB_GENERAL, LOG_DEBUG, LOC +
878  QString("Queue: 0x%1, not registered for id (%2)")
879  .arg((size_t)parent, QT_POINTER_SIZE, 16, QChar('0'))
880  .arg(id));
881  }
882  else
883  {
884  // check if notification card has been suspended, in which case
885  // refuse all notification updates
886  if (m_suspended.contains(id))
887  {
888  if (notification.type() == MythNotification::Update)
889  {
890  delete tmp;
891  return false;
892  }
893  // got something else than an update, remove it from the
894  // suspended list
895  m_suspended.removeAll(id);
896  }
897  }
898  }
899  m_notifications.append(tmp);
900 
901  // Tell the GUI thread we have new notifications to process
902  QCoreApplication::postEvent(
904 
905  return true;
906 }
907 
909 {
910  QMutexLocker lock(&m_lock);
911 
913 
914  foreach (MythNotification *n, m_notifications)
915  {
916  int id = n->GetId();
917  bool created = false;
918  MythNotificationScreen *screen = nullptr;
919 
920  if (id > 0)
921  {
922  screen = m_registrations[id];
923  }
924  if (!screen)
925  {
926  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Creating screen %1, \"%2\"")
927  .arg(id).arg(n->GetDescription()));
928 
929  // We have a registration, but no screen. Create one and display it
930  screen = CreateScreen(n);
931  if (!screen) // Reads screen definition from xml, and constructs screen
932  {
933  LOG(VB_GENERAL, LOG_ERR, LOC +
934  QString("ProcessQueue: couldn't create required screen"));
935  delete n;
936  continue; // something is wrong ; ignore
937  }
938  if (id > 0)
939  {
940  m_registrations[id] = screen;
941  }
942  created = true;
943  }
944  else
945  {
946  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Using screen %1, \"%2\"")
947  .arg(id).arg(screen->m_title));
948  screen->SetNotification(*n);
949  }
950 
951  // if the screen got allocated, but did't read theme yet, do it now
952  if (screen && !screen->m_created)
953  {
954  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Reloading screen %1, \"%2\"")
955  .arg(id).arg(screen->m_title));
956 
957 
958  if (!screen->Create())
959  {
960  delete screen;
961  delete n;
962  continue;
963  }
964  created = true;
965  }
966 
967  if (created || !m_screens.contains(screen))
968  {
969  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Inserting screen %1").arg(id));
970 
971  int pos = InsertScreen(screen);
972  // adjust vertical positions
974  }
975 
976  screen->doInit();
977  delete n;
978  }
979  m_notifications.clear();
980 
982 }
983 
989 {
990  MythNotificationScreen *screen = nullptr;
991 
992  if (n)
993  {
994  screen = new MythNotificationScreen(m_screenStack, *n);
995  }
996  else
997  {
998  screen = new MythNotificationScreen(m_screenStack, id);
999  }
1000 
1001  if (!screen->Create()) // Reads screen definition from xml, and constructs screen
1002  {
1003  // If we can't create the screen then we can't display it, so delete
1004  // and abort
1005  delete screen;
1006  return nullptr;
1007  }
1008  connect(screen, SIGNAL(ScreenDeleted()), this, SLOT(ScreenDeleted()));
1009  return screen;
1010 }
1011 
1012 int NCPrivate::Register(void *from)
1013 {
1014  QMutexLocker lock(&m_lock);
1015 
1016  if (!from)
1017  return -1;
1018 
1019  m_currentId++;
1020  m_registrations.insert(m_currentId, nullptr);
1021  m_clients.insert(m_currentId, from);
1022 
1023  return m_currentId;
1024 }
1025 
1026 void NCPrivate::UnRegister(void *from, int id, bool closeimemdiately)
1027 {
1028  QMutexLocker lock(&m_lock);
1029 
1030  if (!m_registrations.contains(id))
1031  {
1032  LOG(VB_GENERAL, LOG_ERR, LOC +
1033  QString("UnRegister: 0x%1, no such registration (%2)")
1034  .arg((size_t)from, QT_POINTER_SIZE, 16, QChar('0'))
1035  .arg(id));
1036  return;
1037  }
1038 
1039  if (m_clients[id] != from)
1040  {
1041  LOG(VB_GENERAL, LOG_ERR, LOC +
1042  QString("UnRegister: 0x%1, not registered for id (%2")
1043  .arg((size_t)from, QT_POINTER_SIZE, 16, QChar('0'))
1044  .arg(id));
1045  }
1046 
1047  // queue the de-registration
1048  m_unregistered[id] = closeimemdiately;
1049 
1050  m_clients.remove(id);
1051 
1052  // Tell the GUI thread we have something to process
1053  QCoreApplication::postEvent(
1055 }
1056 
1058 {
1059  QMap<int, MythNotificationScreen*>::iterator it = m_registrations.begin();
1060 
1061  for (; it != m_registrations.end(); ++it)
1062  {
1063  if (*it)
1064  {
1065  m_deletedScreens.append(*it);
1066  }
1067  }
1068  m_registrations.clear();
1069 }
1070 
1072 {
1073  // delete all screens waiting to be deleted
1074  while(!m_deletedScreens.isEmpty())
1075  {
1076  MythNotificationScreen *screen = m_deletedScreens.last();
1077  // we remove the screen from the list before deleting the screen
1078  // so the MythScreenType::Exiting() signal won't process it a second time
1079  m_deletedScreens.removeLast();
1080  if (m_screenStack == nullptr &&
1082  {
1083  // our screen stack got deleted already and all its children
1084  // would have been marked for deletion during the
1085  // ScreenStack destruction but not yet deleted as the event loop may
1086  // not be running; so we can leave the screen alone.
1087  // However for clarity, call deleteLater()
1088  // as it is safe to call deleteLater more than once
1089  screen->deleteLater();
1090  }
1091  else if (screen->GetScreenStack() == m_screenStack)
1092  {
1093  screen->GetScreenStack()->PopScreen(screen, true, true);
1094  }
1095  else if (screen->GetScreenStack() == nullptr)
1096  {
1097  // this screen was never added to a screen stack, delete it now
1098  delete screen;
1099  }
1100  }
1101 }
1102 
1104 {
1105  QMap<int,bool>::iterator it = m_unregistered.begin();
1106  bool needdelete = false;
1107 
1108  for (; it != m_unregistered.end(); ++it)
1109  {
1110  int id = it.key();
1111  bool closeimemdiately = it.value();
1112  MythNotificationScreen *screen = nullptr;
1113 
1114  if (m_registrations.contains(id))
1115  {
1116  screen = m_registrations[id];
1117  if (screen != nullptr && !m_suspended.contains(id))
1118  {
1119  // mark the screen for deletion if no timer is set
1120  if (screen->m_duration <= 0 || closeimemdiately)
1121  {
1122  m_deletedScreens.append(screen);
1123  needdelete = true;
1124  }
1125  }
1126  m_registrations.remove(id);
1127  }
1128 
1129  if (m_suspended.contains(id))
1130  {
1131  // screen had been suspended, delete suspended screen
1132  delete screen;
1133  m_suspended.removeAll(id);
1134  }
1135  }
1136  m_unregistered.clear();
1137 
1138  if (needdelete)
1139  {
1140  DeleteAllScreens();
1141  }
1142 }
1143 
1149 {
1150  QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1151  QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1152 
1153 // if (screen->m_id > 0)
1154 // {
1155 // // we want a permanent screen; add it after the existing one
1156 // for (; it != itend; ++it)
1157 // {
1158 // if ((*it)->m_id <= 0 ||
1159 // (*it)->m_id > screen->m_id)
1160 // break; // reached the temporary screens
1161 // }
1162 // // it points to where we want to insert item
1163 // }
1164 // else
1165  {
1166  it = itend;
1167  }
1168  it = m_screens.insert(it, screen);
1169 
1170  return it - m_screens.begin();
1171 }
1172 
1178 {
1179  QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1180  QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1181 
1182  for (; it != itend; ++it)
1183  {
1184  if (*it == screen)
1185  break;
1186  }
1187 
1188  if (it != itend)
1189  {
1190  it = m_screens.erase(it);
1191  }
1192 
1193  return it - m_screens.begin();
1194 }
1195 
1200 {
1201  QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1202  QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1203 
1204  int position = 0;
1205 
1206  for (; it != itend; ++it)
1207  {
1208  if ((*it)->IsVisible())
1209  {
1210  (*it)->AdjustIndex(position++, true);
1211  }
1212  else
1213  {
1214  (*it)->AdjustIndex(position, true);
1215  }
1216  if ((*it)->m_fullscreen)
1217  {
1218  position = 0;
1219  continue;
1220  }
1221  }
1222 }
1223 
1224 void NCPrivate::GetNotificationScreens(QList<MythScreenType*> &_screens)
1225 {
1226  QList<MythScreenType*> list;
1227  QVector<MythScreenType*> screens;
1228 
1229  if (!m_screenStack)
1230  return;
1231 
1233 
1234  QMutexLocker lock(&m_lock);
1235 
1236  m_screenStack->GetScreenList(screens);
1237 
1238  QVector<MythScreenType*>::const_iterator it = screens.begin();
1239  QVector<MythScreenType*>::const_iterator itend = screens.end();
1240 
1241  int position = 0;
1242  for (; it != itend; ++it)
1243  {
1244  auto *screen = dynamic_cast<MythNotificationScreen*>(*it);
1245  if (screen)
1246  {
1247  if ((screen->m_visibility & MythNotification::kPlayback) == 0)
1248  continue;
1249 
1250  MythNotificationScreen *newscreen = nullptr;
1251 
1252  if (!m_converted.contains(screen))
1253  {
1254  // screen hasn't been created, return it
1255  newscreen = new MythNotificationScreen(nullptr, *screen);
1256  // CreateScreen can never fail, no need to test return value
1257  m_converted[screen] = newscreen;
1258  }
1259  else
1260  {
1261  newscreen = m_converted[screen];
1262  // Copy new content in case it has changed
1263  newscreen->UpdateFrom(*screen);
1264  }
1265  newscreen->SetVisible(true);
1266  newscreen->SetIndex(position++);
1267  if (screen->m_fullscreen)
1268  {
1269  position = 0;
1270  }
1271  list.append(newscreen);
1272  }
1273  else
1274  {
1275  list.append(*it);
1276  }
1277  }
1278  _screens = list;
1279 }
1280 
1282 {
1283  return m_screens.size();
1284 }
1285 
1287 {
1288  return m_notifications.size();
1289 }
1290 
1292 {
1293  QMutexLocker lock(&m_lock);
1294 
1295  if (m_screens.isEmpty())
1296  return false;
1297 
1298  // The top screen is the only currently displayed first, if there's a
1299  // fullscreen notification displayed, it's the last one
1300  MythNotificationScreen *top = m_screens.front();
1301  QList<MythNotificationScreen *>::const_iterator it = m_screens.end() - 1;
1302 
1303  // loop from last to 2nd
1304  for (; it != m_screens.begin(); --it)
1305  {
1306  MythNotificationScreen *s = *it;
1307 
1308  if (s->m_fullscreen)
1309  {
1310  top = s;
1311  break;
1312  }
1313  }
1314 
1315  if (MythDate::current() < top->m_creation.addMSecs(MIN_LIFE))
1316  return false;
1317 
1318  // simulate time-out
1319  top->ProcessTimer();
1320  return true;
1321 }
1322 
1324 
1326 {
1327  return GetNotificationCenter();
1328 }
1329 
1331  : d(new NCPrivate())
1332 {
1333  const bool isGuiThread =
1334  QThread::currentThread() == QCoreApplication::instance()->thread();
1335 
1336  if (!isGuiThread)
1337  {
1338  LOG(VB_GENERAL, LOG_ERR, LOC + "Constructor not called from GUI thread");
1339  }
1340 }
1341 
1343 {
1344  const bool isGuiThread =
1345  QThread::currentThread() == QCoreApplication::instance()->thread();
1346 
1347  if (!isGuiThread)
1348  {
1349  LOG(VB_GENERAL, LOG_ERR, LOC + "Destructor not called from GUI thread");
1350  }
1351 
1352  delete d;
1353  d = nullptr;
1354 }
1355 
1357 {
1358  return d->Queue(notification);
1359 }
1360 
1362 {
1363  const bool isGuiThread =
1364  QThread::currentThread() == QCoreApplication::instance()->thread();
1365 
1366  if (!isGuiThread)
1367  {
1368  LOG(VB_GENERAL, LOG_ERR, LOC + "ProcessQueue not called from GUI thread");
1369  return;
1370  }
1371 
1372  d->ProcessQueue();
1373 }
1374 
1376 {
1377  return d->Register(from);
1378 }
1379 
1380 void MythNotificationCenter::UnRegister(void *from, int id, bool closeimemdiately)
1381 {
1382  d->UnRegister(from, id, closeimemdiately);
1383 }
1384 
1386 {
1387  const auto *s = dynamic_cast<const MythNotificationScreen*>(screen);
1388  if (!s)
1389  return QDateTime();
1390  return s->m_expiry;
1391 }
1392 
1394 {
1395  const auto *s = dynamic_cast<const MythNotificationScreen*>(screen);
1396  if (!s)
1397  return true;
1398  return s->m_created;
1399 }
1400 
1401 void MythNotificationCenter::GetNotificationScreens(QList<MythScreenType*> &_screens)
1402 {
1403  d->GetNotificationScreens(_screens);
1404 }
1405 
1407 {
1408  auto *s = dynamic_cast<MythNotificationScreen*>(screen);
1409  if (!s)
1410  return;
1411 
1412  if (s->m_created)
1413  {
1414  s->doInit();
1415  }
1416 }
1417 
1419 {
1420  return d->DisplayedNotifications();
1421 }
1422 
1424 {
1425  return d->QueuedNotifications();
1426 }
1427 
1429 {
1430  return d->RemoveFirst();
1431 }
1432 
1433 void ShowNotificationError(const QString &msg,
1434  const QString &from,
1435  const QString &detail,
1436  const VNMask visibility,
1437  const MythNotification::Priority priority)
1438 {
1439  ShowNotification(true, msg, from, detail,
1440  QString(), QString(), QString(), -1, -1, false,
1441  visibility, priority);
1442 }
1443 
1444 void ShowNotification(const QString &msg,
1445  const QString &from,
1446  const QString &detail,
1447  const VNMask visibility,
1448  const MythNotification::Priority priority)
1449 {
1450  ShowNotification(false, msg, from, detail,
1451  QString(), QString(), QString(), -1, -1, false,
1452  visibility, priority);
1453 }
1454 
1456  const QString &msg,
1457  const QString &origin,
1458  const QString &detail,
1459  const QString &image,
1460  const QString &extra,
1461  const QString &progress_text, float progress,
1462  int duration,
1463  bool fullscreen,
1464  const VNMask visibility,
1465  const MythNotification::Priority priority,
1466  const QString &style)
1467 {
1469  msg, origin, detail, image, extra, progress_text, progress,
1470  duration, fullscreen, visibility, priority, style);
1471 }
1472 
1473 void ShowNotification(MythNotification::Type type,
1474  const QString &msg,
1475  const QString &origin,
1476  const QString &detail,
1477  const QString &image,
1478  const QString &extra,
1479  const QString &progress_text, float progress,
1480  int duration,
1481  bool fullscreen,
1482  const VNMask visibility,
1483  const MythNotification::Priority priority,
1484  const QString &style)
1485 {
1486  if (!GetNotificationCenter())
1487  return;
1488 
1489  MythNotification *n = nullptr;
1490  DMAP data;
1491 
1492  data["minm"] = msg;
1493  data["asar"] = origin.isNull() ? QCoreApplication::translate("(Common)",
1494  "MythTV") : origin;
1495  data["asal"] = detail;
1496  data["asfm"] = extra;
1497 
1498  if (type == MythNotification::Error ||
1502  {
1503  n = new MythNotification(type, data);
1504  if (!duration &&
1507  {
1508  // default duration for those type of notifications is 10s
1509  duration = 10;
1510  }
1511  }
1512  else
1513  {
1514  if (!image.isEmpty())
1515  {
1516  if (progress >= 0)
1517  {
1518  n = new MythMediaNotification(type,
1519  image, data,
1520  progress, progress_text);
1521  }
1522  else
1523  {
1524  n = new MythImageNotification(type, image, data);
1525  }
1526  }
1527  else if (progress >= 0)
1528  {
1530  progress, progress_text, data);
1531  }
1532  else
1533  {
1534  n = new MythNotification(type, data);
1535  }
1536  }
1537  n->SetDuration(duration);
1538  n->SetFullScreen(fullscreen);
1539  n->SetPriority(priority);
1540  n->SetVisibility(visibility);
1541  n->SetStyle(style);
1542 
1544  delete n;
1545 }
void AdjustIndex(int by, bool set=false)
MythScreenStack * GetScreenStack() const
void ScreenStackDeleted(void)
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)
QString GetDescription(void) const
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.
#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:927
static void error(const char *str,...)
Definition: vbi.c:42
void SetRedraw(void)
Definition: mythuitype.cpp:295
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 MythNotificationCenter * GetInstance(void)
returns the MythNotificationCenter singleton
static guint32 * tmp
Definition: goom_core.c:35
QString getHeight(void) const
Definition: mythrect.cpp:326
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:519
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
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:866
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 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:909
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...
void setY(const QString &sY)
Definition: mythrect.cpp:460
bool keyPressEvent(QKeyEvent *) override
Key event handler.
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
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:545
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:86
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:486
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:132
QList< MythNotification * > m_notifications
MythNotificationCenter * GetNotificationCenter(void)
void SetNotification(MythNotification &notification)
void SetVisibility(VNMask n)
define a bitmask of Visibility
DMAP GetMetaData(void) const
int QueuedNotifications(void) const
Returns number of notifications currently queued.