MythTV  0.28pre
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  QString MythVersion = MYTH_SOURCE_PATH;
155 
156  // Treat devel branches as master
157  if (!MythVersion.isEmpty() && !MythVersion.startsWith("fixes/"))
158  // FIXME: For now, treat git master the same as svn trunk
159  MythVersion = "trunk";
160 
161  QStringList themesSeen;
162  QDir themes(m_userThemeDir);
163  themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
164  themes.setSorting(QDir::Name | QDir::IgnoreCase);
165 
166  m_infoList = themes.entryInfoList();
167 
168  for( QFileInfoList::iterator it = m_infoList.begin();
169  it != m_infoList.end();
170  ++it )
171  {
172  if (loadThemeInfo(*it))
173  {
174  themesSeen << (*it).fileName();
175  m_themeStatuses[(*it).fileName()] = "default";
176  }
177  }
178 
179  themes.setPath(GetThemesParentDir());
180  QFileInfoList sharedThemes = themes.entryInfoList();
181  for( QFileInfoList::iterator it = sharedThemes.begin();
182  it != sharedThemes.end();
183  ++it )
184  {
185  if ((!themesSeen.contains((*it).fileName())) &&
186  (loadThemeInfo(*it)))
187  {
188  m_infoList << *it;
189  themesSeen << (*it).fileName();
190  m_themeStatuses[(*it).fileName()] = "default";
191  }
192  }
193 
194  if (MythVersion == "trunk")
195  {
196  LoadVersion(MythVersion, themesSeen, true);
197  LOG(VB_GUI, LOG_INFO, QString("Loading themes for %1").arg(MythVersion));
198  }
199  else
200  {
201 
202  MythVersion = MYTH_BINARY_VERSION; // Example: 0.25.20101017-1
203  MythVersion.replace(QRegExp("\\.[0-9]{8,}.*"), "");
204  LOG(VB_GUI, LOG_INFO, QString("Loading themes for %1").arg(MythVersion));
205  LoadVersion(MythVersion, themesSeen, true);
206 
207  // If a version of the theme for this tag exists, use it...
208  QRegExp subexp("v[0-9]+.[0-9]+.([0-9]+)-*");
209  int pos = subexp.indexIn(MYTH_SOURCE_VERSION);
210  if (pos > -1)
211  {
212  QString subversion;
213  int idx = subexp.cap(1).toInt();
214  for ( ; idx > 0; --idx)
215  {
216  subversion = MythVersion + "." + QString::number(idx);
217  LOG(VB_GUI, LOG_INFO, QString("Loading themes for %1").arg(subversion));
218  LoadVersion(subversion, themesSeen, false);
219  }
220  }
221  }
222 
223  ResetBusyPopup();
224 
225  qSort(m_infoList.begin(), m_infoList.end(), sortThemeNames);
226 }
227 
228 void ThemeChooser::LoadVersion(const QString &version,
229  QStringList &themesSeen, bool alert_user)
230 {
231  QString remoteThemesFile = GetConfDir();
232  remoteThemesFile.append("/tmp/themes.zip");
233  QString themeSite = QString("%1/%2")
234  .arg(gCoreContext->GetSetting("ThemeRepositoryURL",
235  "http://themes.mythtv.org/themes/repository")).arg(version);
236  QDir remoteThemesDir(GetMythUI()->GetThemeCacheDir()
237  .append("/themechooser/").append(version));
238 
239  int downloadFailures =
240  gCoreContext->GetNumSetting("ThemeInfoDownloadFailures", 0);
241  if (QFile::exists(remoteThemesFile))
242  {
243  QFileInfo finfo(remoteThemesFile);
244  if (finfo.lastModified().toUTC() <
245  MythDate::current().addSecs(-600))
246  {
247  LOG(VB_GUI, LOG_INFO, LOC +
248  QString("%1 is over 10 minutes old, forcing "
249  "remote theme list download").arg(remoteThemesFile));
251  }
252 
253  if (!remoteThemesDir.exists())
255  }
256  else if (downloadFailures < 2) // (and themes.zip does not exist)
257  {
258  LOG(VB_GUI, LOG_INFO, LOC +
259  QString("%1 does not exist, forcing remote theme "
260  "list download").arg(remoteThemesFile));
262  }
263 
265  {
266  QFile test(remoteThemesFile);
267  if (test.open(QIODevice::WriteOnly))
268  test.remove();
269  else
270  {
271  ShowOkPopup(tr("Unable to create '%1'").arg(remoteThemesFile));
272  return;
273  }
274 
275  SetBusyPopupMessage(tr("Refreshing Downloadable Themes Information"));
276 
277  QString url = themeSite;
278  url.append("/themes.zip");
279  QString destdir = GetMythUI()->GetThemeCacheDir();
280  destdir.append("/themechooser");
281  QString versiondir = QString("%1/%2").arg(destdir).arg(version);
282  if (!removeThemeDir(versiondir))
283  ShowOkPopup(tr("Unable to remove '%1'").arg(versiondir));
284  QDir dir;
285  if (!dir.mkpath(destdir))
286  ShowOkPopup(tr("Unable to create '%1'").arg(destdir));
287  bool result = GetMythDownloadManager()->download(url, remoteThemesFile);
288 
289  LOG(VB_GUI, LOG_INFO, LOC +
290  QString("Downloading '%1' to '%2'").arg(url).arg(remoteThemesFile));
291 
292  SetBusyPopupMessage(tr("Extracting Downloadable Themes Information"));
293 
294  if (!result || !extractZIP(remoteThemesFile, destdir))
295  {
296  QFile::remove(remoteThemesFile);
297 
298  downloadFailures++;
299  gCoreContext->SaveSetting("ThemeInfoDownloadFailures",
300  downloadFailures);
301 
302  if (!result)
303  {
304  LOG(VB_GUI, LOG_ERR, LOC +
305  QString("Failed to download '%1'").arg(url));
306  if (alert_user)
307  ShowOkPopup(tr("Failed to download '%1'").arg(url));
308  }
309  else
310  {
311  LOG(VB_GUI, LOG_ERR, LOC +
312  QString("Failed to unzip '%1' to '%2'")
313  .arg(remoteThemesFile).arg(destdir));
314  if (alert_user)
315  ShowOkPopup(tr("Failed to unzip '%1' to '%2'")
316  .arg(remoteThemesFile).arg(destdir));
317  }
318  }
319  else
320  LOG(VB_GUI, LOG_INFO, LOC +
321  QString("Unzipped '%1' to '%2'")
322  .arg(remoteThemesFile)
323  .arg(destdir));
324  }
325 
326  QDir themes;
327  themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
328  themes.setSorting(QDir::Name | QDir::IgnoreCase);
329 
330  if ((QFile::exists(remoteThemesFile)) &&
331  (remoteThemesDir.exists()))
332  {
333  SetBusyPopupMessage(tr("Loading Downloadable Themes"));
334 
335  LOG(VB_GUI, LOG_INFO, LOC +
336  QString("%1 and %2 exist, using cached remote themes list")
337  .arg(remoteThemesFile).arg(remoteThemesDir.absolutePath()));
338 
339  QString themesPath = remoteThemesDir.absolutePath();
340  themes.setPath(themesPath);
341 
342  QFileInfoList downloadableThemes = themes.entryInfoList();
343  for( QFileInfoList::iterator it = downloadableThemes.begin();
344  it != downloadableThemes.end();
345  ++it )
346  {
347  QString dirName = (*it).fileName();
348  QString themeName = dirName;
349  QString remoteDir = themeSite;
350  remoteDir.append("/").append(dirName);
351  QString localDir = themes.absolutePath();
352  localDir.append("/").append(dirName);
353 
354  ThemeInfo remoteTheme((*it).absoluteFilePath());
355 
356  if (themesSeen.contains(dirName))
357  {
358  ThemeInfo *localTheme = m_themeNameInfos[dirName];
359 
360  themeName = remoteTheme.GetName();
361 
362  int rmtMaj = remoteTheme.GetMajorVersion();
363  int rmtMin = remoteTheme.GetMinorVersion();
364  int locMaj = localTheme->GetMajorVersion();
365  int locMin = localTheme->GetMinorVersion();
366 
367  if ((rmtMaj > locMaj) ||
368  ((rmtMaj == locMaj) &&
369  (rmtMin > locMin)))
370  {
371  if (loadThemeInfo(*it))
372  {
373  LOG(VB_GUI, LOG_DEBUG, LOC +
374  QString("'%1' old version %2.%3, new version %4.%5")
375  .arg(themeName).arg(locMaj).arg(locMin)
376  .arg(rmtMaj).arg(rmtMin));
377 
378  m_infoList << *it;
379  m_themeStatuses[themeName] = "updateavailable";
380 
381  QFileInfo finfo(remoteTheme.GetPreviewPath());
383  remoteDir.append("/").append(finfo.fileName()),
384  localDir.append("/").append(finfo.fileName()),
385  NULL);
386  }
387  }
388  else if ((rmtMaj == locMaj) &&
389  (rmtMin == locMin))
390  {
391  LOG(VB_GUI, LOG_DEBUG, LOC +
392  QString("'%1' up to date (%2.%3)")
393  .arg(themeName).arg(locMaj).arg(locMin));
394 
395  m_themeStatuses[themeName] = "uptodate";
396  }
397  }
398  else
399  {
400  LOG(VB_GUI, LOG_DEBUG, LOC +
401  QString("'%1' (%2.%3) available")
402  .arg(themeName)
403  .arg(remoteTheme.GetMajorVersion())
404  .arg(remoteTheme.GetMinorVersion()));
405 
406  ThemeInfo *remoteTheme = loadThemeInfo(*it);
407  if (remoteTheme)
408  {
409  themeName = remoteTheme->GetName();
410  themesSeen << dirName;
411  m_infoList << *it;
412  m_themeStatuses[themeName] = "updateavailable";
413 
414  QFileInfo finfo(remoteTheme->GetPreviewPath());
416  remoteDir.append("/").append(finfo.fileName()),
417  localDir.append("/").append(finfo.fileName()),
418  NULL);
419  }
420  }
421  }
422  }
423 
424 }
425 
427 {
428  QString curTheme = gCoreContext->GetSetting("Theme");
429  ThemeInfo *themeinfo = NULL;
430  ThemeInfo *curThemeInfo = NULL;
431  MythUIButtonListItem *item = NULL;
432 
433  m_themes->Reset();
434  for( QFileInfoList::iterator it = m_infoList.begin();
435  it != m_infoList.end();
436  ++it )
437  {
438  QFileInfo &theme = *it;
439 
440  if (!m_themeFileNameInfos.contains(theme.filePath()))
441  continue;
442 
443  themeinfo = m_themeFileNameInfos[theme.filePath()];
444  if (!themeinfo)
445  continue;
446 
447  QString buttonText = QString("%1 %2.%3")
448  .arg(themeinfo->GetName())
449  .arg(themeinfo->GetMajorVersion())
450  .arg(themeinfo->GetMinorVersion());
451 
452  item = new MythUIButtonListItem(m_themes, buttonText);
453  if (item)
454  {
455  if (themeinfo->GetDownloadURL().isEmpty())
456  item->DisplayState("local", "themelocation");
457  else
458  item->DisplayState("remote", "themelocation");
459 
460  item->DisplayState(themeinfo->GetAspect(), "aspectstate");
461 
462  item->DisplayState(m_themeStatuses[themeinfo->GetName()],
463  "themestatus");
464  InfoMap infomap;
465  themeinfo->ToMap(infomap);
466  item->SetTextFromMap(infomap);
467  item->SetData(qVariantFromValue(themeinfo));
468 
469  QString thumbnail = themeinfo->GetPreviewPath();
470  QFileInfo fInfo(thumbnail);
471  // Downloadable themeinfos have thumbnail copies of their preview images
472  if (!themeinfo->GetDownloadURL().isEmpty())
473  thumbnail = thumbnail.append(".thumb.jpg");
474  item->SetImage(thumbnail);
475 
476  if (curTheme == themeinfo->GetDirectoryName())
477  curThemeInfo = themeinfo;
478  }
479  }
480 
482 
483  if (curThemeInfo)
484  m_themes->SetValueByData(qVariantFromValue(curThemeInfo));
485 
487  if (current)
488  itemChanged(current);
489 
490  QString testFile = m_userThemeDir + "/.test";
491  QFile test(testFile);
492  if (test.open(QIODevice::WriteOnly))
493  test.remove();
494  else
495  {
496  ShowOkPopup(tr("Error creating test file, %1 themes directory is "
497  "not writable.").arg(m_userThemeDir));
498  }
499 }
500 
502 {
503  if (theme.fileName() == "default" || theme.fileName() == "default-wide")
504  return NULL;
505 
506  ThemeInfo *themeinfo = NULL;
507  if (theme.exists()) // local directory vs http:// or remote URL
508  themeinfo = new ThemeInfo(theme.absoluteFilePath());
509  else
510  themeinfo = new ThemeInfo(theme.filePath());
511 
512  if (!themeinfo)
513  return NULL;
514 
515  if (themeinfo->GetName().isEmpty() || !(themeinfo->GetType() & THEME_UI))
516  {
517  delete themeinfo;
518  return NULL;
519  }
520 
521  m_themeFileNameInfos[theme.filePath()] = themeinfo;
522  m_themeNameInfos[theme.fileName()] = themeinfo;
523 
524  return themeinfo;
525 }
526 
528 {
529  if (m_popupMenu)
530  return;
531 
532  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
533  QString label = tr("Theme Chooser Menu");
534 
535  m_popupMenu =
536  new MythDialogBox(label, popupStack, "themechoosermenupopup");
537 
538  connect(m_popupMenu, SIGNAL(Closed(QString, int)), SLOT(popupClosed(QString, int)));
539 
540  if (m_popupMenu->Create())
541  popupStack->AddScreen(m_popupMenu);
542  else
543  {
544  delete m_popupMenu;
545  m_popupMenu = NULL;
546  return;
547  }
548 
549  m_popupMenu->SetReturnEvent(this, "popupmenu");
550 
552  {
554  m_popupMenu->AddButton(tr("Hide Fullscreen Preview"),
555  SLOT(toggleFullscreenPreview()));
556  else
557  m_popupMenu->AddButton(tr("Show Fullscreen Preview"),
558  SLOT(toggleFullscreenPreview()));
559  }
560 
561  m_popupMenu->AddButton(tr("Refresh Downloadable Themes"),
562  SLOT(refreshDownloadableThemes()));
563 
565  if (current)
566  {
567  ThemeInfo *info = current->GetData().value<ThemeInfo *>();
568 
569  if (info)
570  {
571  m_popupMenu->AddButton(tr("Select Theme"),
572  SLOT(saveAndReload()));
573 
574  if (info->GetPreviewPath().startsWith(m_userThemeDir))
575  m_popupMenu->AddButton(tr("Delete Theme"),
576  SLOT(removeTheme()));
577  }
578  }
579 
580  if (gCoreContext->GetNumSetting("ThemeUpdateNofications", 1))
581  m_popupMenu->AddButton(tr("Disable Theme Update Notifications"),
583  else
584  m_popupMenu->AddButton(tr("Enable Theme Update Notifications"),
586 }
587 
588 void ThemeChooser::popupClosed(QString which, int result)
589 {
590  (void)which;
591  (void)result;
592 
593  m_popupMenu = NULL;
594 }
595 
596 bool ThemeChooser::keyPressEvent(QKeyEvent *event)
597 {
598  if (GetFocusWidget()->keyPressEvent(event))
599  return true;
600 
601  bool handled = false;
602  QStringList actions;
603  handled = GetMythMainWindow()->TranslateKeyPress("Theme Chooser", event, actions);
604 
605  for (int i = 0; i < actions.size() && !handled; ++i)
606  {
607  QString action = actions[i];
608  handled = true;
609 
610  if (action == "MENU")
611  showPopupMenu();
612  else if (action == "DELETE")
613  removeTheme();
614  else if ((action == "ESCAPE") &&
616  {
618  }
619  else
620  handled = false;
621  }
622 
623  if (!handled && MythScreenType::keyPressEvent(event))
624  handled = true;
625 
626  return handled;
627 }
628 
630 {
632  {
634  {
637 
638  if (m_fullScreenName)
640 
642  m_fullPreviewShowing = false;
643  }
644  else
645  {
647  ThemeInfo *info = item->GetData().value<ThemeInfo*>();
648  if (info)
649  {
651  {
654  }
655 
656  if (m_fullScreenName)
657  m_fullScreenName->SetText(info->GetName());
658 
659  m_fullPreviewStateType->DisplayState("fullscreen");
660  m_fullPreviewShowing = true;
661  }
662  }
663  }
664 }
665 
667 {
668  if (gCoreContext->GetNumSetting("ThemeUpdateNofications", 1))
669  gCoreContext->SaveSettingOnHost("ThemeUpdateNofications", "0", "");
670  else
671  gCoreContext->SaveSettingOnHost("ThemeUpdateNofications", "1", "");
672 }
673 
675 {
676  LOG(VB_GUI, LOG_INFO, LOC + "Forcing remote theme list refresh");
678  gCoreContext->SaveSetting("ThemeInfoDownloadFailures", 0);
680 }
681 
683 {
685  if (current)
686  saveAndReload(current);
687 }
688 
690 {
691  ThemeInfo *info = item->GetData().value<ThemeInfo *>();
692 
693  if (!info)
694  return;
695 
696  if (!info->GetDownloadURL().isEmpty())
697  {
698  QString testFile = m_userThemeDir + "/.test";
699  QFile test(testFile);
700  if (test.open(QIODevice::WriteOnly))
701  test.remove();
702  else
703  {
704  ShowOkPopup(tr("Unable to install theme, %1 themes directory is "
705  "not writable.").arg(m_userThemeDir));
706  return;
707  }
708 
709  QString downloadURL = info->GetDownloadURL();
710  QFileInfo qfile(downloadURL);
711  QString baseName = qfile.fileName();
712 
713  if (!gCoreContext->GetSetting("ThemeDownloadURL").isEmpty())
714  {
715  QStringList tokens =
716  gCoreContext->GetSetting("ThemeDownloadURL")
717  .split(";", QString::SkipEmptyParts);
718  QString origURL = downloadURL;
719  downloadURL.replace(tokens[0], tokens[1]);
720  LOG(VB_FILE, LOG_WARNING, LOC +
721  QString("Theme download URL overridden from %1 to %2.")
722  .arg(origURL).arg(downloadURL));
723  }
724 
725  OpenBusyPopup(tr("Downloading %1 Theme").arg(info->GetName()));
726  m_downloadTheme = info;
727 #if 0
728  m_downloadFile = RemoteDownloadFile(downloadURL,
729  "Temp", baseName);
731 #else
732  QString localFile = GetConfDir() + "/tmp/" + baseName;
733  GetMythDownloadManager()->queueDownload(downloadURL, localFile, this);
734  m_downloadFile = localFile;
736 #endif
737  }
738  else
739  {
740  gCoreContext->SaveSetting("Theme", info->GetDirectoryName());
741  GetMythMainWindow()->JumpTo("Reload Theme");
742  }
743 }
744 
746 {
747  ThemeInfo *info = item->GetData().value<ThemeInfo*>();
748 
749  if (!info)
750  return;
751 
752  QFileInfo preview(info->GetPreviewPath());
753  InfoMap infomap;
754  info->ToMap(infomap);
755  SetTextFromMap(infomap);
756  if (m_preview)
757  {
758  if (preview.exists())
759  {
761  m_preview->Load();
762  }
763  else
764  m_preview->Reset();
765  }
767  {
769  {
770  if (preview.exists())
771  {
774  }
775  else
777  }
778 
779  if (m_fullScreenName)
780  m_fullScreenName->SetText(info->GetName());
781  }
782 
783  MythUIStateType *themeLocation =
784  dynamic_cast<MythUIStateType*>(GetChild("themelocation"));
785  if (themeLocation)
786  {
787  if (info->GetDownloadURL().isEmpty())
788  themeLocation->DisplayState("local");
789  else
790  themeLocation->DisplayState("remote");
791  }
792 
793  MythUIStateType *aspectState =
794  dynamic_cast<MythUIStateType*>(GetChild("aspectstate"));
795  if (aspectState)
796  aspectState->DisplayState(info->GetAspect());
797 }
798 
799 void ThemeChooser::updateProgressBar(int bytesReceived,
800  int bytesTotal)
801 {
802  MythUIProgressBar *progressBar =
803  dynamic_cast<MythUIProgressBar *>(GetChild("downloadprogressbar"));
804 
805  if (!progressBar)
806  return;
807 
808  progressBar->SetUsed(bytesReceived);
809  progressBar->SetTotal(bytesTotal);
810 }
811 
813 {
814  if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage)
815  {
816  MythEvent *me = (MythEvent *)e;
817  QStringList tokens = me->Message().split(" ", QString::SkipEmptyParts);
818 
819  if (tokens.isEmpty())
820  return;
821 
822  if (tokens[0] == "DOWNLOAD_FILE")
823  {
824  QStringList args = me->ExtraDataList();
825  if ((m_downloadState == dsIdle) ||
826  (tokens.size() != 2) ||
827  (!m_downloadTheme) ||
828  (args[1] != m_downloadFile))
829  return;
830 
831  if (tokens[1] == "UPDATE")
832  {
833  updateProgressBar(args[2].toInt(), args[3].toInt());
834  }
835  else if (tokens[1] == "FINISHED")
836  {
837  bool remoteFileIsLocal = false;
838  int fileSize = args[2].toInt();
839  int errorCode = args[4].toInt();
840 
841  CloseBusyPopup();
842 
843  QFileInfo file(m_downloadFile);
845  (m_downloadFile.startsWith("myth://")))
846  {
847  // The backend download is finished so start the
848  // frontend download
849  if ((errorCode == 0) &&
850  (fileSize > 0))
851  {
853  QString localFile = GetConfDir() + "/tmp/" +
854  file.fileName();
855  file.setFile(localFile);
856 
857  if (file.exists())
858  {
859  remoteFileIsLocal = true;
860  m_downloadFile = localFile;
861  }
862  else
863  {
865  m_downloadFile, localFile, this);
866  OpenBusyPopup(tr("Copying %1 Theme Package")
867  .arg(m_downloadTheme->GetName()));
868  m_downloadFile = localFile;
869  return;
870  }
871  }
872  else
873  {
875  ShowOkPopup(tr("ERROR downloading theme package on master backend."));
876  }
877  }
878 
880  (file.exists()))
881  {
882  // The frontend download is finished
883  if ((errorCode == 0) &&
884  (fileSize > 0))
885  {
887  ThemeExtractThread *extractThread =
891  extractThread, "ThemeExtract");
892 
893  if (!remoteFileIsLocal)
894  RemoteFile::DeleteFile(args[0]);
895 
896  OpenBusyPopup(tr("Installing %1 Theme")
897  .arg(m_downloadTheme->GetName()));
898  }
899  else
900  {
902  ShowOkPopup(tr("ERROR downloading theme package from master backend."));
903  }
904  }
905  }
906  }
907  else if ((me->Message() == "THEME_INSTALLED") &&
908  (m_downloadTheme) &&
910  {
912  CloseBusyPopup();
913  QStringList args = me->ExtraDataList();
914  QFile::remove(args[0]);
915 
916  QString event = QString("THEME_INSTALLED PATH %1")
917  .arg(m_userThemeDir +
920 
922 
923  // Send a message to ourself so we trigger a reload our next chance
924  MythEvent *me = new MythEvent("THEME_RELOAD");
925  qApp->postEvent(this, me);
926  }
927  else if ((me->Message() == "THEME_RELOAD") &&
928  (m_downloadState == dsIdle))
929  {
930  GetMythMainWindow()->JumpTo("Reload Theme");
931  }
932  }
933 }
934 
936 {
938  if (!current)
939  {
940  ShowOkPopup(tr("Error, no theme selected."));
941  return;
942  }
943 
944  ThemeInfo *info = current->GetData().value<ThemeInfo *>();
945  if (!info)
946  {
947  ShowOkPopup(tr("Error, unable to find current theme."));
948  return;
949  }
950 
951  if (!info->GetPreviewPath().startsWith(m_userThemeDir))
952  {
953  ShowOkPopup(tr("%1 is not a user-installed theme and can not "
954  "be deleted.").arg(info->GetName()));
955  return;
956  }
957 
959 
961 }
962 
964 {
965  if ((!dirname.startsWith(m_userThemeDir)) &&
966  (!dirname.startsWith(GetMythUI()->GetThemeCacheDir())))
967  return true;
968 
969  QDir dir(dirname);
970 
971  if (!dir.exists())
972  return true;
973 
974  dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
975  QFileInfoList list = dir.entryInfoList();
976  QFileInfoList::const_iterator it = list.begin();
977  const QFileInfo *fi;
978 
979  while (it != list.end())
980  {
981  fi = &(*it++);
982  if (fi->isFile() && !fi->isSymLink())
983  {
984  if (!QFile::remove(fi->absoluteFilePath()))
985  return false;
986  }
987  else if (fi->isDir() && !fi->isSymLink())
988  {
989  if (!removeThemeDir(fi->absoluteFilePath()))
990  return false;
991  }
992  }
993 
994  return dir.rmdir(dirname);
995 }
996 
998 
1000  m_updateTimer(new QTimer(this))
1001 {
1002  QString version = MYTH_SOURCE_PATH;
1003 
1004  if (!version.isEmpty() && !version.startsWith("fixes/"))
1005  // Treat devel branches as master
1006  m_mythVersions << "trunk";
1007  else
1008  {
1009  version = MYTH_BINARY_VERSION; // Example: 0.25.20101017-1
1010  version.replace(QRegExp("\\.[0-9]{8,}.*"), "");
1011 
1012  // If a version of the theme for this tag exists, use it...
1013  QRegExp subexp("v[0-9]+.[0-9]+.([0-9]+)-*");
1014  int pos = subexp.indexIn(MYTH_SOURCE_VERSION);
1015  if (pos > -1)
1016  {
1017  QString subversion;
1018  int idx = subexp.cap(1).toInt();
1019  for ( ; idx > 0; --idx)
1020  m_mythVersions << version + "." + QString::number(idx);
1021  }
1023  }
1024 
1027  "remotethemes/themes.zip",
1028  "Temp");
1029 
1030  gCoreContext->SaveSetting("ThemeUpdateStatus", "");
1031 
1032  connect(m_updateTimer, SIGNAL(timeout()), SLOT(checkForUpdate()));
1033  m_updateTimer->start(60 * 60 * 1000); // Run once an hour
1034 
1035  // Run once 15 seconds from now
1036  QTimer::singleShot(15 * 1000, this, SLOT(checkForUpdate()));
1037 }
1038 
1040 {
1041  if (m_updateTimer)
1042  {
1043  m_updateTimer->stop();
1044  delete m_updateTimer;
1045  m_updateTimer = NULL;
1046  }
1047 }
1048 
1050 {
1051  if (GetMythUI()->GetCurrentLocation(false, true) != "mainmenu")
1052  return;
1053 
1054  ThemeInfo *localTheme = NULL;
1055  int locMaj = 0;
1056  int locMin = 0;
1057 
1059  {
1060  QStringList::iterator Iversion;
1061 
1062  for (Iversion = m_mythVersions.begin();
1063  Iversion != m_mythVersions.end(); ++Iversion)
1064  {
1065 
1066  QString remoteThemeDir =
1069  QString("remotethemes/%1/%2")
1070  .arg(*Iversion)
1071  .arg(GetMythUI()->GetThemeName()),
1072  "Temp");
1073 
1074  QString infoXML = remoteThemeDir;
1075  infoXML.append("/themeinfo.xml");
1076 
1077  LOG(VB_GUI, LOG_INFO, QString("ThemeUpdateChecker Loading '%1'")
1078  .arg(infoXML));
1079 
1080  if (RemoteFile::Exists(infoXML))
1081  {
1082  ThemeInfo *remoteTheme = new ThemeInfo(remoteThemeDir);
1083  if (!remoteTheme || remoteTheme->GetType() & THEME_UNKN)
1084  {
1085  LOG(VB_GENERAL, LOG_ERR,
1086  QString("ThemeUpdateChecker::checkForUpdate(): "
1087  "Unable to create ThemeInfo for %1")
1088  .arg(infoXML));
1089  delete remoteTheme;
1090  remoteTheme = NULL;
1091  return;
1092  }
1093 
1094  if (!localTheme)
1095  {
1096  localTheme = new ThemeInfo(GetMythUI()->GetThemeDir());
1097  if (!localTheme || localTheme->GetType() & THEME_UNKN)
1098  {
1099  LOG(VB_GENERAL, LOG_ERR,
1100  "ThemeUpdateChecker::checkForUpdate(): "
1101  "Unable to create ThemeInfo for current theme");
1102  delete localTheme;
1103  localTheme = NULL;
1104  return;
1105  }
1106  locMaj = localTheme->GetMajorVersion();
1107  locMin = localTheme->GetMinorVersion();
1108  }
1109 
1110  int rmtMaj = remoteTheme->GetMajorVersion();
1111  int rmtMin = remoteTheme->GetMinorVersion();
1112 
1113  delete remoteTheme;
1114  remoteTheme = NULL;
1115 
1116  if ((rmtMaj > locMaj) ||
1117  ((rmtMaj == locMaj) &&
1118  (rmtMin > locMin)))
1119  {
1121  QString("%1-%2.%3").arg(GetMythUI()->GetThemeName())
1122  .arg(rmtMaj).arg(rmtMin);
1123 
1124  QString status = gCoreContext->GetSetting
1125  ("ThemeUpdateStatus");
1126  QString currentLocation = GetMythUI()->GetCurrentLocation
1127  (false, true);
1128 
1129  if ((!status.startsWith(m_lastKnownThemeVersion)) &&
1130  (currentLocation == "mainmenu"))
1131  {
1132  m_currentVersion = QString("%1.%2")
1133  .arg(locMaj).arg(locMin);
1134  m_newVersion = QString("%1.%2").arg(rmtMaj).arg(rmtMin);
1135 
1136  gCoreContext->SaveSetting("ThemeUpdateStatus",
1138  + " notified");
1139 
1140  QString message = tr("Version %1 of the %2 theme is now "
1141  "available in the Theme Chooser. "
1142  "The currently installed version "
1143  "is %3.")
1144  .arg(m_newVersion)
1145  .arg(GetMythUI()->GetThemeName())
1146  .arg(m_currentVersion);
1147 
1148  ShowOkPopup(message);
1149  break;
1150  }
1151  }
1152  }
1153  }
1154  }
1155 
1156  delete localTheme;
1157 }
1158 
1159 /* vim: set expandtab tabstop=4 shiftwidth=4: */
This widget is used for grouping other widgets for display when a particular named state is called...
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=NULL)
Definition: mythuiutils.h:26
static bool DeleteFile(const QString &url)
Definition: remotefile.cpp:381
bool removeThemeDir(const QString &dirname)
ThemeInfo * loadThemeInfo(QFileInfo &theme)
QString GetName() const
Definition: themeinfo.h:30
void Reset(void)
Reset the widget to it's original state, should not reset changes made by the theme.
void ToMap(InfoMap &infoMap) const
Definition: themeinfo.cpp:246
void updateProgressBar(int bytesReceived, int bytesTotal)
ThemeChooser(MythScreenStack *parent, const QString &name="ThemeChooser")
Creates a new ThemeChooser Screen.
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:83
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)
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)
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:88
int GetMinorVersion() const
Definition: themeinfo.h:37
Basic menu dialog, message and a list of options.
QString m_userThemeDir
Definition: themechooser.h:84
void Reset(void)
Reset the widget to it'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
AllMusic * parent
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 GetPreviewPath() const
Definition: themeinfo.h:34
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
MythUIButtonListItem * GetItemCurrent() const
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 GetDownloadURL() const
Definition: themeinfo.h:39
void checkForUpdate(void)
QString GetThemeCacheDir(void)
QString GetConfDir(void)
Definition: mythdirs.cpp:140
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's original state, should not reset changes made by the theme.
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="")
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:150
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
tuple dirname
Definition: mirobridge.py:824
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 GetMajorVersion() const
Definition: themeinfo.h:36
QString GetDirectoryName() const
Definition: themeinfo.h:43
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:338
MythUIType * GetFocusWidget(void) const
void Init(void)
Used after calling Load() to assign data to widgets and other UI initilisation which is prohibited in...
QString GetAspect() const
Definition: themeinfo.h:28
MythUIHelper * GetMythUI()
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
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:424
int GetNumSetting(const QString &key, int defaultval=0)
void toggleFullscreenPreview(void)
QMap< QString, ThemeInfo * > m_themeNameInfos
Definition: themechooser.h:86
void CloseBusyPopup(void)
QMap< QString, QString > m_themeStatuses
Definition: themechooser.h:88
bool Create(void)
ThemeInfo * m_downloadTheme
Definition: themechooser.h:89
typedef void(APIENTRY *MYTH_GLTEXIMAGE1DPROC)(GLenum target
virtual bool keyPressEvent(QKeyEvent *)
Key event handler.
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.
const QStringList & ExtraDataList() const
Definition: mythevent.h:59
int GetType() const
Definition: themeinfo.h:35
QString m_downloadFile
Definition: themechooser.h:90
bool DisplayState(const QString &name)
string themeName
Definition: mythburn.py:189
void toggleThemeUpdateNotifications(void)
void saveAndReload(void)
QString GetThemesParentDir(void)
Definition: mythdirs.cpp:141
static const QString LOC
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.
Progress bar widget.
QStringList m_mythVersions
Definition: themechooser.h:111
void Reset(void)
Reset the image back to the default defined in the theme.
ThemeExtractThread(ThemeChooser *parent, const QString &srcFile, const QString &destDir)
const QString & Message() const
Definition: mythevent.h:57
QString GetHostName(void)
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
void ResetBusyPopup(void)
MythUIStateType * m_fullPreviewStateType
Definition: themechooser.h:78
void LoadInBackground(QString message="")