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  QVector<MythScreenType *>::Iterator it;
96  for (it = m_DrawOrder.begin(); it != m_DrawOrder.end(); ++it)
97  {
98  if (*it != screen && !(*it)->IsDeleting())
99  {
100  m_topScreen = (*it);
101  (*it)->SetAlpha(255);
102  if (poppedFullscreen)
103  (*it)->aboutToShow();
104  }
105  }
106  }
107 
108  if (m_topScreen)
109  {
111  }
112  else
113  {
114  // Screen still needs to be redrawn if we have popped the last screen
115  // off the popup stack, or similar
116  if (mainwindow->GetMainStack())
117  {
118  MythScreenType *mainscreen = mainwindow->GetMainStack()->GetTopScreen();
119  if (mainscreen)
120  mainscreen->SetRedraw();
121  }
122  }
123 }
124 
126 {
127  if (m_Children.isEmpty())
128  return nullptr;
129  // The top screen is the only currently displayed first, if there's a
130  // fullscreen notification displayed, it's the last one
131  MythScreenType *top = m_Children.front();
132  QVector<MythScreenType *>::const_iterator it = m_Children.end() - 1;
133 
134  // loop from last to 2nd
135  for (; it != m_Children.begin(); --it)
136  {
137  MythNotificationScreen *s = dynamic_cast<MythNotificationScreen *>(*it);
138 
139  if (!s)
140  {
141  // if for whatever reason it's not a notification on our screen
142  // it will be dropped as we don't know how it appears
143  top = s;
144  continue;
145  }
146  if (s->m_fullscreen)
147  {
148  top = s;
149  break;
150  }
151  }
152  return top;
153 }
154 
156 
158  int id)
159  : MythScreenType(stack, "mythnotification"), m_id(id),
160  m_timer(new QTimer(this))
161 {
162  // Set timer if need be
164  connect(m_timer, SIGNAL(timeout()), this, SLOT(ProcessTimer()));
165 }
166 
168  MythNotification &notification)
169  : MythScreenType(stack, "mythnotification"), m_id(notification.GetId()),
170  m_duration(notification.GetDuration()),
171  m_timer(new QTimer(this))
172 {
173  SetNotification(notification);
174  connect(m_timer, SIGNAL(timeout()), this, SLOT(ProcessTimer()));
175 }
176 
178  const MythNotificationScreen &s)
179  : MythScreenType(stack, "mythnotification"),
180  m_id(s.m_id),
181  m_image(s.m_image),
182  m_imagePath(s.m_imagePath),
183  m_title(s.m_title),
184  m_origin(s.m_origin),
185  m_description(s.m_description),
186  m_extra(s.m_extra),
187  m_duration(s.m_duration),
188  m_progress(s.m_progress),
189  m_progresstext(s.m_progresstext),
190  m_fullscreen(s.m_fullscreen),
191  m_content(s.m_content),
192  m_update(s.m_content), // so all fields are initialised regardless of notification type
193  m_type(s.m_type),
194  m_timer(new QTimer(this)),
195  m_style(s.m_style)
196 {
197  connect(m_timer, SIGNAL(timeout()), this, SLOT(ProcessTimer()));
198 }
199 
201 {
202  m_timer->stop();
203  LOG(VB_GUI, LOG_DEBUG, LOC + "MythNotificationScreen dtor");
204  // We can't rely on Exiting() default MythScreenType signal as
205  // by the time it is emitted, the destructor would have already been called
206  // making the members unusable
207  emit ScreenDeleted();
208 }
209 
211 {
212  bool update;
213  m_update = kNone;
214 
215  m_type = notification.type();
216 
221  {
222  m_update |= kImage;
223  update = false;
224  }
225  else if (m_type == MythNotification::Update)
226  {
227  update = true;
228  }
229  else
230  {
231  update = false;
232  }
233 
234  MythImageNotification *img =
235  dynamic_cast<MythImageNotification*>(&notification);
236  if (img)
237  {
238  QString path = img->GetImagePath();
239 
240  m_update |= kImage;
241 
242  if (path.isNull())
243  {
244  UpdateArtwork(img->GetImage());
245  }
246  else
247  {
248  UpdateArtwork(path);
249  }
250  }
251 
253  dynamic_cast<MythPlaybackNotification*>(&notification);
254  if (play)
255  {
256  UpdatePlayback(play->GetProgress(), play->GetProgressText());
257 
258  m_update |= kDuration;
259  }
260 
261  MythMediaNotification *media =
262  dynamic_cast<MythMediaNotification*>(&notification);
263  if (media && m_imagePath.isEmpty() && m_image.isNull())
264  {
265  m_update |= kNoArtwork;
266  }
267 
268  if (!notification.GetMetaData().isEmpty())
269  {
270  UpdateMetaData(notification.GetMetaData());
271  m_update |= kMetaData;
272  }
273  else if (!update)
274  {
275  // A new notification, will always update the metadata field
276  m_update |= kMetaData;
277  }
278 
279  if (!notification.GetStyle().isEmpty())
280  {
281  m_style = notification.GetStyle();
282  m_update |= kStyle;
283  }
284 
285  if (!update)
286  {
288  m_fullscreen = notification.GetFullScreen();
289  }
290 
291  m_duration = notification.GetDuration();
292  m_visibility = notification.GetVisibility();
293  if (!m_visibility)
294  {
295  // no visibility is all visibility to get around QVariant always making 0 the default
296  m_visibility = ~0;
297  }
298  m_priority = notification.GetPriority();
299 
300  // Set timer if need be
302 
303  // We need to re-run init
304  m_refresh = true;
305 }
306 
308 {
309  bool foundtheme = false;
310 
311  // Load the theme for this screen
312  // The xml file containing the screen definition is airplay-ui.xml in this
313  // example, the name of the screen in the xml is airplaypicture. This
314  // should make sense when you look at the xml below
315 
316  QString theme;
317  if (m_fullscreen)
318  {
319  theme = "notification-full";
320  }
321  else if (m_content & kImage)
322  {
323  theme = "notification-image";
324  }
325  else
326  {
327  theme = "notification";
328  }
329 
330  QString theme_attempt = theme + (m_style.isEmpty() ? "" : "-" + m_style);
331 
332  // See if we have an alternative theme available as defined in the notification
333  foundtheme = LoadWindowFromXML("notification-ui.xml", theme_attempt, this);
334  if (!foundtheme && theme_attempt != theme)
335  {
336  // if not, default to the main one
337  foundtheme = LoadWindowFromXML("notification-ui.xml", theme, this);
338  }
339 
340  if (!foundtheme) // If we cannot load the theme for any reason ...
341  return false;
342 
343  m_artworkImage = dynamic_cast<MythUIImage*>(GetChild("image"));
344  m_titleText = dynamic_cast<MythUIText*>(GetChild("title"));
345  m_originText = dynamic_cast<MythUIText*>(GetChild("origin"));
346  m_descriptionText = dynamic_cast<MythUIText*>(GetChild("description"));
347  m_extraText = dynamic_cast<MythUIText*>(GetChild("extra"));
348  m_progresstextText = dynamic_cast<MythUIText*>(GetChild("progress_text"));
349  m_progressBar = dynamic_cast<MythUIProgressBar*>(GetChild("progress"));
350  m_errorState = dynamic_cast<MythUIStateType*>(GetChild("errorstate"));
351  m_mediaState = dynamic_cast<MythUIStateType*>(GetChild("mediastate"));
352 
353  SetErrorState();
354 
355  if (m_mediaState && (m_update & kImage))
356  {
357  m_mediaState->DisplayState((m_content & kNoArtwork) ? "noartwork" : "ok");
358  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Create: Set media state to %1").arg((m_content & kNoArtwork) ? "noartwork" : "ok"));
359  }
360 
361  // store original position
363  m_created = true;
364 
366  {
367  // Visibility will be set automatically during video playback
368  // so can be ignored here
369  SetVisible(false);
370  }
371 
372  // We need to re-run init
373  m_refresh = true;
374 
375  return true;
376 }
377 
382 {
383  if (!m_refresh) // nothing got changed so far, return
384  return;
385 
386  AdjustYPosition();
387 
388  if (m_artworkImage && (m_update & kImage))
389  {
390  if (!m_imagePath.isNull())
391  {
392  // We have a path to the image, use it
394  m_artworkImage->Load();
395  }
396  else if (!m_image.isNull())
397  {
398  // We don't have a path to the image, but the image itself
400  img->Assign(m_image);
401  m_artworkImage->SetImage(img);
402  img->DecrRef();
403  }
404  else
405  {
406  // Will default to displaying whatever placeholder image is defined
407  // in the xml by the themer, means we can show _something_ rather than
408  // a big empty hole. Generally you always want to call Reset() in
409  // these circumstances
411  }
412  }
413 
414  if (m_update != kNone)
415  {
416  InfoMap tmap;
417 
418  tmap["title"] = m_title;
419  if (m_update & kImage)
420  {
421  tmap["image"] = m_imagePath;
422  }
423  tmap["origin"] = m_origin;
424  tmap["description"] = m_description;
425  tmap["extra"] = m_extra;
426  if (m_update & kDuration)
427  {
428  tmap["progress_text"] = m_progresstext;
429  tmap["progress"] = QString("%1").arg((int)(m_progress * 100));
430  }
431  SetTextFromMap(tmap);
432  }
433 
434  if (m_update & kMetaData)
435  {
436  if (m_titleText && m_title.isNull())
437  {
438  m_titleText->Reset();
439  }
440  if (m_originText && m_origin.isNull())
441  {
442  m_originText->Reset();
443  }
444  if (m_descriptionText && m_description.isNull())
445  {
447  }
448  if (m_extraText && m_extra.isNull())
449  {
450  m_extraText->Reset();
451  }
452  }
453 
454  if (m_update & kDuration)
455  {
456  if (m_progresstextText && m_progresstext.isEmpty())
457  {
459  }
460  if (m_progressBar)
461  {
462  if (m_progress >= 0)
463  {
465  m_progressBar->SetTotal(100);
467  }
468  else
469  {
470  // Same as above, calling Reset() allows for a sane, themer defined
471  //default to be displayed
472  m_progressBar->Reset();
473  }
474  }
475  }
476 
477  if (m_progressBar)
478  {
480 
481  }
482 
483  SetErrorState();
484 
485  if (m_mediaState && (m_update & kImage))
486  {
487  m_mediaState->DisplayState((m_update & kNoArtwork) ? "noartwork" : "ok");
488  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Init: Set media state to %1").arg((m_update & kNoArtwork) ? "noartwork" : "ok"));
489  }
490 
491  // No field will be refreshed the next time unless specified otherwise
492  m_update = kNone;
493 
494  if (GetScreenStack() && !m_added)
495  {
496  GetScreenStack()->AddScreen(this);
497  m_added = true;
498  }
499  m_refresh = false;
500 }
501 
503 {
504  if (!m_errorState)
505  return;
506 
507  const char *state;
508 
510  {
511  state = "error";
512  }
513  else if (m_type == MythNotification::Warning)
514  {
515  state = "warning";
516  }
517  else if (m_type == MythNotification::Check)
518  {
519  state = "check";
520  }
521  else if (m_type == MythNotification::Busy)
522  {
523  state = "busy";
524  }
525  else
526  {
527  state = "ok";
528  }
529  LOG(VB_GUI, LOG_DEBUG, LOC + QString("SetErrorState: Set error state to %1").arg(state));
530  m_errorState->DisplayState(state);
531 }
532 
537 void MythNotificationScreen::UpdateArtwork(const QImage &image)
538 {
539  m_image = image;
540  // We need to re-run init
541  m_refresh = true;
542 }
543 
548 void MythNotificationScreen::UpdateArtwork(const QString &image)
549 {
550  m_imagePath = image;
551  // We need to re-run init
552  m_refresh = true;
553 }
554 
562 {
563  QString tmp;
564 
565  tmp = data["minm"];
566  if (!(tmp.isNull() && (m_update & kMetaData)))
567  {
568  m_title = tmp;
569  }
570  tmp = data["asar"];
571  if (!(tmp.isNull() && (m_update & kMetaData)))
572  {
573  m_origin = tmp;
574  }
575  tmp = data["asal"];
576  if (!(tmp.isNull() && (m_update & kMetaData)))
577  {
578  m_description = tmp;
579  }
580  tmp = data["asfm"];
581  if (!(tmp.isNull() && (m_update & kMetaData)))
582  {
583  m_extra = tmp;
584  }
585  // We need to re-run init
586  m_refresh = true;
587 }
588 
593 void MythNotificationScreen::UpdatePlayback(float progress, const QString &text)
594 {
596  m_progresstext = text;
597  // We need to re-run init
598  m_refresh = true;
599 }
600 
605 {
606  // check if anything has changed
607  m_refresh = !(
608  m_id == s.m_id &&
609  m_image == s.m_image &&
610  m_imagePath == s.m_imagePath &&
611  m_title == s.m_title &&
612  m_origin == s.m_origin &&
614  m_extra == s.m_extra &&
615  m_duration == s.m_duration &&
616  m_progress == s.m_progress &&
618  m_content == s.m_content &&
619  m_fullscreen == s.m_fullscreen &&
620  m_expiry == s.m_expiry &&
621  m_index == s.m_index &&
622  m_style == s.m_style &&
623  m_visibility == s.m_visibility &&
624  m_priority == s.m_priority &&
625  m_type == s.m_type
626  );
627 
628  if (m_refresh)
629  {
630  m_id = s.m_id;
631  m_image = s.m_image;
633  m_title = s.m_title;
634  m_origin = s.m_origin;
636  m_extra = s.m_extra;
640  m_content = s.m_content;
642  m_expiry = s.m_expiry;
643  m_index = s.m_index;
644  m_style = s.m_style;
647  m_type = s.m_type;
648  }
649 
650  m_update = m_content; // so all fields are initialised regardless of notification type
651 }
652 
658 {
659  MythPoint point = m_position;
660  point.setY(m_position.getY().toInt() + (GetHeight() + HGAP) * m_index);
661 
662  if (point == GetPosition())
663  return;
664 
665  SetPosition(point);
666  // We need to re-run init
667  m_refresh = true;
668 }
669 
671 {
672  if (set)
673  {
674  m_index = by;
675  }
676  else
677  {
678  m_index += by;
679  }
680  AdjustYPosition();
681 }
682 
687 {
688  if (index != m_index)
689  {
690  m_refresh = true;
691  m_index = index;
692  }
693 }
694 
696 {
697  return GetArea().getHeight().toInt();
698 }
699 
701 {
702  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Screen id %1 \"%2\" expired")
703  .arg(m_id).arg(m_title));
704  // delete screen
705  GetScreenStack()->PopScreen(this, true, true);
706 }
707 
709 {
710  // only registered application can display non-expiring notification
711  if (m_id > 0 && s < 0)
712  return;
713 
714  int ms = s * 1000;
715  ms = ms <= DEFAULT_DURATION ? DEFAULT_DURATION : ms;
716 
717  if (!update)
718  {
720  }
721  m_expiry = MythDate::current().addMSecs(ms);
722 
723  m_timer->stop();
724  m_timer->setSingleShot(true);
725  m_timer->start(ms);
726  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Screen %1 expires at %2")
727  .arg(m_id).arg(m_expiry.toString("mm:ss")));
728 
729 }
730 
731 // Public event handling
733 {
734  QStringList actions;
735  bool handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions);
736 
737  for (int i = 0; i < actions.size() && !handled; i++)
738  {
739  QString action = actions[i];
740 
741  if (action == "ESCAPE")
742  {
743  if (MythDate::current() < m_creation.addMSecs(MIN_LIFE))
744  return true; // was updated less than 1s ago, ignore
745  }
746  }
747  if (!handled)
748  {
749  handled = MythScreenType::keyPressEvent(event);
750  }
751  return handled;
752 }
753 
755 
757 {
759  "mythnotificationcenter",
760  this);
762 }
763 
765 {
766  const bool isGuiThread =
767  QThread::currentThread() == QCoreApplication::instance()->thread();
768 
769  if (!isGuiThread)
770  {
771  LOG(VB_GENERAL, LOG_ERR, LOC + "Destructor not called from GUI thread");
772  }
773 
774  QMutexLocker lock(&m_lock);
775 
779 
780  // Delete all outstanding queued notifications
781  foreach(MythNotification *n, m_notifications)
782  {
783  delete n;
784  }
785  m_notifications.clear();
786 
787  delete m_screenStack;
789 }
790 
796 {
797  MythNotificationScreen *screen =
798  static_cast<MythNotificationScreen*>(sender());
799 
800  bool duefordeletion = m_deletedScreens.contains(screen);
801 
802  LOG(VB_GUI, LOG_DEBUG, LOC +
803  QString("ScreenDeleted: Entering (%1)").arg(duefordeletion));
804  // Check that screen wasn't about to be deleted
805  if (duefordeletion)
806  {
807  m_deletedScreens.removeAll(screen);
808  }
809 
810  int n = m_screens.indexOf(screen);
811  if (n >= 0)
812  {
813  int num = m_screens.removeAll(screen);
814  LOG(VB_GUI, LOG_DEBUG, LOC +
815  QString("%1 screen removed from screens list").arg(num));
817  }
818  else
819  {
820  LOG(VB_GUI, LOG_DEBUG, LOC +
821  QString("Screen[%1] not found in screens list").arg(screen->m_id));
822  }
823 
824  // remove the converted equivalent screen if any
825  if (m_converted.contains(screen))
826  {
827  delete m_converted[screen];
828  }
829  m_converted.remove(screen);
830 
831  // search if an application had registered for it
832  if (m_registrations.contains(screen->m_id))
833  {
834  if (!duefordeletion)
835  {
836  if (!m_screenStack)
837  {
838  // we're in the middle of being deleted
839  m_registrations.remove(screen->m_id);
840  m_unregistered.remove(screen->m_id);
841  }
842  else
843  {
844  // don't remove the id from the list, as the application is still registered
845  // re-create the screen
846  MythNotificationScreen *newscreen =
848  connect(newscreen, SIGNAL(ScreenDeleted()), this, SLOT(ScreenDeleted()));
849  m_registrations[screen->m_id] = newscreen;
850  // Screen was deleted, add it to suspended list
851  m_suspended.append(screen->m_id);
852  LOG(VB_GUI, LOG_DEBUG, LOC +
853  "ScreenDeleted: Suspending registered screen");
854  }
855  }
856  else
857  {
858  LOG(VB_GUI, LOG_DEBUG, LOC +
859  "ScreenDeleted: Deleting registered screen");
860  }
861  }
862 }
863 
865 {
866  m_screenStack = nullptr;
867 }
868 
869 bool NCPrivate::Queue(const MythNotification &notification)
870 {
871  QMutexLocker lock(&m_lock);
872 
873  int id = notification.GetId();
874  void *parent = notification.GetParent();
875 
876  MythNotification *tmp = static_cast<MythNotification*>(notification.clone());
877  if (id > 0)
878  {
879  // quick sanity check to ensure the right caller is attempting
880  // to register a notification
881  if (!m_registrations.contains(id) || m_clients[id] != parent)
882  {
883  LOG(VB_GENERAL, LOG_DEBUG, LOC +
884  QString("Queue: 0x%1, not registered for id (%2)")
885  .arg((size_t)parent, QT_POINTER_SIZE, 16, QChar('0'))
886  .arg(id));
887  id = -1;
888  }
889  else
890  {
891  // check if notification card has been suspended, in which case
892  // refuse all notification updates
893  if (m_suspended.contains(id))
894  {
895  if (notification.type() == MythNotification::Update)
896  {
897  delete tmp;
898  return false;
899  }
900  // got something else than an update, remove it from the
901  // suspended list
902  m_suspended.removeAll(id);
903  }
904  }
905  }
906  m_notifications.append(tmp);
907 
908  // Tell the GUI thread we have new notifications to process
909  QCoreApplication::postEvent(
911 
912  return true;
913 }
914 
916 {
917  QMutexLocker lock(&m_lock);
918 
920 
921  foreach (MythNotification *n, m_notifications)
922  {
923  int id = n->GetId();
924  bool created = false;
925  MythNotificationScreen *screen = nullptr;
926 
927  if (id > 0)
928  {
929  screen = m_registrations[id];
930  }
931  if (!screen)
932  {
933  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Creating screen %1, \"%2\"")
934  .arg(id).arg(n->GetDescription()));
935 
936  // We have a registration, but no screen. Create one and display it
937  screen = CreateScreen(n);
938  if (!screen) // Reads screen definition from xml, and constructs screen
939  {
940  LOG(VB_GENERAL, LOG_ERR, LOC +
941  QString("ProcessQueue: couldn't create required screen"));
942  delete n;
943  continue; // something is wrong ; ignore
944  }
945  if (id > 0)
946  {
947  m_registrations[id] = screen;
948  }
949  created = true;
950  }
951  else
952  {
953  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Using screen %1, \"%2\"")
954  .arg(id).arg(screen->m_title));
955  screen->SetNotification(*n);
956  }
957 
958  // if the screen got allocated, but did't read theme yet, do it now
959  if (screen && !screen->m_created)
960  {
961  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Reloading screen %1, \"%2\"")
962  .arg(id).arg(screen->m_title));
963 
964 
965  if (!screen->Create())
966  {
967  delete screen;
968  delete n;
969  continue;
970  }
971  created = true;
972  }
973 
974  if (created || !m_screens.contains(screen))
975  {
976  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Inserting screen %1").arg(id));
977 
978  int pos = InsertScreen(screen);
979  // adjust vertical positions
981  }
982 
983  screen->doInit();
984  delete n;
985  }
986  m_notifications.clear();
987 
989 }
990 
996 {
997  MythNotificationScreen *screen;
998 
999  if (n)
1000  {
1001  screen = new MythNotificationScreen(m_screenStack, *n);
1002  }
1003  else
1004  {
1005  screen = new MythNotificationScreen(m_screenStack, id);
1006  }
1007 
1008  if (!screen->Create()) // Reads screen definition from xml, and constructs screen
1009  {
1010  // If we can't create the screen then we can't display it, so delete
1011  // and abort
1012  delete screen;
1013  return nullptr;
1014  }
1015  connect(screen, SIGNAL(ScreenDeleted()), this, SLOT(ScreenDeleted()));
1016  return screen;
1017 }
1018 
1019 int NCPrivate::Register(void *from)
1020 {
1021  QMutexLocker lock(&m_lock);
1022 
1023  if (!from)
1024  return -1;
1025 
1026  m_currentId++;
1027  m_registrations.insert(m_currentId, nullptr);
1028  m_clients.insert(m_currentId, from);
1029 
1030  return m_currentId;
1031 }
1032 
1033 void NCPrivate::UnRegister(void *from, int id, bool closeimemdiately)
1034 {
1035  QMutexLocker lock(&m_lock);
1036 
1037  if (!m_registrations.contains(id))
1038  {
1039  LOG(VB_GENERAL, LOG_ERR, LOC +
1040  QString("UnRegister: 0x%1, no such registration (%2)")
1041  .arg((size_t)from, QT_POINTER_SIZE, 16, QChar('0'))
1042  .arg(id));
1043  return;
1044  }
1045 
1046  if (m_clients[id] != from)
1047  {
1048  LOG(VB_GENERAL, LOG_ERR, LOC +
1049  QString("UnRegister: 0x%1, not registered for id (%2")
1050  .arg((size_t)from, QT_POINTER_SIZE, 16, QChar('0'))
1051  .arg(id));
1052  }
1053 
1054  // queue the de-registration
1055  m_unregistered[id] = closeimemdiately;
1056 
1057  m_clients.remove(id);
1058 
1059  // Tell the GUI thread we have something to process
1060  QCoreApplication::postEvent(
1062 }
1063 
1065 {
1066  QMap<int, MythNotificationScreen*>::iterator it = m_registrations.begin();
1067 
1068  for (; it != m_registrations.end(); ++it)
1069  {
1070  if (*it)
1071  {
1072  m_deletedScreens.append(*it);
1073  }
1074  }
1075  m_registrations.clear();
1076 }
1077 
1079 {
1080  // delete all screens waiting to be deleted
1081  while(!m_deletedScreens.isEmpty())
1082  {
1083  MythNotificationScreen *screen = m_deletedScreens.last();
1084  // we remove the screen from the list before deleting the screen
1085  // so the MythScreenType::Exiting() signal won't process it a second time
1086  m_deletedScreens.removeLast();
1087  if (m_screenStack == nullptr &&
1089  {
1090  // our screen stack got deleted already and all its children
1091  // would have been marked for deletion during the
1092  // ScreenStack destruction but not yet deleted as the event loop may
1093  // not be running; so we can leave the screen alone.
1094  // However for clarity, call deleteLater()
1095  // as it is safe to call deleteLater more than once
1096  screen->deleteLater();
1097  }
1098  else if (screen->GetScreenStack() == m_screenStack)
1099  {
1100  screen->GetScreenStack()->PopScreen(screen, true, true);
1101  }
1102  else if (screen->GetScreenStack() == nullptr)
1103  {
1104  // this screen was never added to a screen stack, delete it now
1105  delete screen;
1106  }
1107  }
1108 }
1109 
1111 {
1112  QMap<int,bool>::iterator it = m_unregistered.begin();
1113  bool needdelete = false;
1114 
1115  for (; it != m_unregistered.end(); ++it)
1116  {
1117  int id = it.key();
1118  bool closeimemdiately = it.value();
1119  MythNotificationScreen *screen = nullptr;
1120 
1121  if (m_registrations.contains(id))
1122  {
1123  screen = m_registrations[id];
1124  if (screen != nullptr && !m_suspended.contains(id))
1125  {
1126  // mark the screen for deletion if no timer is set
1127  if (screen->m_duration <= 0 || closeimemdiately)
1128  {
1129  m_deletedScreens.append(screen);
1130  needdelete = true;
1131  }
1132  }
1133  m_registrations.remove(id);
1134  }
1135 
1136  if (m_suspended.contains(id))
1137  {
1138  // screen had been suspended, delete suspended screen
1139  delete screen;
1140  m_suspended.removeAll(id);
1141  }
1142  }
1143  m_unregistered.clear();
1144 
1145  if (needdelete)
1146  {
1147  DeleteAllScreens();
1148  }
1149 }
1150 
1156 {
1157  QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1158  QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1159 
1160 // if (screen->m_id > 0)
1161 // {
1162 // // we want a permanent screen; add it after the existing one
1163 // for (; it != itend; ++it)
1164 // {
1165 // if ((*it)->m_id <= 0 ||
1166 // (*it)->m_id > screen->m_id)
1167 // break; // reached the temporary screens
1168 // }
1169 // // it points to where we want to insert item
1170 // }
1171 // else
1172  {
1173  it = itend;
1174  }
1175  it = m_screens.insert(it, screen);
1176 
1177  return it - m_screens.begin();
1178 }
1179 
1185 {
1186  QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1187  QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1188 
1189  for (; it != itend; ++it)
1190  {
1191  if (*it == screen)
1192  break;
1193  }
1194 
1195  if (it != itend)
1196  {
1197  it = m_screens.erase(it);
1198  }
1199 
1200  return it - m_screens.begin();
1201 }
1202 
1207 {
1208  QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1209  QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1210 
1211  int position = 0;
1212 
1213  for (; it != itend; ++it)
1214  {
1215  if ((*it)->IsVisible())
1216  {
1217  (*it)->AdjustIndex(position++, true);
1218  }
1219  else
1220  {
1221  (*it)->AdjustIndex(position, true);
1222  }
1223  if ((*it)->m_fullscreen)
1224  {
1225  position = 0;
1226  continue;
1227  }
1228  }
1229 }
1230 
1231 void NCPrivate::GetNotificationScreens(QList<MythScreenType*> &_screens)
1232 {
1233  QList<MythScreenType*> list;
1234  QVector<MythScreenType*> screens;
1235 
1236  if (!m_screenStack)
1237  return;
1238 
1240 
1241  QMutexLocker lock(&m_lock);
1242 
1243  m_screenStack->GetScreenList(screens);
1244 
1245  QVector<MythScreenType*>::const_iterator it = screens.begin();
1246  QVector<MythScreenType*>::const_iterator itend = screens.end();
1247 
1248  int position = 0;
1249  for (; it != itend; ++it)
1250  {
1251  MythNotificationScreen *screen =
1252  dynamic_cast<MythNotificationScreen*>(*it);
1253 
1254  if (screen)
1255  {
1256  if ((screen->m_visibility & MythNotification::kPlayback) == 0)
1257  continue;
1258 
1259  MythNotificationScreen *newscreen;
1260 
1261  if (!m_converted.contains(screen))
1262  {
1263  // screen hasn't been created, return it
1264  newscreen = new MythNotificationScreen(nullptr, *screen);
1265  // CreateScreen can never fail, no need to test return value
1266  m_converted[screen] = newscreen;
1267  }
1268  else
1269  {
1270  newscreen = m_converted[screen];
1271  // Copy new content in case it has changed
1272  newscreen->UpdateFrom(*screen);
1273  }
1274  newscreen->SetVisible(true);
1275  newscreen->SetIndex(position++);
1276  if (screen->m_fullscreen)
1277  {
1278  position = 0;
1279  }
1280  list.append(newscreen);
1281  }
1282  else
1283  {
1284  list.append(*it);
1285  }
1286  }
1287  _screens = list;
1288 }
1289 
1291 {
1292  return m_screens.size();
1293 }
1294 
1296 {
1297  return m_notifications.size();
1298 }
1299 
1301 {
1302  QMutexLocker lock(&m_lock);
1303 
1304  if (m_screens.isEmpty())
1305  return false;
1306 
1307  // The top screen is the only currently displayed first, if there's a
1308  // fullscreen notification displayed, it's the last one
1309  MythNotificationScreen *top = m_screens.front();
1310  QList<MythNotificationScreen *>::const_iterator it = m_screens.end() - 1;
1311 
1312  // loop from last to 2nd
1313  for (; it != m_screens.begin(); --it)
1314  {
1315  MythNotificationScreen *s = *it;
1316 
1317  if (s->m_fullscreen)
1318  {
1319  top = s;
1320  break;
1321  }
1322  }
1323 
1324  if (MythDate::current() < top->m_creation.addMSecs(MIN_LIFE))
1325  return false;
1326 
1327  // simulate time-out
1328  top->ProcessTimer();
1329  return true;
1330 }
1331 
1333 
1335 {
1336  return GetNotificationCenter();
1337 }
1338 
1340  : d(new NCPrivate())
1341 {
1342  const bool isGuiThread =
1343  QThread::currentThread() == QCoreApplication::instance()->thread();
1344 
1345  if (!isGuiThread)
1346  {
1347  LOG(VB_GENERAL, LOG_ERR, LOC + "Constructor not called from GUI thread");
1348  }
1349 }
1350 
1352 {
1353  const bool isGuiThread =
1354  QThread::currentThread() == QCoreApplication::instance()->thread();
1355 
1356  if (!isGuiThread)
1357  {
1358  LOG(VB_GENERAL, LOG_ERR, LOC + "Destructor not called from GUI thread");
1359  }
1360 
1361  delete d;
1362  d = nullptr;
1363 }
1364 
1366 {
1367  return d->Queue(notification);
1368 }
1369 
1371 {
1372  const bool isGuiThread =
1373  QThread::currentThread() == QCoreApplication::instance()->thread();
1374 
1375  if (!isGuiThread)
1376  {
1377  LOG(VB_GENERAL, LOG_ERR, LOC + "ProcessQueue not called from GUI thread");
1378  return;
1379  }
1380 
1381  d->ProcessQueue();
1382 }
1383 
1385 {
1386  return d->Register(from);
1387 }
1388 
1389 void MythNotificationCenter::UnRegister(void *from, int id, bool closeimemdiately)
1390 {
1391  d->UnRegister(from, id, closeimemdiately);
1392 }
1393 
1395 {
1396  const MythNotificationScreen *s =
1397  dynamic_cast<const MythNotificationScreen*>(screen);
1398 
1399  if (!s)
1400  return QDateTime();
1401  return s->m_expiry;
1402 }
1403 
1405 {
1406  const MythNotificationScreen *s =
1407  dynamic_cast<const MythNotificationScreen*>(screen);
1408 
1409  if (!s)
1410  return true;;
1411  return s->m_created;
1412 }
1413 
1414 void MythNotificationCenter::GetNotificationScreens(QList<MythScreenType*> &_screens)
1415 {
1416  d->GetNotificationScreens(_screens);
1417 }
1418 
1420 {
1422  dynamic_cast<MythNotificationScreen*>(screen);
1423 
1424  if (!s)
1425  return;
1426 
1427  if (s->m_created)
1428  {
1429  s->doInit();
1430  }
1431 }
1432 
1434 {
1435  return d->DisplayedNotifications();
1436 }
1437 
1439 {
1440  return d->QueuedNotifications();
1441 }
1442 
1444 {
1445  return d->RemoveFirst();
1446 }
1447 
1448 void ShowNotificationError(const QString &msg,
1449  const QString &from,
1450  const QString &detail,
1451  const VNMask visibility,
1452  const MythNotification::Priority priority)
1453 {
1454  ShowNotification(true, msg, from, detail,
1455  QString(), QString(), QString(), -1, -1, false,
1456  visibility, priority);
1457 }
1458 
1459 void ShowNotification(const QString &msg,
1460  const QString &from,
1461  const QString &detail,
1462  const VNMask visibility,
1463  const MythNotification::Priority priority)
1464 {
1465  ShowNotification(false, msg, from, detail,
1466  QString(), QString(), QString(), -1, -1, false,
1467  visibility, priority);
1468 }
1469 
1471  const QString &msg,
1472  const QString &origin,
1473  const QString &detail,
1474  const QString &image,
1475  const QString &extra,
1476  const QString &progress_text, float progress,
1477  int duration,
1478  bool fullscreen,
1479  const VNMask visibility,
1480  const MythNotification::Priority priority,
1481  const QString &style)
1482 {
1484  msg, origin, detail, image, extra, progress_text, progress,
1485  duration, fullscreen, visibility, priority, style);
1486 }
1487 
1488 void ShowNotification(MythNotification::Type type,
1489  const QString &msg,
1490  const QString &origin,
1491  const QString &detail,
1492  const QString &image,
1493  const QString &extra,
1494  const QString &progress_text, float progress,
1495  int duration,
1496  bool fullscreen,
1497  const VNMask visibility,
1498  const MythNotification::Priority priority,
1499  const QString &style)
1500 {
1501  if (!GetNotificationCenter())
1502  return;
1503 
1504  MythNotification *n;
1505  DMAP data;
1506 
1507  data["minm"] = msg;
1508  data["asar"] = origin.isNull() ? QCoreApplication::translate("(Common)",
1509  "MythTV") : origin;
1510  data["asal"] = detail;
1511  data["asfm"] = extra;
1512 
1513  if (type == MythNotification::Error ||
1517  {
1518  n = new MythNotification(type, data);
1519  if (!duration &&
1522  {
1523  // default duration for those type of notifications is 10s
1524  duration = 10;
1525  }
1526  }
1527  else
1528  {
1529  if (!image.isEmpty())
1530  {
1531  if (progress >= 0)
1532  {
1533  n = new MythMediaNotification(type,
1534  image, data,
1535  progress, progress_text);
1536  }
1537  else
1538  {
1539  n = new MythImageNotification(type, image, data);
1540  }
1541  }
1542  else if (progress >= 0)
1543  {
1545  progress, progress_text, data);
1546  }
1547  else
1548  {
1549  n = new MythNotification(type, data);
1550  }
1551  }
1552  n->SetDuration(duration);
1553  n->SetFullScreen(fullscreen);
1554  n->SetPriority(priority);
1555  n->SetVisibility(visibility);
1556  n->SetStyle(style);
1557 
1559  delete n;
1560 }
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...
MythScreenType * GetTopScreen(void) const override
void SetAlpha(int newalpha)
Definition: mythuitype.cpp:924
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
unsigned int VNMask
int GetDuration(void) const
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.
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
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)
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
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:863
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...
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:84
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
QString GetImagePath(void) const
void Init(void) override
Update the various fields of a MythNotificationScreen.
QImage GetImage(void) const
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:906
MythMainWindow * GetMythMainWindow(void)
void UpdateFrom(const MythNotificationScreen &s)
Copy metadata from another notification.
float GetProgress(void) const
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
QString GetProgressText(void) const
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
QMap< QString, QString > DMAP
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.