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  foreach (auto & draw, 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  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  foreach (auto & registration, m_registrations)
1060  {
1061  if (registration)
1062  {
1063  m_deletedScreens.append(registration);
1064  }
1065  }
1066  m_registrations.clear();
1067 }
1068 
1070 {
1071  // delete all screens waiting to be deleted
1072  while(!m_deletedScreens.isEmpty())
1073  {
1074  MythNotificationScreen *screen = m_deletedScreens.last();
1075  // we remove the screen from the list before deleting the screen
1076  // so the MythScreenType::Exiting() signal won't process it a second time
1077  m_deletedScreens.removeLast();
1078  if (m_screenStack == nullptr &&
1080  {
1081  // our screen stack got deleted already and all its children
1082  // would have been marked for deletion during the
1083  // ScreenStack destruction but not yet deleted as the event loop may
1084  // not be running; so we can leave the screen alone.
1085  // However for clarity, call deleteLater()
1086  // as it is safe to call deleteLater more than once
1087  screen->deleteLater();
1088  }
1089  else if (screen->GetScreenStack() == m_screenStack)
1090  {
1091  screen->GetScreenStack()->PopScreen(screen, true, true);
1092  }
1093  else if (screen->GetScreenStack() == nullptr)
1094  {
1095  // this screen was never added to a screen stack, delete it now
1096  delete screen;
1097  }
1098  }
1099 }
1100 
1102 {
1103  bool needdelete = false;
1104 
1105  for (auto it = m_unregistered.begin(); it != m_unregistered.end(); ++it)
1106  {
1107  int id = it.key();
1108  bool closeimemdiately = it.value();
1109  MythNotificationScreen *screen = nullptr;
1110 
1111  if (m_registrations.contains(id))
1112  {
1113  screen = m_registrations[id];
1114  if (screen != nullptr && !m_suspended.contains(id))
1115  {
1116  // mark the screen for deletion if no timer is set
1117  if (screen->m_duration <= 0 || closeimemdiately)
1118  {
1119  m_deletedScreens.append(screen);
1120  needdelete = true;
1121  }
1122  }
1123  m_registrations.remove(id);
1124  }
1125 
1126  if (m_suspended.contains(id))
1127  {
1128  // screen had been suspended, delete suspended screen
1129  delete screen;
1130  m_suspended.removeAll(id);
1131  }
1132  }
1133  m_unregistered.clear();
1134 
1135  if (needdelete)
1136  {
1137  DeleteAllScreens();
1138  }
1139 }
1140 
1146 {
1147  QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1148  QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1149 
1150 // if (screen->m_id > 0)
1151 // {
1152 // // we want a permanent screen; add it after the existing one
1153 // for (; it != itend; ++it)
1154 // {
1155 // if ((*it)->m_id <= 0 ||
1156 // (*it)->m_id > screen->m_id)
1157 // break; // reached the temporary screens
1158 // }
1159 // // it points to where we want to insert item
1160 // }
1161 // else
1162  {
1163  it = itend;
1164  }
1165  it = m_screens.insert(it, screen);
1166 
1167  return it - m_screens.begin();
1168 }
1169 
1175 {
1176  QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1177  QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1178 
1179  for (; it != itend; ++it)
1180  {
1181  if (*it == screen)
1182  break;
1183  }
1184 
1185  if (it != itend)
1186  {
1187  it = m_screens.erase(it);
1188  }
1189 
1190  return it - m_screens.begin();
1191 }
1192 
1197 {
1198  QList<MythNotificationScreen*>::iterator it = m_screens.begin();
1199  QList<MythNotificationScreen*>::iterator itend = m_screens.end();
1200 
1201  int position = 0;
1202 
1203  for (; it != itend; ++it)
1204  {
1205  if ((*it)->IsVisible())
1206  {
1207  (*it)->AdjustIndex(position++, true);
1208  }
1209  else
1210  {
1211  (*it)->AdjustIndex(position, true);
1212  }
1213  if ((*it)->m_fullscreen)
1214  {
1215  position = 0;
1216  continue;
1217  }
1218  }
1219 }
1220 
1221 void NCPrivate::GetNotificationScreens(QList<MythScreenType*> &_screens)
1222 {
1223  QList<MythScreenType*> list;
1224  QVector<MythScreenType*> screens;
1225 
1226  if (!m_screenStack)
1227  return;
1228 
1230 
1231  QMutexLocker lock(&m_lock);
1232 
1233  m_screenStack->GetScreenList(screens);
1234 
1235  int position = 0;
1236  foreach (auto item, screens)
1237  {
1238  auto *screen = dynamic_cast<MythNotificationScreen*>(item);
1239  if (screen)
1240  {
1241  if ((screen->m_visibility & MythNotification::kPlayback) == 0)
1242  continue;
1243 
1244  MythNotificationScreen *newscreen = nullptr;
1245 
1246  if (!m_converted.contains(screen))
1247  {
1248  // screen hasn't been created, return it
1249  newscreen = new MythNotificationScreen(nullptr, *screen);
1250  // CreateScreen can never fail, no need to test return value
1251  m_converted[screen] = newscreen;
1252  }
1253  else
1254  {
1255  newscreen = m_converted[screen];
1256  // Copy new content in case it has changed
1257  newscreen->UpdateFrom(*screen);
1258  }
1259  newscreen->SetVisible(true);
1260  newscreen->SetIndex(position++);
1261  if (screen->m_fullscreen)
1262  {
1263  position = 0;
1264  }
1265  list.append(newscreen);
1266  }
1267  else
1268  {
1269  list.append(item);
1270  }
1271  }
1272  _screens = list;
1273 }
1274 
1276 {
1277  return m_screens.size();
1278 }
1279 
1281 {
1282  return m_notifications.size();
1283 }
1284 
1286 {
1287  QMutexLocker lock(&m_lock);
1288 
1289  if (m_screens.isEmpty())
1290  return false;
1291 
1292  // The top screen is the only currently displayed first, if there's a
1293  // fullscreen notification displayed, it's the last one
1294  MythNotificationScreen *top = m_screens.front();
1295  QList<MythNotificationScreen *>::const_iterator it = m_screens.end() - 1;
1296 
1297  // loop from last to 2nd
1298  for (; it != m_screens.begin(); --it)
1299  {
1300  MythNotificationScreen *s = *it;
1301 
1302  if (s->m_fullscreen)
1303  {
1304  top = s;
1305  break;
1306  }
1307  }
1308 
1309  if (MythDate::current() < top->m_creation.addMSecs(MIN_LIFE))
1310  return false;
1311 
1312  // simulate time-out
1313  top->ProcessTimer();
1314  return true;
1315 }
1316 
1318 
1320 {
1321  return GetNotificationCenter();
1322 }
1323 
1325  : d(new NCPrivate())
1326 {
1327  const bool isGuiThread =
1328  QThread::currentThread() == QCoreApplication::instance()->thread();
1329 
1330  if (!isGuiThread)
1331  {
1332  LOG(VB_GENERAL, LOG_ERR, LOC + "Constructor not called from GUI thread");
1333  }
1334 }
1335 
1337 {
1338  const bool isGuiThread =
1339  QThread::currentThread() == QCoreApplication::instance()->thread();
1340 
1341  if (!isGuiThread)
1342  {
1343  LOG(VB_GENERAL, LOG_ERR, LOC + "Destructor not called from GUI thread");
1344  }
1345 
1346  delete d;
1347  d = nullptr;
1348 }
1349 
1351 {
1352  return d->Queue(notification);
1353 }
1354 
1356 {
1357  const bool isGuiThread =
1358  QThread::currentThread() == QCoreApplication::instance()->thread();
1359 
1360  if (!isGuiThread)
1361  {
1362  LOG(VB_GENERAL, LOG_ERR, LOC + "ProcessQueue not called from GUI thread");
1363  return;
1364  }
1365 
1366  d->ProcessQueue();
1367 }
1368 
1370 {
1371  return d->Register(from);
1372 }
1373 
1374 void MythNotificationCenter::UnRegister(void *from, int id, bool closeimemdiately)
1375 {
1376  d->UnRegister(from, id, closeimemdiately);
1377 }
1378 
1380 {
1381  const auto *s = dynamic_cast<const MythNotificationScreen*>(screen);
1382  if (!s)
1383  return QDateTime();
1384  return s->m_expiry;
1385 }
1386 
1388 {
1389  const auto *s = dynamic_cast<const MythNotificationScreen*>(screen);
1390  if (!s)
1391  return true;
1392  return s->m_created;
1393 }
1394 
1395 void MythNotificationCenter::GetNotificationScreens(QList<MythScreenType*> &_screens)
1396 {
1397  d->GetNotificationScreens(_screens);
1398 }
1399 
1401 {
1402  auto *s = dynamic_cast<MythNotificationScreen*>(screen);
1403  if (!s)
1404  return;
1405 
1406  if (s->m_created)
1407  {
1408  s->doInit();
1409  }
1410 }
1411 
1413 {
1414  return d->DisplayedNotifications();
1415 }
1416 
1418 {
1419  return d->QueuedNotifications();
1420 }
1421 
1423 {
1424  return d->RemoveFirst();
1425 }
1426 
1427 void ShowNotificationError(const QString &msg,
1428  const QString &from,
1429  const QString &detail,
1430  const VNMask visibility,
1431  const MythNotification::Priority priority)
1432 {
1433  ShowNotification(true, msg, from, detail,
1434  QString(), QString(), QString(), -1, -1, false,
1435  visibility, priority);
1436 }
1437 
1438 void ShowNotification(const QString &msg,
1439  const QString &from,
1440  const QString &detail,
1441  const VNMask visibility,
1442  const MythNotification::Priority priority)
1443 {
1444  ShowNotification(false, msg, from, detail,
1445  QString(), QString(), QString(), -1, -1, false,
1446  visibility, priority);
1447 }
1448 
1450  const QString &msg,
1451  const QString &origin,
1452  const QString &detail,
1453  const QString &image,
1454  const QString &extra,
1455  const QString &progress_text, float progress,
1456  int duration,
1457  bool fullscreen,
1458  const VNMask visibility,
1459  const MythNotification::Priority priority,
1460  const QString &style)
1461 {
1463  msg, origin, detail, image, extra, progress_text, progress,
1464  duration, fullscreen, visibility, priority, style);
1465 }
1466 
1467 void ShowNotification(MythNotification::Type type,
1468  const QString &msg,
1469  const QString &origin,
1470  const QString &detail,
1471  const QString &image,
1472  const QString &extra,
1473  const QString &progress_text, float progress,
1474  int duration,
1475  bool fullscreen,
1476  const VNMask visibility,
1477  const MythNotification::Priority priority,
1478  const QString &style)
1479 {
1480  if (!GetNotificationCenter())
1481  return;
1482 
1483  MythNotification *n = nullptr;
1484  DMAP data;
1485 
1486  data["minm"] = msg;
1487  data["asar"] = origin.isNull() ? QCoreApplication::translate("(Common)",
1488  "MythTV") : origin;
1489  data["asal"] = detail;
1490  data["asfm"] = extra;
1491 
1492  if (type == MythNotification::Error ||
1496  {
1497  n = new MythNotification(type, data);
1498  if (!duration &&
1501  {
1502  // default duration for those type of notifications is 10s
1503  duration = 10;
1504  }
1505  }
1506  else
1507  {
1508  if (!image.isEmpty())
1509  {
1510  if (progress >= 0)
1511  {
1512  n = new MythMediaNotification(type,
1513  image, data,
1514  progress, progress_text);
1515  }
1516  else
1517  {
1518  n = new MythImageNotification(type, image, data);
1519  }
1520  }
1521  else if (progress >= 0)
1522  {
1524  progress, progress_text, data);
1525  }
1526  else
1527  {
1528  n = new MythNotification(type, data);
1529  }
1530  }
1531  n->SetDuration(duration);
1532  n->SetFullScreen(fullscreen);
1533  n->SetPriority(priority);
1534  n->SetVisibility(visibility);
1535  n->SetStyle(style);
1536 
1538  delete n;
1539 }
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)
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:925
static void error(const char *str,...)
Definition: vbi.c:42
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 MythNotificationCenter * GetInstance(void)
returns the MythNotificationCenter singleton
static guint32 * tmp
Definition: goom_core.c:35
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
#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: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
int QueuedNotifications(void) const
Returns number of notifications currently queued.