MythTV  master
themechooser.cpp
Go to the documentation of this file.
1 
2 // Theme Chooser headers
3 #include "themechooser.h"
4 
5 // Qt headers
6 #include <QCoreApplication>
7 #include <QRunnable>
8 #include <QRegExp>
9 
10 // MythTV headers
11 #include "mythcorecontext.h"
12 #include "mythcoreutil.h"
13 #include "mthreadpool.h"
14 #include "remotefile.h"
15 #include "mythdownloadmanager.h"
16 #include "programtypes.h"
17 #include "mythsystemevent.h"
18 #include "mythdate.h"
19 #include "mythversion.h"
20 #include "mythlogging.h"
21 #include "storagegroup.h"
22 
23 // LibMythUI headers
24 #include "mythmainwindow.h"
25 #include "mythuihelper.h"
26 #include "mythuiprogressbar.h"
27 #include "mythdialogbox.h"
28 #include "mythuibuttonlist.h"
29 #include "mythscreenstack.h"
30 #include "mythuistatetype.h"
31 #include "mythuigroup.h"
32 #include "mythuiimage.h"
33 #include "mythuitext.h"
34 
35 #define LOC QString("ThemeChooser: ")
36 #define LOC_WARN QString("ThemeChooser, Warning: ")
37 #define LOC_ERR QString("ThemeChooser, Error: ")
38 
42 class ThemeExtractThread : public QRunnable
43 {
44  public:
46  const QString &srcFile, const QString &destDir) :
47  m_parent(parent),
48  m_srcFile(srcFile),
49  m_destDir(destDir)
50  {
51  m_srcFile.detach();
52  m_destDir.detach();
53  }
54 
55  void run()
56  {
58 
59  MythEvent *me =
60  new MythEvent("THEME_INSTALLED", QStringList(m_srcFile));
61  QCoreApplication::postEvent(m_parent, me);
62  }
63 
64  private:
66  QString m_srcFile;
67  QString m_destDir;
68 };
69 
70 
76  const QString &name) :
77  MythScreenType(parent, name),
78  m_themes(NULL),
79  m_preview(NULL),
80  m_fullPreviewShowing(false),
81  m_fullPreviewStateType(NULL),
82  m_fullScreenName(NULL),
83  m_fullScreenPreview(NULL),
84  m_refreshDownloadableThemes(false),
85  m_downloadTheme(NULL),
86  m_downloadState(dsIdle),
87  m_popupMenu(NULL)
88 {
90 
91  StorageGroup sgroup("Themes", gCoreContext->GetHostName());
92  m_userThemeDir = sgroup.GetFirstDir(true);
93 }
94 
96 {
98 }
99 
100 static bool sortThemeNames(const QFileInfo &s1, const QFileInfo &s2)
101 {
102  return s1.fileName().toLower() < s2.fileName().toLower();
103 }
104 
105 
107 {
108  // Load the theme for this screen
109  if (!LoadWindowFromXML("settings-ui.xml", "themechooser", this))
110  return false;
111 
112  bool err = false;
113  UIUtilE::Assign(this, m_themes, "themes", &err);
114 
115  UIUtilW::Assign(this, m_preview, "preview");
116  UIUtilW::Assign(this, m_fullPreviewStateType, "fullpreviewstate");
117 
119  {
120  MythUIGroup *state =
121  dynamic_cast<MythUIGroup*>
122  (m_fullPreviewStateType->GetChild("fullscreen"));
123  if (state)
124  {
126  dynamic_cast<MythUIText*>(state->GetChild("fullscreenname"));
128  dynamic_cast<MythUIImage*>(state->GetChild("fullscreenpreview"));
129  }
130  }
131 
132  if (err)
133  {
134  LOG(VB_GENERAL, LOG_ERR, LOC + "Cannot load screen 'themechooser'");
135  return false;
136  }
137 
138  connect(m_themes, SIGNAL(itemClicked(MythUIButtonListItem*)),
139  this, SLOT(saveAndReload(MythUIButtonListItem*)));
140  connect(m_themes, SIGNAL(itemSelected(MythUIButtonListItem*)),
141  this, SLOT(itemChanged(MythUIButtonListItem*)));
142 
143  BuildFocusList();
144 
146 
147  return true;
148 }
149 
151 {
152  SetBusyPopupMessage(tr("Loading Installed Themes"));
153 
154  QStringList themesSeen;
155  QDir themes(m_userThemeDir);
156  themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
157  themes.setSorting(QDir::Name | QDir::IgnoreCase);
158 
159  m_infoList = themes.entryInfoList();
160 
161  for( QFileInfoList::iterator it = m_infoList.begin();
162  it != m_infoList.end();
163  ++it )
164  {
165  if (loadThemeInfo(*it))
166  {
167  themesSeen << (*it).fileName();
168  m_themeStatuses[(*it).fileName()] = "default";
169  }
170  }
171 
172  themes.setPath(GetThemesParentDir());
173  QFileInfoList sharedThemes = themes.entryInfoList();
174  for( QFileInfoList::iterator it = sharedThemes.begin();
175  it != sharedThemes.end();
176  ++it )
177  {
178  if ((!themesSeen.contains((*it).fileName())) &&
179  (loadThemeInfo(*it)))
180  {
181  m_infoList << *it;
182  themesSeen << (*it).fileName();
183  m_themeStatuses[(*it).fileName()] = "default";
184  }
185  }
186 
187  // MYTH_SOURCE_VERSION - examples v29-pre-574-g92517f5, v29-Pre, v29.1-21-ge26a33c
188  QString MythVersion(MYTH_SOURCE_VERSION);
189  QRegExp trunkver("v[0-9]+-pre.*",Qt::CaseInsensitive);
190  QRegExp validver("v[0-9]+.*",Qt::CaseInsensitive);
191 
192  if (!validver.exactMatch(MythVersion))
193  {
194  LOG(VB_GENERAL, LOG_ERR, QString("Invalid MythTV version %1, will use themes from trunk").arg(MythVersion));
195  MythVersion = "trunk";
196  }
197  if (trunkver.exactMatch(MythVersion))
198  MythVersion = "trunk";
199 
200  if (MythVersion == "trunk")
201  {
202  LoadVersion(MythVersion, themesSeen, true);
203  LOG(VB_GUI, LOG_INFO, QString("Loading themes for %1").arg(MythVersion));
204  }
205  else
206  {
207 
208  MythVersion = MYTH_BINARY_VERSION; // Example: 29.20161017-1
209  // Remove the date part and the rest, eg 29.20161017-1 -> 29
210  MythVersion.replace(QRegExp("\\.[0-9]{8,}.*"), "");
211  LOG(VB_GUI, LOG_INFO, QString("Loading themes for %1").arg(MythVersion));
212  LoadVersion(MythVersion, themesSeen, true);
213 
214  // If a version of the theme for this tag exists, use it...
215  // MYTH_SOURCE_VERSION - examples v29-pre-574-g92517f5, v29-Pre, v29.1-21-ge26a33c
216  QRegExp subexp("v[0-9]+\\.([0-9]+)-*");
217  // This captures the subversion, i.e. the number after a dot
218  int pos = subexp.indexIn(MYTH_SOURCE_VERSION);
219  if (pos > -1)
220  {
221  QString subversion;
222  int idx = subexp.cap(1).toInt();
223  for ( ; idx > 0; --idx)
224  {
225  subversion = MythVersion + "." + QString::number(idx);
226  LOG(VB_GUI, LOG_INFO, QString("Loading themes for %1").arg(subversion));
227  LoadVersion(subversion, themesSeen, false);
228  }
229  }
230  }
231 
232  ResetBusyPopup();
233 
234  qSort(m_infoList.begin(), m_infoList.end(), sortThemeNames);
235 }
236 
237 void ThemeChooser::LoadVersion(const QString &version,
238  QStringList &themesSeen, bool alert_user)
239 {
240  QString remoteThemesFile = GetConfDir();
241  remoteThemesFile.append("/tmp/themes.zip");
242  QString themeSite = QString("%1/%2")
243  .arg(gCoreContext->GetSetting("ThemeRepositoryURL",
244  "http://themes.mythtv.org/themes/repository")).arg(version);
245  QString destdir = GetCacheDir().append("/themechooser/");
246  QString versiondir = QString("%1/%2").arg(destdir).arg(version);
247  QDir remoteThemesDir(versiondir);
248 
249  int downloadFailures =
250  gCoreContext->GetNumSetting("ThemeInfoDownloadFailures", 0);
251  if (QFile::exists(remoteThemesFile))
252  {
253  QFileInfo finfo(remoteThemesFile);
254  if (finfo.lastModified().toUTC() <
255  MythDate::current().addSecs(-600))
256  {
257  LOG(VB_GUI, LOG_INFO, LOC +
258  QString("%1 is over 10 minutes old, forcing "
259  "remote theme list download").arg(remoteThemesFile));
261  }
262 
263  if (!remoteThemesDir.exists())
265  }
266  else if (downloadFailures < 2) // (and themes.zip does not exist)
267  {
268  LOG(VB_GUI, LOG_INFO, LOC +
269  QString("%1 does not exist, forcing remote theme "
270  "list download").arg(remoteThemesFile));
272  }
273 
275  {
276  QFile test(remoteThemesFile);
277  if (test.open(QIODevice::WriteOnly))
278  test.remove();
279  else
280  {
281  ShowOkPopup(tr("Unable to create '%1'").arg(remoteThemesFile));
282  return;
283  }
284 
285  SetBusyPopupMessage(tr("Refreshing Downloadable Themes Information"));
286 
287  QString url = themeSite;
288  url.append("/themes.zip");
289  if (!removeThemeDir(versiondir))
290  ShowOkPopup(tr("Unable to remove '%1'").arg(versiondir));
291  QDir dir;
292  if (!dir.mkpath(destdir))
293  ShowOkPopup(tr("Unable to create '%1'").arg(destdir));
294  bool result = GetMythDownloadManager()->download(url, remoteThemesFile);
295 
296  LOG(VB_GUI, LOG_INFO, LOC +
297  QString("Downloading '%1' to '%2'").arg(url).arg(remoteThemesFile));
298 
299  SetBusyPopupMessage(tr("Extracting Downloadable Themes Information"));
300 
301  if (!result || !extractZIP(remoteThemesFile, destdir))
302  {
303  QFile::remove(remoteThemesFile);
304 
305  downloadFailures++;
306  gCoreContext->SaveSetting("ThemeInfoDownloadFailures",
307  downloadFailures);
308 
309  if (!result)
310  {
311  LOG(VB_GUI, LOG_ERR, LOC +
312  QString("Failed to download '%1'").arg(url));
313  if (alert_user)
314  ShowOkPopup(tr("Failed to download '%1'").arg(url));
315  }
316  else
317  {
318  LOG(VB_GUI, LOG_ERR, LOC +
319  QString("Failed to unzip '%1' to '%2'")
320  .arg(remoteThemesFile).arg(destdir));
321  if (alert_user)
322  ShowOkPopup(tr("Failed to unzip '%1' to '%2'")
323  .arg(remoteThemesFile).arg(destdir));
324  }
325  }
326  else
327  LOG(VB_GUI, LOG_INFO, LOC +
328  QString("Unzipped '%1' to '%2'")
329  .arg(remoteThemesFile)
330  .arg(destdir));
331  }
332 
333  QDir themes;
334  themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
335  themes.setSorting(QDir::Name | QDir::IgnoreCase);
336 
337  if ((QFile::exists(remoteThemesFile)) &&
338  (remoteThemesDir.exists()))
339  {
340  SetBusyPopupMessage(tr("Loading Downloadable Themes"));
341 
342  LOG(VB_GUI, LOG_INFO, LOC +
343  QString("%1 and %2 exist, using cached remote themes list")
344  .arg(remoteThemesFile).arg(remoteThemesDir.absolutePath()));
345 
346  QString themesPath = remoteThemesDir.absolutePath();
347  themes.setPath(themesPath);
348 
349  QFileInfoList downloadableThemes = themes.entryInfoList();
350  for( QFileInfoList::iterator it = downloadableThemes.begin();
351  it != downloadableThemes.end();
352  ++it )
353  {
354  QString dirName = (*it).fileName();
355  QString themeName = dirName;
356  QString remoteDir = themeSite;
357  remoteDir.append("/").append(dirName);
358  QString localDir = themes.absolutePath();
359  localDir.append("/").append(dirName);
360 
361  ThemeInfo remoteTheme((*it).absoluteFilePath());
362 
363  if (themesSeen.contains(dirName))
364  {
365  ThemeInfo *localTheme = m_themeNameInfos[dirName];
366 
367  themeName = remoteTheme.GetName();
368 
369  int rmtMaj = remoteTheme.GetMajorVersion();
370  int rmtMin = remoteTheme.GetMinorVersion();
371  int locMaj = localTheme->GetMajorVersion();
372  int locMin = localTheme->GetMinorVersion();
373 
374  if ((rmtMaj > locMaj) ||
375  ((rmtMaj == locMaj) &&
376  (rmtMin > locMin)))
377  {
378  if (loadThemeInfo(*it))
379  {
380  LOG(VB_GUI, LOG_DEBUG, LOC +
381  QString("'%1' old version %2.%3, new version %4.%5")
382  .arg(themeName).arg(locMaj).arg(locMin)
383  .arg(rmtMaj).arg(rmtMin));
384 
385  m_infoList << *it;
386  m_themeStatuses[themeName] = "updateavailable";
387 
388  QFileInfo finfo(remoteTheme.GetPreviewPath());
390  remoteDir.append("/").append(finfo.fileName()),
391  localDir.append("/").append(finfo.fileName()),
392  NULL);
393  }
394  }
395  else if ((rmtMaj == locMaj) &&
396  (rmtMin == locMin))
397  {
398  LOG(VB_GUI, LOG_DEBUG, LOC +
399  QString("'%1' up to date (%2.%3)")
400  .arg(themeName).arg(locMaj).arg(locMin));
401 
402  m_themeStatuses[themeName] = "uptodate";
403  }
404  }
405  else
406  {
407  LOG(VB_GUI, LOG_DEBUG, LOC +
408  QString("'%1' (%2.%3) available")
409  .arg(themeName)
410  .arg(remoteTheme.GetMajorVersion())
411  .arg(remoteTheme.GetMinorVersion()));
412 
413  ThemeInfo *remoteTheme = loadThemeInfo(*it);
414  if (remoteTheme)
415  {
416  themeName = remoteTheme->GetName();
417  themesSeen << dirName;
418  m_infoList << *it;
419  m_themeStatuses[themeName] = "updateavailable";
420 
421  QFileInfo finfo(remoteTheme->GetPreviewPath());
423  remoteDir.append("/").append(finfo.fileName()),
424  localDir.append("/").append(finfo.fileName()),
425  NULL);
426  }
427  }
428  }
429  }
430 
431 }
432 
434 {
435  QString curTheme = gCoreContext->GetSetting("Theme");
436  ThemeInfo *themeinfo = NULL;
437  ThemeInfo *curThemeInfo = NULL;
438  MythUIButtonListItem *item = NULL;
439 
440  m_themes->Reset();
441  for( QFileInfoList::iterator it = m_infoList.begin();
442  it != m_infoList.end();
443  ++it )
444  {
445  QFileInfo &theme = *it;
446 
447  if (!m_themeFileNameInfos.contains(theme.filePath()))
448  continue;
449 
450  themeinfo = m_themeFileNameInfos[theme.filePath()];
451  if (!themeinfo)
452  continue;
453 
454  QString buttonText = QString("%1 %2.%3")
455  .arg(themeinfo->GetName())
456  .arg(themeinfo->GetMajorVersion())
457  .arg(themeinfo->GetMinorVersion());
458 
459  item = new MythUIButtonListItem(m_themes, buttonText);
460  if (item)
461  {
462  if (themeinfo->GetDownloadURL().isEmpty())
463  item->DisplayState("local", "themelocation");
464  else
465  item->DisplayState("remote", "themelocation");
466 
467  item->DisplayState(themeinfo->GetAspect(), "aspectstate");
468 
469  item->DisplayState(m_themeStatuses[themeinfo->GetName()],
470  "themestatus");
471  InfoMap infomap;
472  themeinfo->ToMap(infomap);
473  item->SetTextFromMap(infomap);
474  item->SetData(qVariantFromValue(themeinfo));
475 
476  QString thumbnail = themeinfo->GetPreviewPath();
477  QFileInfo fInfo(thumbnail);
478  // Downloadable themeinfos have thumbnail copies of their preview images
479  if (!themeinfo->GetDownloadURL().isEmpty())
480  thumbnail = thumbnail.append(".thumb.jpg");
481  item->SetImage(thumbnail);
482 
483  if (curTheme == themeinfo->GetDirectoryName())
484  curThemeInfo = themeinfo;
485  }
486  }
487 
489 
490  if (curThemeInfo)
491  m_themes->SetValueByData(qVariantFromValue(curThemeInfo));
492 
494  if (current)
495  itemChanged(current);
496 
497  QString testFile = m_userThemeDir + "/.test";
498  QFile test(testFile);
499  if (test.open(QIODevice::WriteOnly))
500  test.remove();
501  else
502  {
503  ShowOkPopup(tr("Error creating test file, %1 themes directory is "
504  "not writable.").arg(m_userThemeDir));
505  }
506 }
507 
509 {
510  if (theme.fileName() == "default" || theme.fileName() == "default-wide")
511  return NULL;
512 
513  ThemeInfo *themeinfo = NULL;
514  if (theme.exists()) // local directory vs http:// or remote URL
515  themeinfo = new ThemeInfo(theme.absoluteFilePath());
516  else
517  themeinfo = new ThemeInfo(theme.filePath());
518 
519  if (!themeinfo)
520  return NULL;
521 
522  if (themeinfo->GetName().isEmpty() || !(themeinfo->GetType() & THEME_UI))
523  {
524  delete themeinfo;
525  return NULL;
526  }
527 
528  m_themeFileNameInfos[theme.filePath()] = themeinfo;
529  m_themeNameInfos[theme.fileName()] = themeinfo;
530 
531  return themeinfo;
532 }
533 
535 {
536  if (m_popupMenu)
537  return;
538 
539  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
540  QString label = tr("Theme Chooser Menu");
541 
542  m_popupMenu =
543  new MythDialogBox(label, popupStack, "themechoosermenupopup");
544 
545  connect(m_popupMenu, SIGNAL(Closed(QString, int)), SLOT(popupClosed(QString, int)));
546 
547  if (m_popupMenu->Create())
548  popupStack->AddScreen(m_popupMenu);
549  else
550  {
551  delete m_popupMenu;
552  m_popupMenu = NULL;
553  return;
554  }
555 
556  m_popupMenu->SetReturnEvent(this, "popupmenu");
557 
559  {
561  m_popupMenu->AddButton(tr("Hide Fullscreen Preview"),
562  SLOT(toggleFullscreenPreview()));
563  else
564  m_popupMenu->AddButton(tr("Show Fullscreen Preview"),
565  SLOT(toggleFullscreenPreview()));
566  }
567 
568  m_popupMenu->AddButton(tr("Refresh Downloadable Themes"),
569  SLOT(refreshDownloadableThemes()));
570 
572  if (current)
573  {
574  ThemeInfo *info = current->GetData().value<ThemeInfo *>();
575 
576  if (info)
577  {
578  m_popupMenu->AddButton(tr("Select Theme"),
579  SLOT(saveAndReload()));
580 
581  if (info->GetPreviewPath().startsWith(m_userThemeDir))
582  m_popupMenu->AddButton(tr("Delete Theme"),
583  SLOT(removeTheme()));
584  }
585  }
586 
587  if (gCoreContext->GetNumSetting("ThemeUpdateNofications", 1))
588  m_popupMenu->AddButton(tr("Disable Theme Update Notifications"),
590  else
591  m_popupMenu->AddButton(tr("Enable Theme Update Notifications"),
593 }
594 
595 void ThemeChooser::popupClosed(QString which, int result)
596 {
597  (void)which;
598  (void)result;
599 
600  m_popupMenu = NULL;
601 }
602 
603 bool ThemeChooser::keyPressEvent(QKeyEvent *event)
604 {
605  if (GetFocusWidget()->keyPressEvent(event))
606  return true;
607 
608  bool handled = false;
609  QStringList actions;
610  handled = GetMythMainWindow()->TranslateKeyPress("Theme Chooser", event, actions);
611 
612  for (int i = 0; i < actions.size() && !handled; ++i)
613  {
614  QString action = actions[i];
615  handled = true;
616 
617  if (action == "MENU")
618  showPopupMenu();
619  else if (action == "DELETE")
620  removeTheme();
621  else if ((action == "ESCAPE") &&
623  {
625  }
626  else
627  handled = false;
628  }
629 
630  if (!handled && MythScreenType::keyPressEvent(event))
631  handled = true;
632 
633  return handled;
634 }
635 
637 {
639  {
641  {
644 
645  if (m_fullScreenName)
647 
649  m_fullPreviewShowing = false;
650  }
651  else
652  {
654  ThemeInfo *info = item->GetData().value<ThemeInfo*>();
655  if (info)
656  {
658  {
661  }
662 
663  if (m_fullScreenName)
664  m_fullScreenName->SetText(info->GetName());
665 
666  m_fullPreviewStateType->DisplayState("fullscreen");
667  m_fullPreviewShowing = true;
668  }
669  }
670  }
671 }
672 
674 {
675  if (gCoreContext->GetNumSetting("ThemeUpdateNofications", 1))
676  gCoreContext->SaveSettingOnHost("ThemeUpdateNofications", "0", "");
677  else
678  gCoreContext->SaveSettingOnHost("ThemeUpdateNofications", "1", "");
679 }
680 
682 {
683  LOG(VB_GUI, LOG_INFO, LOC + "Forcing remote theme list refresh");
685  gCoreContext->SaveSetting("ThemeInfoDownloadFailures", 0);
687 }
688 
690 {
692  if (current)
693  saveAndReload(current);
694 }
695 
697 {
698  ThemeInfo *info = item->GetData().value<ThemeInfo *>();
699 
700  if (!info)
701  return;
702 
703  if (!info->GetDownloadURL().isEmpty())
704  {
705  QString testFile = m_userThemeDir + "/.test";
706  QFile test(testFile);
707  if (test.open(QIODevice::WriteOnly))
708  test.remove();
709  else
710  {
711  ShowOkPopup(tr("Unable to install theme, %1 themes directory is "
712  "not writable.").arg(m_userThemeDir));
713  return;
714  }
715 
716  QString downloadURL = info->GetDownloadURL();
717  QFileInfo qfile(downloadURL);
718  QString baseName = qfile.fileName();
719 
720  if (!gCoreContext->GetSetting("ThemeDownloadURL").isEmpty())
721  {
722  QStringList tokens =
723  gCoreContext->GetSetting("ThemeDownloadURL")
724  .split(";", QString::SkipEmptyParts);
725  QString origURL = downloadURL;
726  downloadURL.replace(tokens[0], tokens[1]);
727  LOG(VB_FILE, LOG_WARNING, LOC +
728  QString("Theme download URL overridden from %1 to %2.")
729  .arg(origURL).arg(downloadURL));
730  }
731 
732  OpenBusyPopup(tr("Downloading %1 Theme").arg(info->GetName()));
733  m_downloadTheme = info;
734 #if 0
735  m_downloadFile = RemoteDownloadFile(downloadURL,
736  "Temp", baseName);
738 #else
739  QString localFile = GetConfDir() + "/tmp/" + baseName;
740  GetMythDownloadManager()->queueDownload(downloadURL, localFile, this);
741  m_downloadFile = localFile;
743 #endif
744  }
745  else
746  {
747  gCoreContext->SaveSetting("Theme", info->GetDirectoryName());
748  GetMythMainWindow()->JumpTo("Reload Theme");
749  }
750 }
751 
753 {
754  ThemeInfo *info = item->GetData().value<ThemeInfo*>();
755 
756  if (!info)
757  return;
758 
759  QFileInfo preview(info->GetPreviewPath());
760  InfoMap infomap;
761  info->ToMap(infomap);
762  SetTextFromMap(infomap);
763  if (m_preview)
764  {
765  if (preview.exists())
766  {
768  m_preview->Load();
769  }
770  else
771  m_preview->Reset();
772  }
774  {
776  {
777  if (preview.exists())
778  {
781  }
782  else
784  }
785 
786  if (m_fullScreenName)
787  m_fullScreenName->SetText(info->GetName());
788  }
789 
790  MythUIStateType *themeLocation =
791  dynamic_cast<MythUIStateType*>(GetChild("themelocation"));
792  if (themeLocation)
793  {
794  if (info->GetDownloadURL().isEmpty())
795  themeLocation->DisplayState("local");
796  else
797  themeLocation->DisplayState("remote");
798  }
799 
800  MythUIStateType *aspectState =
801  dynamic_cast<MythUIStateType*>(GetChild("aspectstate"));
802  if (aspectState)
803  aspectState->DisplayState(info->GetAspect());
804 }
805 
806 void ThemeChooser::updateProgressBar(int bytesReceived,
807  int bytesTotal)
808 {
809  MythUIProgressBar *progressBar =
810  dynamic_cast<MythUIProgressBar *>(GetChild("downloadprogressbar"));
811 
812  if (!progressBar)
813  return;
814 
815  progressBar->SetUsed(bytesReceived);
816  progressBar->SetTotal(bytesTotal);
817 }
818 
820 {
821  if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage)
822  {
823  MythEvent *me = (MythEvent *)e;
824  QStringList tokens = me->Message().split(" ", QString::SkipEmptyParts);
825 
826  if (tokens.isEmpty())
827  return;
828 
829  if (tokens[0] == "DOWNLOAD_FILE")
830  {
831  QStringList args = me->ExtraDataList();
832  if ((m_downloadState == dsIdle) ||
833  (tokens.size() != 2) ||
834  (!m_downloadTheme) ||
835  (args[1] != m_downloadFile))
836  return;
837 
838  if (tokens[1] == "UPDATE")
839  {
840  updateProgressBar(args[2].toInt(), args[3].toInt());
841  }
842  else if (tokens[1] == "FINISHED")
843  {
844  bool remoteFileIsLocal = false;
845  int fileSize = args[2].toInt();
846  int errorCode = args[4].toInt();
847 
848  CloseBusyPopup();
849 
850  QFileInfo file(m_downloadFile);
852  (m_downloadFile.startsWith("myth://")))
853  {
854  // The backend download is finished so start the
855  // frontend download
856  if ((errorCode == 0) &&
857  (fileSize > 0))
858  {
860  QString localFile = GetConfDir() + "/tmp/" +
861  file.fileName();
862  file.setFile(localFile);
863 
864  if (file.exists())
865  {
866  remoteFileIsLocal = true;
867  m_downloadFile = localFile;
868  }
869  else
870  {
872  m_downloadFile, localFile, this);
873  OpenBusyPopup(tr("Copying %1 Theme Package")
874  .arg(m_downloadTheme->GetName()));
875  m_downloadFile = localFile;
876  return;
877  }
878  }
879  else
880  {
882  ShowOkPopup(tr("ERROR downloading theme package on master backend."));
883  }
884  }
885 
887  (file.exists()))
888  {
889  // The frontend download is finished
890  if ((errorCode == 0) &&
891  (fileSize > 0))
892  {
894  ThemeExtractThread *extractThread =
898  extractThread, "ThemeExtract");
899 
900  if (!remoteFileIsLocal)
901  RemoteFile::DeleteFile(args[0]);
902 
903  OpenBusyPopup(tr("Installing %1 Theme")
904  .arg(m_downloadTheme->GetName()));
905  }
906  else
907  {
909  ShowOkPopup(tr("ERROR downloading theme package from master backend."));
910  }
911  }
912  }
913  }
914  else if ((me->Message() == "THEME_INSTALLED") &&
915  (m_downloadTheme) &&
917  {
919  CloseBusyPopup();
920  QStringList args = me->ExtraDataList();
921  QFile::remove(args[0]);
922 
923  QString event = QString("THEME_INSTALLED PATH %1")
924  .arg(m_userThemeDir +
927 
929 
930  // Send a message to ourself so we trigger a reload our next chance
931  MythEvent *me = new MythEvent("THEME_RELOAD");
932  qApp->postEvent(this, me);
933  }
934  else if ((me->Message() == "THEME_RELOAD") &&
935  (m_downloadState == dsIdle))
936  {
937  GetMythMainWindow()->JumpTo("Reload Theme");
938  }
939  }
940 }
941 
943 {
945  if (!current)
946  {
947  ShowOkPopup(tr("Error, no theme selected."));
948  return;
949  }
950 
951  ThemeInfo *info = current->GetData().value<ThemeInfo *>();
952  if (!info)
953  {
954  ShowOkPopup(tr("Error, unable to find current theme."));
955  return;
956  }
957 
958  if (!info->GetPreviewPath().startsWith(m_userThemeDir))
959  {
960  ShowOkPopup(tr("%1 is not a user-installed theme and can not "
961  "be deleted.").arg(info->GetName()));
962  return;
963  }
964 
966 
968 }
969 
970 bool ThemeChooser::removeThemeDir(const QString &dirname)
971 {
972  if ((!dirname.startsWith(m_userThemeDir)) &&
973  (!dirname.startsWith(GetMythUI()->GetThemeCacheDir())))
974  return true;
975 
976  QDir dir(dirname);
977 
978  if (!dir.exists())
979  return true;
980 
981  dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
982  QFileInfoList list = dir.entryInfoList();
983  QFileInfoList::const_iterator it = list.begin();
984  const QFileInfo *fi;
985 
986  while (it != list.end())
987  {
988  fi = &(*it++);
989  if (fi->isFile() && !fi->isSymLink())
990  {
991  if (!QFile::remove(fi->absoluteFilePath()))
992  return false;
993  }
994  else if (fi->isDir() && !fi->isSymLink())
995  {
996  if (!removeThemeDir(fi->absoluteFilePath()))
997  return false;
998  }
999  }
1000 
1001  return dir.rmdir(dirname);
1002 }
1003 
1005 
1007  m_updateTimer(new QTimer(this))
1008 {
1009  QString version = MYTH_SOURCE_PATH;
1010 
1011  if (!version.isEmpty() && !version.startsWith("fixes/"))
1012  // Treat devel branches as master
1013  m_mythVersions << "trunk";
1014  else
1015  {
1016  version = MYTH_BINARY_VERSION; // Example: 0.25.20101017-1
1017  version.replace(QRegExp("\\.[0-9]{8,}.*"), "");
1018 
1019  // If a version of the theme for this tag exists, use it...
1020  QRegExp subexp("v[0-9]+.[0-9]+.([0-9]+)-*");
1021  int pos = subexp.indexIn(MYTH_SOURCE_VERSION);
1022  if (pos > -1)
1023  {
1024  QString subversion;
1025  int idx = subexp.cap(1).toInt();
1026  for ( ; idx > 0; --idx)
1027  m_mythVersions << version + "." + QString::number(idx);
1028  }
1030  }
1031 
1034  "remotethemes/themes.zip",
1035  "Temp");
1036 
1037  gCoreContext->SaveSetting("ThemeUpdateStatus", "");
1038 
1039  connect(m_updateTimer, SIGNAL(timeout()), SLOT(checkForUpdate()));
1040 
1041  if (getenv("MYTHTV_DEBUGMDM"))
1042  {
1043  LOG(VB_GENERAL, LOG_INFO, "Checking for theme updates every minute");
1044  m_updateTimer->start(60 * 1000); // Run once a minute
1045  }
1046  else
1047  {
1048  LOG(VB_GENERAL, LOG_INFO, "Checking for theme updates every hour");
1049  m_updateTimer->start(60 * 60 * 1000); // Run once an hour
1050  }
1051 
1052  // Run once 15 seconds from now
1053  QTimer::singleShot(15 * 1000, this, SLOT(checkForUpdate()));
1054 }
1055 
1057 {
1058  if (m_updateTimer)
1059  {
1060  m_updateTimer->stop();
1061  delete m_updateTimer;
1062  m_updateTimer = NULL;
1063  }
1064 }
1065 
1067 {
1068  if (GetMythUI()->GetCurrentLocation(false, true) != "mainmenu")
1069  return;
1070 
1071  ThemeInfo *localTheme = NULL;
1072  int locMaj = 0;
1073  int locMin = 0;
1074 
1076  {
1077  QStringList::iterator Iversion;
1078 
1079  for (Iversion = m_mythVersions.begin();
1080  Iversion != m_mythVersions.end(); ++Iversion)
1081  {
1082 
1083  QString remoteThemeDir =
1086  QString("remotethemes/%1/%2")
1087  .arg(*Iversion)
1088  .arg(GetMythUI()->GetThemeName()),
1089  "Temp");
1090 
1091  QString infoXML = remoteThemeDir;
1092  infoXML.append("/themeinfo.xml");
1093 
1094  LOG(VB_GUI, LOG_INFO, QString("ThemeUpdateChecker Loading '%1'")
1095  .arg(infoXML));
1096 
1097  if (RemoteFile::Exists(infoXML))
1098  {
1099  ThemeInfo *remoteTheme = new ThemeInfo(remoteThemeDir);
1100  if (!remoteTheme || remoteTheme->GetType() & THEME_UNKN)
1101  {
1102  LOG(VB_GENERAL, LOG_ERR,
1103  QString("ThemeUpdateChecker::checkForUpdate(): "
1104  "Unable to create ThemeInfo for %1")
1105  .arg(infoXML));
1106  delete remoteTheme;
1107  remoteTheme = NULL;
1108  return;
1109  }
1110 
1111  if (!localTheme)
1112  {
1113  localTheme = new ThemeInfo(GetMythUI()->GetThemeDir());
1114  if (!localTheme || localTheme->GetType() & THEME_UNKN)
1115  {
1116  LOG(VB_GENERAL, LOG_ERR,
1117  "ThemeUpdateChecker::checkForUpdate(): "
1118  "Unable to create ThemeInfo for current theme");
1119  delete localTheme;
1120  localTheme = NULL;
1121  return;
1122  }
1123  locMaj = localTheme->GetMajorVersion();
1124  locMin = localTheme->GetMinorVersion();
1125  }
1126 
1127  int rmtMaj = remoteTheme->GetMajorVersion();
1128  int rmtMin = remoteTheme->GetMinorVersion();
1129 
1130  delete remoteTheme;
1131  remoteTheme = NULL;
1132 
1133  if ((rmtMaj > locMaj) ||
1134  ((rmtMaj == locMaj) &&
1135  (rmtMin > locMin)))
1136  {
1138  QString("%1-%2.%3").arg(GetMythUI()->GetThemeName())
1139  .arg(rmtMaj).arg(rmtMin);
1140 
1141  QString status = gCoreContext->GetSetting
1142  ("ThemeUpdateStatus");
1143  QString currentLocation = GetMythUI()->GetCurrentLocation
1144  (false, true);
1145 
1146  if ((!status.startsWith(m_lastKnownThemeVersion)) &&
1147  (currentLocation == "mainmenu"))
1148  {
1149  m_currentVersion = QString("%1.%2")
1150  .arg(locMaj).arg(locMin);
1151  m_newVersion = QString("%1.%2").arg(rmtMaj).arg(rmtMin);
1152 
1153  gCoreContext->SaveSetting("ThemeUpdateStatus",
1155  + " notified");
1156 
1157  QString message = tr("Version %1 of the %2 theme is now "
1158  "available in the Theme Chooser. "
1159  "The currently installed version "
1160  "is %3.")
1161  .arg(m_newVersion)
1162  .arg(GetMythUI()->GetThemeName())
1163  .arg(m_currentVersion);
1164 
1165  ShowOkPopup(message);
1166  break;
1167  }
1168  }
1169  }
1170  }
1171  }
1172 
1173  delete localTheme;
1174 }
1175 
1176 /* vim: set expandtab tabstop=4 shiftwidth=4: */
This widget is used for grouping other widgets for display when a particular named state is called...
void ToMap(InfoMap &infoMap) const
Definition: themeinfo.cpp:258
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=NULL)
Definition: mythuiutils.h:27
static bool DeleteFile(const QString &url)
Definition: remotefile.cpp:429
bool removeThemeDir(const QString &dirname)
ThemeInfo * loadThemeInfo(QFileInfo &theme)
int GetMajorVersion() const
Definition: themeinfo.h:36
void Reset(void)
Reset the widget to it&#39;s original state, should not reset changes made by the theme.
void updateProgressBar(int bytesReceived, int bytesTotal)
ThemeChooser(MythScreenStack *parent, const QString &name="ThemeChooser")
Creates a new ThemeChooser Screen.
QString GenMythURL(QString host=QString(), QString port=QString(), QString path=QString(), QString storageGroup=QString())
void start(QRunnable *runnable, QString debugName, int priority=0)
MythConfirmationDialog * ShowOkPopup(const QString &message, QObject *parent, const char *slot, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
All purpose text widget, displays a text string.
Definition: mythuitext.h:28
ThemeChooser * m_parent
void queueDownload(const QString &url, const QString &dest, QObject *caller, const bool reload=false)
Adds a url to the download queue.
void SetData(QVariant data)
QString GetAspect() const
Definition: themeinfo.h:28
void SaveSetting(const QString &key, int newValue)
static Type MythEventMessage
Definition: mythevent.h:65
void removeListener(QObject *listener)
Remove a listener to the observable.
int GetMasterServerPort(void)
Returns the Master Backend control port If no master server port has been defined in the database...
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:97
Basic menu dialog, message and a list of options.
QString m_userThemeDir
Definition: themechooser.h:84
void Reset(void)
Reset the widget to it&#39;s original state, should not reset changes made by the theme.
Definition: mythuitext.cpp:114
static bool downloadURL(const QString &url, QByteArray *buffer)
static bool sortThemeNames(const QFileInfo &s1, const QFileInfo &s2)
virtual bool Create(void)
void removeTheme(void)
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:166
MythScreenStack * GetStack(const QString &stackname)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString m_lastKnownThemeVersion
Definition: themechooser.h:113
QString GetCurrentLocation(bool fullPath=false, bool mainStackOnly=true)
void addListener(QObject *listener)
Add a listener to the observable.
QMap< QString, ThemeInfo * > m_themeFileNameInfos
Definition: themechooser.h:87
DownloadState m_downloadState
Definition: themechooser.h:91
void itemChanged(MythUIButtonListItem *item)
bool extractZIP(const QString &zipFile, const QString &outDir)
bool Load(bool allowLoadInBackground=true, bool forceStat=false)
Load the image(s), wraps ImageLoader::LoadImage()
void BuildFocusList(void)
QString m_currentVersion
Definition: themechooser.h:114
bool m_fullPreviewShowing
Definition: themechooser.h:77
void SetImage(MythImage *image, const QString &name="")
Sets an image directly, should only be used in special circumstances since it bypasses the cache...
QString GetName() const
Definition: themeinfo.h:30
#define LOC
void checkForUpdate(void)
QString GetConfDir(void)
Definition: mythdirs.cpp:224
void showPopupMenu(void)
void AddButton(const QString &title, QVariant data=0, bool newMenu=false, bool setCurrent=false)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
void SetValueByData(QVariant data)
This class is used as a container for messages.
Definition: mythevent.h:15
void Reset()
Reset the widget to it&#39;s original state, should not reset changes made by the theme.
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:83
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
void customEvent(QEvent *e)
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
QString GetSetting(const QString &key, const QString &defaultval="")
void Load(void)
Load data which will ultimately be displayed on-screen or used to determine what appears on-screen (S...
View and select installed themes.
Definition: themechooser.h:27
Create a group of widgets.
Definition: mythuigroup.h:11
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
MythUIImage * m_fullScreenPreview
Definition: themechooser.h:80
bool download(const QString &url, const QString &dest, const bool reload=false)
Downloads a URL to a file in blocking mode.
void ReloadInBackground(void)
void refreshDownloadableThemes(void)
virtual void SetTextFromMap(const InfoMap &infoMap)
QString GetMasterHostName(void)
int GetMinorVersion() const
Definition: themeinfo.h:37
bool m_refreshDownloadableThemes
Definition: themechooser.h:83
void LoadVersion(const QString &version, QStringList &themesSeen, bool alert_user)
void JumpTo(const QString &destination, bool pop=true)
void SetBusyPopupMessage(const QString &message)
MythDialogBox * m_popupMenu
Definition: themechooser.h:93
const char * name
Definition: ParseText.cpp:339
void Init(void)
Used after calling Load() to assign data to widgets and other UI initilisation which is prohibited in...
MythUIHelper * GetMythUI()
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
MythUIType * GetFocusWidget(void) const
MythMainWindow * GetMythMainWindow(void)
MythUIImage * m_preview
Definition: themechooser.h:75
QString RemoteDownloadFile(const QString &url, const QString &storageGroup, const QString &filename)
static MThreadPool * globalInstance(void)
bool keyPressEvent(QKeyEvent *)
Key event handler.
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:472
int GetNumSetting(const QString &key, int defaultval=0)
void toggleFullscreenPreview(void)
QMap< QString, ThemeInfo * > m_themeNameInfos
Definition: themechooser.h:86
QString GetCacheDir(void)
Returns the base directory for all cached files.
Definition: mythdirs.cpp:234
int GetType() const
Definition: themeinfo.h:35
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:38
void CloseBusyPopup(void)
QMap< QString, QString > m_themeStatuses
Definition: themechooser.h:88
bool Create(void)
ThemeInfo * m_downloadTheme
Definition: themechooser.h:89
virtual bool keyPressEvent(QKeyEvent *)
Key event handler.
QString GetDownloadURL() const
Definition: themeinfo.h:39
void SetFilename(const QString &filename)
Must be followed by a call to Load() to load the image.
void SetReturnEvent(QObject *retobject, const QString &resultid)
QFileInfoList m_infoList
Definition: themechooser.h:82
bool SetFocusWidget(MythUIType *widget=NULL)
bool TranslateKeyPress(const QString &context, QKeyEvent *e, QStringList &actions, bool allowJumps=true) MUNUSED_RESULT
Get a list of actions for a keypress in the given context.
QString m_downloadFile
Definition: themechooser.h:90
#define MYTH_BINARY_VERSION
Update this whenever the plug-in ABI changes.
Definition: mythversion.h:15
bool DisplayState(const QString &name)
string themeName
Definition: mythburn.py:189
void toggleThemeUpdateNotifications(void)
void saveAndReload(void)
QString GetThemesParentDir(void)
Definition: mythdirs.cpp:225
QTimer * m_updateTimer
Definition: themechooser.h:110
void OpenBusyPopup(QString message="")
MythUIButtonList * m_themes
Definition: themechooser.h:74
void popupClosed(QString which, int result)
bool SaveSettingOnHost(const QString &key, const QString &newValue, const QString &host)
Screen in which all other widgets are contained and rendered.
QString GetDirectoryName() const
Definition: themeinfo.h:43
Progress bar widget.
QStringList m_mythVersions
Definition: themechooser.h:111
const QString & Message() const
Definition: mythevent.h:57
void Reset(void)
Reset the image back to the default defined in the theme.
ThemeExtractThread(ThemeChooser *parent, const QString &srcFile, const QString &destDir)
QString GetHostName(void)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:155
void DisplayState(const QString &state, const QString &name)
void SetTextFromMap(const InfoMap &infoMap, const QString &state="")
void SendSystemEvent(const QString &msg)
MythUIText * m_fullScreenName
Definition: themechooser.h:79
const QStringList & ExtraDataList() const
Definition: mythevent.h:59
void ResetBusyPopup(void)
QString GetPreviewPath() const
Definition: themeinfo.h:34
MythUIStateType * m_fullPreviewStateType
Definition: themechooser.h:78
MythUIButtonListItem * GetItemCurrent() const
void LoadInBackground(QString message="")