MythTV  0.28pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
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 
32 #define LOC QString("ThemeChooser: ")
33 #define LOC_WARN QString("ThemeChooser, Warning: ")
34 #define LOC_ERR QString("ThemeChooser, Error: ")
35 
39 class ThemeExtractThread : public QRunnable
40 {
41  public:
43  const QString &srcFile, const QString &destDir) :
44  m_parent(parent),
45  m_srcFile(srcFile),
46  m_destDir(destDir)
47  {
48  m_srcFile.detach();
49  m_destDir.detach();
50  }
51 
52  void run()
53  {
55 
56  MythEvent *me =
57  new MythEvent("THEME_INSTALLED", QStringList(m_srcFile));
58  QCoreApplication::postEvent(m_parent, me);
59  }
60 
61  private:
63  QString m_srcFile;
64  QString m_destDir;
65 };
66 
67 
73  const QString &name) :
74  MythScreenType(parent, name),
75  m_themes(NULL),
76  m_preview(NULL),
77  m_fullPreviewShowing(false),
78  m_fullPreviewStateType(NULL),
79  m_fullScreenName(NULL),
80  m_fullScreenPreview(NULL),
81  m_refreshDownloadableThemes(false),
82  m_downloadTheme(NULL),
83  m_downloadState(dsIdle),
84  m_popupMenu(NULL)
85 {
87 
88  StorageGroup sgroup("Themes", gCoreContext->GetHostName());
89  m_userThemeDir = sgroup.GetFirstDir(true);
90 }
91 
93 {
95 }
96 
97 static bool sortThemeNames(const QFileInfo &s1, const QFileInfo &s2)
98 {
99  return s1.fileName().toLower() < s2.fileName().toLower();
100 }
101 
102 
104 {
105  // Load the theme for this screen
106  if (!LoadWindowFromXML("settings-ui.xml", "themechooser", this))
107  return false;
108 
109  bool err = false;
110  UIUtilE::Assign(this, m_themes, "themes", &err);
111 
112  UIUtilW::Assign(this, m_preview, "preview");
113  UIUtilW::Assign(this, m_fullPreviewStateType, "fullpreviewstate");
114 
116  {
117  MythUIGroup *state =
118  dynamic_cast<MythUIGroup*>
119  (m_fullPreviewStateType->GetChild("fullscreen"));
120  if (state)
121  {
123  dynamic_cast<MythUIText*>(state->GetChild("fullscreenname"));
125  dynamic_cast<MythUIImage*>(state->GetChild("fullscreenpreview"));
126  }
127  }
128 
129  if (err)
130  {
131  LOG(VB_GENERAL, LOG_ERR, LOC + "Cannot load screen 'themechooser'");
132  return false;
133  }
134 
135  connect(m_themes, SIGNAL(itemClicked(MythUIButtonListItem*)),
136  this, SLOT(saveAndReload(MythUIButtonListItem*)));
137  connect(m_themes, SIGNAL(itemSelected(MythUIButtonListItem*)),
138  this, SLOT(itemChanged(MythUIButtonListItem*)));
139 
140  BuildFocusList();
141 
143 
144  return true;
145 }
146 
148 {
149  SetBusyPopupMessage(tr("Loading Installed Themes"));
150 
151  QString MythVersion = MYTH_SOURCE_PATH;
152 
153  // Treat devel branches as master
154  if (!MythVersion.isEmpty() && !MythVersion.startsWith("fixes/"))
155  // FIXME: For now, treat git master the same as svn trunk
156  MythVersion = "trunk";
157 
158  QStringList themesSeen;
159  QDir themes(m_userThemeDir);
160  themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
161  themes.setSorting(QDir::Name | QDir::IgnoreCase);
162 
163  m_infoList = themes.entryInfoList();
164 
165  for( QFileInfoList::iterator it = m_infoList.begin();
166  it != m_infoList.end();
167  ++it )
168  {
169  if (loadThemeInfo(*it))
170  {
171  themesSeen << (*it).fileName();
172  m_themeStatuses[(*it).fileName()] = "default";
173  }
174  }
175 
176  themes.setPath(GetThemesParentDir());
177  QFileInfoList sharedThemes = themes.entryInfoList();
178  for( QFileInfoList::iterator it = sharedThemes.begin();
179  it != sharedThemes.end();
180  ++it )
181  {
182  if ((!themesSeen.contains((*it).fileName())) &&
183  (loadThemeInfo(*it)))
184  {
185  m_infoList << *it;
186  themesSeen << (*it).fileName();
187  m_themeStatuses[(*it).fileName()] = "default";
188  }
189  }
190 
191  if (MythVersion == "trunk")
192  {
193  LoadVersion(MythVersion, themesSeen, true);
194  LOG(VB_GUI, LOG_INFO, QString("Loading themes for %1").arg(MythVersion));
195  }
196  else
197  {
198 
199  MythVersion = MYTH_BINARY_VERSION; // Example: 0.25.20101017-1
200  MythVersion.replace(QRegExp("\\.[0-9]{8,}.*"), "");
201  LOG(VB_GUI, LOG_INFO, QString("Loading themes for %1").arg(MythVersion));
202  LoadVersion(MythVersion, themesSeen, true);
203 
204  // If a version of the theme for this tag exists, use it...
205  QRegExp subexp("v[0-9]+.[0-9]+.([0-9]+)-*");
206  int pos = subexp.indexIn(MYTH_SOURCE_VERSION);
207  if (pos > -1)
208  {
209  QString subversion;
210  int idx = subexp.cap(1).toInt();
211  for ( ; idx > 0; --idx)
212  {
213  subversion = MythVersion + "." + QString::number(idx);
214  LOG(VB_GUI, LOG_INFO, QString("Loading themes for %1").arg(subversion));
215  LoadVersion(subversion, themesSeen, false);
216  }
217  }
218  }
219 
220  ResetBusyPopup();
221 
222  qSort(m_infoList.begin(), m_infoList.end(), sortThemeNames);
223 }
224 
225 void ThemeChooser::LoadVersion(const QString &version,
226  QStringList &themesSeen, bool alert_user)
227 {
228  QString remoteThemesFile = GetConfDir();
229  remoteThemesFile.append("/tmp/themes.zip");
230  QString themeSite = QString("%1/%2")
231  .arg(gCoreContext->GetSetting("ThemeRepositoryURL",
232  "http://themes.mythtv.org/themes/repository")).arg(version);
233  QDir remoteThemesDir(GetMythUI()->GetThemeCacheDir()
234  .append("/themechooser/").append(version));
235 
236  int downloadFailures =
237  gCoreContext->GetNumSetting("ThemeInfoDownloadFailures", 0);
238  if (QFile::exists(remoteThemesFile))
239  {
240  QFileInfo finfo(remoteThemesFile);
241  if (finfo.lastModified().toUTC() <
242  MythDate::current().addSecs(-600))
243  {
244  LOG(VB_GUI, LOG_INFO, LOC +
245  QString("%1 is over 10 minutes old, forcing "
246  "remote theme list download").arg(remoteThemesFile));
248  }
249 
250  if (!remoteThemesDir.exists())
252  }
253  else if (downloadFailures < 2) // (and themes.zip does not exist)
254  {
255  LOG(VB_GUI, LOG_INFO, LOC +
256  QString("%1 does not exist, forcing remote theme "
257  "list download").arg(remoteThemesFile));
259  }
260 
262  {
263  QFile test(remoteThemesFile);
264  if (test.open(QIODevice::WriteOnly))
265  test.remove();
266  else
267  {
268  ShowOkPopup(tr("Unable to create '%1'").arg(remoteThemesFile));
269  return;
270  }
271 
272  SetBusyPopupMessage(tr("Refreshing Downloadable Themes Information"));
273 
274  QString url = themeSite;
275  url.append("/themes.zip");
276  QString destdir = GetMythUI()->GetThemeCacheDir();
277  destdir.append("/themechooser");
278  QString versiondir = QString("%1/%2").arg(destdir).arg(version);
279  if (!removeThemeDir(versiondir))
280  ShowOkPopup(tr("Unable to remove '%1'").arg(versiondir));
281  QDir dir;
282  if (!dir.mkpath(destdir))
283  ShowOkPopup(tr("Unable to create '%1'").arg(destdir));
284  bool result = GetMythDownloadManager()->download(url, remoteThemesFile);
285 
286  LOG(VB_GUI, LOG_INFO, LOC +
287  QString("Downloading '%1' to '%2'").arg(url).arg(remoteThemesFile));
288 
289  SetBusyPopupMessage(tr("Extracting Downloadable Themes Information"));
290 
291  if (!result || !extractZIP(remoteThemesFile, destdir))
292  {
293  QFile::remove(remoteThemesFile);
294 
295  downloadFailures++;
296  gCoreContext->SaveSetting("ThemeInfoDownloadFailures",
297  downloadFailures);
298 
299  if (!result)
300  {
301  LOG(VB_GUI, LOG_ERR, LOC +
302  QString("Failed to download '%1'").arg(url));
303  if (alert_user)
304  ShowOkPopup(tr("Failed to download '%1'").arg(url));
305  }
306  else
307  {
308  LOG(VB_GUI, LOG_ERR, LOC +
309  QString("Failed to unzip '%1' to '%2'")
310  .arg(remoteThemesFile).arg(destdir));
311  if (alert_user)
312  ShowOkPopup(tr("Failed to unzip '%1' to '%2'")
313  .arg(remoteThemesFile).arg(destdir));
314  }
315  }
316  else
317  LOG(VB_GUI, LOG_INFO, LOC +
318  QString("Unzipped '%1' to '%2'")
319  .arg(remoteThemesFile)
320  .arg(destdir));
321  }
322 
323  QDir themes;
324  themes.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
325  themes.setSorting(QDir::Name | QDir::IgnoreCase);
326 
327  if ((QFile::exists(remoteThemesFile)) &&
328  (remoteThemesDir.exists()))
329  {
330  SetBusyPopupMessage(tr("Loading Downloadable Themes"));
331 
332  LOG(VB_GUI, LOG_INFO, LOC +
333  QString("%1 and %2 exist, using cached remote themes list")
334  .arg(remoteThemesFile).arg(remoteThemesDir.absolutePath()));
335 
336  QString themesPath = remoteThemesDir.absolutePath();
337  themes.setPath(themesPath);
338 
339  QFileInfoList downloadableThemes = themes.entryInfoList();
340  for( QFileInfoList::iterator it = downloadableThemes.begin();
341  it != downloadableThemes.end();
342  ++it )
343  {
344  QString dirName = (*it).fileName();
345  QString themeName = dirName;
346  QString remoteDir = themeSite;
347  remoteDir.append("/").append(dirName);
348  QString localDir = themes.absolutePath();
349  localDir.append("/").append(dirName);
350 
351  ThemeInfo remoteTheme((*it).absoluteFilePath());
352 
353  if (themesSeen.contains(dirName))
354  {
355  ThemeInfo *localTheme = m_themeNameInfos[dirName];
356 
357  themeName = remoteTheme.GetName();
358 
359  int rmtMaj = remoteTheme.GetMajorVersion();
360  int rmtMin = remoteTheme.GetMinorVersion();
361  int locMaj = localTheme->GetMajorVersion();
362  int locMin = localTheme->GetMinorVersion();
363 
364  if ((rmtMaj > locMaj) ||
365  ((rmtMaj == locMaj) &&
366  (rmtMin > locMin)))
367  {
368  if (loadThemeInfo(*it))
369  {
370  LOG(VB_GUI, LOG_DEBUG, LOC +
371  QString("'%1' old version %2.%3, new version %4.%5")
372  .arg(themeName).arg(locMaj).arg(locMin)
373  .arg(rmtMaj).arg(rmtMin));
374 
375  m_infoList << *it;
376  m_themeStatuses[themeName] = "updateavailable";
377 
378  QFileInfo finfo(remoteTheme.GetPreviewPath());
380  remoteDir.append("/").append(finfo.fileName()),
381  localDir.append("/").append(finfo.fileName()),
382  NULL);
383  }
384  }
385  else if ((rmtMaj == locMaj) &&
386  (rmtMin == locMin))
387  {
388  LOG(VB_GUI, LOG_DEBUG, LOC +
389  QString("'%1' up to date (%2.%3)")
390  .arg(themeName).arg(locMaj).arg(locMin));
391 
392  m_themeStatuses[themeName] = "uptodate";
393  }
394  }
395  else
396  {
397  LOG(VB_GUI, LOG_DEBUG, LOC +
398  QString("'%1' (%2.%3) available")
399  .arg(themeName)
400  .arg(remoteTheme.GetMajorVersion())
401  .arg(remoteTheme.GetMinorVersion()));
402 
403  ThemeInfo *remoteTheme = loadThemeInfo(*it);
404  if (remoteTheme)
405  {
406  themeName = remoteTheme->GetName();
407  themesSeen << dirName;
408  m_infoList << *it;
409  m_themeStatuses[themeName] = "updateavailable";
410 
411  QFileInfo finfo(remoteTheme->GetPreviewPath());
413  remoteDir.append("/").append(finfo.fileName()),
414  localDir.append("/").append(finfo.fileName()),
415  NULL);
416  }
417  }
418  }
419  }
420 
421 }
422 
424 {
425  QString curTheme = gCoreContext->GetSetting("Theme");
426  ThemeInfo *themeinfo = NULL;
427  ThemeInfo *curThemeInfo = NULL;
428  MythUIButtonListItem *item = NULL;
429 
430  m_themes->Reset();
431  for( QFileInfoList::iterator it = m_infoList.begin();
432  it != m_infoList.end();
433  ++it )
434  {
435  QFileInfo &theme = *it;
436 
437  if (!m_themeFileNameInfos.contains(theme.filePath()))
438  continue;
439 
440  themeinfo = m_themeFileNameInfos[theme.filePath()];
441  if (!themeinfo)
442  continue;
443 
444  QString buttonText = QString("%1 %2.%3")
445  .arg(themeinfo->GetName())
446  .arg(themeinfo->GetMajorVersion())
447  .arg(themeinfo->GetMinorVersion());
448 
449  item = new MythUIButtonListItem(m_themes, buttonText);
450  if (item)
451  {
452  if (themeinfo->GetDownloadURL().isEmpty())
453  item->DisplayState("local", "themelocation");
454  else
455  item->DisplayState("remote", "themelocation");
456 
457  item->DisplayState(themeinfo->GetAspect(), "aspectstate");
458 
459  item->DisplayState(m_themeStatuses[themeinfo->GetName()],
460  "themestatus");
461  InfoMap infomap;
462  themeinfo->ToMap(infomap);
463  item->SetTextFromMap(infomap);
464  item->SetData(qVariantFromValue(themeinfo));
465 
466  QString thumbnail = themeinfo->GetPreviewPath();
467  QFileInfo fInfo(thumbnail);
468  // Downloadable themeinfos have thumbnail copies of their preview images
469  if (!themeinfo->GetDownloadURL().isEmpty())
470  thumbnail = thumbnail.append(".thumb.jpg");
471  item->SetImage(thumbnail);
472 
473  if (curTheme == themeinfo->GetDirectoryName())
474  curThemeInfo = themeinfo;
475  }
476  }
477 
479 
480  if (curThemeInfo)
481  m_themes->SetValueByData(qVariantFromValue(curThemeInfo));
482 
484  if (current)
485  itemChanged(current);
486 
487  QString testFile = m_userThemeDir + "/.test";
488  QFile test(testFile);
489  if (test.open(QIODevice::WriteOnly))
490  test.remove();
491  else
492  {
493  ShowOkPopup(tr("Error creating test file, %1 themes directory is "
494  "not writable.").arg(m_userThemeDir));
495  }
496 }
497 
499 {
500  if (theme.fileName() == "default" || theme.fileName() == "default-wide")
501  return NULL;
502 
503  ThemeInfo *themeinfo = NULL;
504  if (theme.exists()) // local directory vs http:// or remote URL
505  themeinfo = new ThemeInfo(theme.absoluteFilePath());
506  else
507  themeinfo = new ThemeInfo(theme.filePath());
508 
509  if (!themeinfo)
510  return NULL;
511 
512  if (themeinfo->GetName().isEmpty() || !(themeinfo->GetType() & THEME_UI))
513  {
514  delete themeinfo;
515  return NULL;
516  }
517 
518  m_themeFileNameInfos[theme.filePath()] = themeinfo;
519  m_themeNameInfos[theme.fileName()] = themeinfo;
520 
521  return themeinfo;
522 }
523 
525 {
526  if (m_popupMenu)
527  return;
528 
529  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
530  QString label = tr("Theme Chooser Menu");
531 
532  m_popupMenu =
533  new MythDialogBox(label, popupStack, "themechoosermenupopup");
534 
535  connect(m_popupMenu, SIGNAL(Closed(QString, int)), SLOT(popupClosed(QString, int)));
536 
537  if (m_popupMenu->Create())
538  popupStack->AddScreen(m_popupMenu);
539  else
540  {
541  delete m_popupMenu;
542  m_popupMenu = NULL;
543  return;
544  }
545 
546  m_popupMenu->SetReturnEvent(this, "popupmenu");
547 
549  {
551  m_popupMenu->AddButton(tr("Hide Fullscreen Preview"),
552  SLOT(toggleFullscreenPreview()));
553  else
554  m_popupMenu->AddButton(tr("Show Fullscreen Preview"),
555  SLOT(toggleFullscreenPreview()));
556  }
557 
558  m_popupMenu->AddButton(tr("Refresh Downloadable Themes"),
559  SLOT(refreshDownloadableThemes()));
560 
562  if (current)
563  {
564  ThemeInfo *info = current->GetData().value<ThemeInfo *>();
565 
566  if (info)
567  {
568  m_popupMenu->AddButton(tr("Select Theme"),
569  SLOT(saveAndReload()));
570 
571  if (info->GetPreviewPath().startsWith(m_userThemeDir))
572  m_popupMenu->AddButton(tr("Delete Theme"),
573  SLOT(removeTheme()));
574  }
575  }
576 
577  if (gCoreContext->GetNumSetting("ThemeUpdateNofications", 1))
578  m_popupMenu->AddButton(tr("Disable Theme Update Notifications"),
580  else
581  m_popupMenu->AddButton(tr("Enable Theme Update Notifications"),
583 }
584 
585 void ThemeChooser::popupClosed(QString which, int result)
586 {
587  (void)which;
588  (void)result;
589 
590  m_popupMenu = NULL;
591 }
592 
593 bool ThemeChooser::keyPressEvent(QKeyEvent *event)
594 {
595  if (GetFocusWidget()->keyPressEvent(event))
596  return true;
597 
598  bool handled = false;
599  QStringList actions;
600  handled = GetMythMainWindow()->TranslateKeyPress("Theme Chooser", event, actions);
601 
602  for (int i = 0; i < actions.size() && !handled; ++i)
603  {
604  QString action = actions[i];
605  handled = true;
606 
607  if (action == "MENU")
608  showPopupMenu();
609  else if (action == "DELETE")
610  removeTheme();
611  else if ((action == "ESCAPE") &&
613  {
615  }
616  else
617  handled = false;
618  }
619 
620  if (!handled && MythScreenType::keyPressEvent(event))
621  handled = true;
622 
623  return handled;
624 }
625 
627 {
629  {
631  {
634 
635  if (m_fullScreenName)
637 
639  m_fullPreviewShowing = false;
640  }
641  else
642  {
644  ThemeInfo *info = item->GetData().value<ThemeInfo*>();
645  if (info)
646  {
648  {
651  }
652 
653  if (m_fullScreenName)
654  m_fullScreenName->SetText(info->GetName());
655 
656  m_fullPreviewStateType->DisplayState("fullscreen");
657  m_fullPreviewShowing = true;
658  }
659  }
660  }
661 }
662 
664 {
665  if (gCoreContext->GetNumSetting("ThemeUpdateNofications", 1))
666  gCoreContext->SaveSettingOnHost("ThemeUpdateNofications", "0", "");
667  else
668  gCoreContext->SaveSettingOnHost("ThemeUpdateNofications", "1", "");
669 }
670 
672 {
673  LOG(VB_GUI, LOG_INFO, LOC + "Forcing remote theme list refresh");
675  gCoreContext->SaveSetting("ThemeInfoDownloadFailures", 0);
677 }
678 
680 {
682  if (current)
683  saveAndReload(current);
684 }
685 
687 {
688  ThemeInfo *info = item->GetData().value<ThemeInfo *>();
689 
690  if (!info)
691  return;
692 
693  if (!info->GetDownloadURL().isEmpty())
694  {
695  QString testFile = m_userThemeDir + "/.test";
696  QFile test(testFile);
697  if (test.open(QIODevice::WriteOnly))
698  test.remove();
699  else
700  {
701  ShowOkPopup(tr("Unable to install theme, %1 themes directory is "
702  "not writable.").arg(m_userThemeDir));
703  return;
704  }
705 
706  QString downloadURL = info->GetDownloadURL();
707  QFileInfo qfile(downloadURL);
708  QString baseName = qfile.fileName();
709 
710  if (!gCoreContext->GetSetting("ThemeDownloadURL").isEmpty())
711  {
712  QStringList tokens =
713  gCoreContext->GetSetting("ThemeDownloadURL")
714  .split(";", QString::SkipEmptyParts);
715  QString origURL = downloadURL;
716  downloadURL.replace(tokens[0], tokens[1]);
717  LOG(VB_FILE, LOG_WARNING, LOC +
718  QString("Theme download URL overridden from %1 to %2.")
719  .arg(origURL).arg(downloadURL));
720  }
721 
722  OpenBusyPopup(tr("Downloading %1 Theme").arg(info->GetName()));
723  m_downloadTheme = info;
724 #if 0
725  m_downloadFile = RemoteDownloadFile(downloadURL,
726  "Temp", baseName);
728 #else
729  QString localFile = GetConfDir() + "/tmp/" + baseName;
730  GetMythDownloadManager()->queueDownload(downloadURL, localFile, this);
731  m_downloadFile = localFile;
733 #endif
734  }
735  else
736  {
737  gCoreContext->SaveSetting("Theme", info->GetDirectoryName());
738  GetMythMainWindow()->JumpTo("Reload Theme");
739  }
740 }
741 
743 {
744  ThemeInfo *info = item->GetData().value<ThemeInfo*>();
745 
746  if (!info)
747  return;
748 
749  QFileInfo preview(info->GetPreviewPath());
750  InfoMap infomap;
751  info->ToMap(infomap);
752  SetTextFromMap(infomap);
753  if (m_preview)
754  {
755  if (preview.exists())
756  {
758  m_preview->Load();
759  }
760  else
761  m_preview->Reset();
762  }
764  {
766  {
767  if (preview.exists())
768  {
771  }
772  else
774  }
775 
776  if (m_fullScreenName)
777  m_fullScreenName->SetText(info->GetName());
778  }
779 
780  MythUIStateType *themeLocation =
781  dynamic_cast<MythUIStateType*>(GetChild("themelocation"));
782  if (themeLocation)
783  {
784  if (info->GetDownloadURL().isEmpty())
785  themeLocation->DisplayState("local");
786  else
787  themeLocation->DisplayState("remote");
788  }
789 
790  MythUIStateType *aspectState =
791  dynamic_cast<MythUIStateType*>(GetChild("aspectstate"));
792  if (aspectState)
793  aspectState->DisplayState(info->GetAspect());
794 }
795 
796 void ThemeChooser::updateProgressBar(int bytesReceived,
797  int bytesTotal)
798 {
799  MythUIProgressBar *progressBar =
800  dynamic_cast<MythUIProgressBar *>(GetChild("downloadprogressbar"));
801 
802  if (!progressBar)
803  return;
804 
805  progressBar->SetUsed(bytesReceived);
806  progressBar->SetTotal(bytesTotal);
807 }
808 
810 {
811  if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage)
812  {
813  MythEvent *me = (MythEvent *)e;
814  QStringList tokens = me->Message().split(" ", QString::SkipEmptyParts);
815 
816  if (tokens.isEmpty())
817  return;
818 
819  if (tokens[0] == "DOWNLOAD_FILE")
820  {
821  QStringList args = me->ExtraDataList();
822  if ((m_downloadState == dsIdle) ||
823  (tokens.size() != 2) ||
824  (!m_downloadTheme) ||
825  (args[1] != m_downloadFile))
826  return;
827 
828  if (tokens[1] == "UPDATE")
829  {
830  updateProgressBar(args[2].toInt(), args[3].toInt());
831  }
832  else if (tokens[1] == "FINISHED")
833  {
834  bool remoteFileIsLocal = false;
835  int fileSize = args[2].toInt();
836  int errorCode = args[4].toInt();
837 
838  CloseBusyPopup();
839 
840  QFileInfo file(m_downloadFile);
842  (m_downloadFile.startsWith("myth://")))
843  {
844  // The backend download is finished so start the
845  // frontend download
846  if ((errorCode == 0) &&
847  (fileSize > 0))
848  {
850  QString localFile = GetConfDir() + "/tmp/" +
851  file.fileName();
852  file.setFile(localFile);
853 
854  if (file.exists())
855  {
856  remoteFileIsLocal = true;
857  m_downloadFile = localFile;
858  }
859  else
860  {
862  m_downloadFile, localFile, this);
863  OpenBusyPopup(tr("Copying %1 Theme Package")
864  .arg(m_downloadTheme->GetName()));
865  m_downloadFile = localFile;
866  return;
867  }
868  }
869  else
870  {
872  ShowOkPopup(tr("ERROR downloading theme package on master backend."));
873  }
874  }
875 
877  (file.exists()))
878  {
879  // The frontend download is finished
880  if ((errorCode == 0) &&
881  (fileSize > 0))
882  {
884  ThemeExtractThread *extractThread =
888  extractThread, "ThemeExtract");
889 
890  if (!remoteFileIsLocal)
891  RemoteFile::DeleteFile(args[0]);
892 
893  OpenBusyPopup(tr("Installing %1 Theme")
894  .arg(m_downloadTheme->GetName()));
895  }
896  else
897  {
899  ShowOkPopup(tr("ERROR downloading theme package from master backend."));
900  }
901  }
902  }
903  }
904  else if ((me->Message() == "THEME_INSTALLED") &&
905  (m_downloadTheme) &&
907  {
909  CloseBusyPopup();
910  QStringList args = me->ExtraDataList();
911  QFile::remove(args[0]);
912 
913  QString event = QString("THEME_INSTALLED PATH %1")
914  .arg(m_userThemeDir +
917 
919 
920  // Send a message to ourself so we trigger a reload our next chance
921  MythEvent *me = new MythEvent("THEME_RELOAD");
922  qApp->postEvent(this, me);
923  }
924  else if ((me->Message() == "THEME_RELOAD") &&
925  (m_downloadState == dsIdle))
926  {
927  GetMythMainWindow()->JumpTo("Reload Theme");
928  }
929  }
930 }
931 
933 {
935  if (!current)
936  {
937  ShowOkPopup(tr("Error, no theme selected."));
938  return;
939  }
940 
941  ThemeInfo *info = current->GetData().value<ThemeInfo *>();
942  if (!info)
943  {
944  ShowOkPopup(tr("Error, unable to find current theme."));
945  return;
946  }
947 
948  if (!info->GetPreviewPath().startsWith(m_userThemeDir))
949  {
950  ShowOkPopup(tr("%1 is not a user-installed theme and can not "
951  "be deleted.").arg(info->GetName()));
952  return;
953  }
954 
956 
958 }
959 
961 {
962  if ((!dirname.startsWith(m_userThemeDir)) &&
963  (!dirname.startsWith(GetMythUI()->GetThemeCacheDir())))
964  return true;
965 
966  QDir dir(dirname);
967 
968  if (!dir.exists())
969  return true;
970 
971  dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
972  QFileInfoList list = dir.entryInfoList();
973  QFileInfoList::const_iterator it = list.begin();
974  const QFileInfo *fi;
975 
976  while (it != list.end())
977  {
978  fi = &(*it++);
979  if (fi->isFile() && !fi->isSymLink())
980  {
981  if (!QFile::remove(fi->absoluteFilePath()))
982  return false;
983  }
984  else if (fi->isDir() && !fi->isSymLink())
985  {
986  if (!removeThemeDir(fi->absoluteFilePath()))
987  return false;
988  }
989  }
990 
991  return dir.rmdir(dirname);
992 }
993 
995 
997  m_updateTimer(new QTimer(this))
998 {
999  QString version = MYTH_SOURCE_PATH;
1000 
1001  if (!version.isEmpty() && !version.startsWith("fixes/"))
1002  // Treat devel branches as master
1003  m_mythVersions << "trunk";
1004  else
1005  {
1006  version = MYTH_BINARY_VERSION; // Example: 0.25.20101017-1
1007  version.replace(QRegExp("\\.[0-9]{8,}.*"), "");
1008 
1009  // If a version of the theme for this tag exists, use it...
1010  QRegExp subexp("v[0-9]+.[0-9]+.([0-9]+)-*");
1011  int pos = subexp.indexIn(MYTH_SOURCE_VERSION);
1012  if (pos > -1)
1013  {
1014  QString subversion;
1015  int idx = subexp.cap(1).toInt();
1016  for ( ; idx > 0; --idx)
1017  m_mythVersions << version + "." + QString::number(idx);
1018  }
1020  }
1021 
1024  "remotethemes/themes.zip",
1025  "Temp");
1026 
1027  gCoreContext->SaveSetting("ThemeUpdateStatus", "");
1028 
1029  connect(m_updateTimer, SIGNAL(timeout()), SLOT(checkForUpdate()));
1030  m_updateTimer->start(60 * 60 * 1000); // Run once an hour
1031 
1032  // Run once 15 seconds from now
1033  QTimer::singleShot(15 * 1000, this, SLOT(checkForUpdate()));
1034 }
1035 
1037 {
1038  if (m_updateTimer)
1039  {
1040  m_updateTimer->stop();
1041  delete m_updateTimer;
1042  m_updateTimer = NULL;
1043  }
1044 }
1045 
1047 {
1048  if (GetMythUI()->GetCurrentLocation(false, true) != "mainmenu")
1049  return;
1050 
1051  ThemeInfo *localTheme = NULL;
1052  int locMaj = 0;
1053  int locMin = 0;
1054 
1056  {
1057  QStringList::iterator Iversion;
1058 
1059  for (Iversion = m_mythVersions.begin();
1060  Iversion != m_mythVersions.end(); ++Iversion)
1061  {
1062 
1063  QString remoteThemeDir =
1066  QString("remotethemes/%1/%2")
1067  .arg(*Iversion)
1068  .arg(GetMythUI()->GetThemeName()),
1069  "Temp");
1070 
1071  QString infoXML = remoteThemeDir;
1072  infoXML.append("/themeinfo.xml");
1073 
1074  LOG(VB_GUI, LOG_INFO, QString("ThemeUpdateChecker Loading '%1'")
1075  .arg(infoXML));
1076 
1077  if (RemoteFile::Exists(infoXML))
1078  {
1079  ThemeInfo *remoteTheme = new ThemeInfo(remoteThemeDir);
1080  if (!remoteTheme || remoteTheme->GetType() & THEME_UNKN)
1081  {
1082  LOG(VB_GENERAL, LOG_ERR,
1083  QString("ThemeUpdateChecker::checkForUpdate(): "
1084  "Unable to create ThemeInfo for %1")
1085  .arg(infoXML));
1086  delete remoteTheme;
1087  remoteTheme = NULL;
1088  return;
1089  }
1090 
1091  if (!localTheme)
1092  {
1093  localTheme = new ThemeInfo(GetMythUI()->GetThemeDir());
1094  if (!localTheme || localTheme->GetType() & THEME_UNKN)
1095  {
1096  LOG(VB_GENERAL, LOG_ERR,
1097  "ThemeUpdateChecker::checkForUpdate(): "
1098  "Unable to create ThemeInfo for current theme");
1099  delete localTheme;
1100  localTheme = NULL;
1101  return;
1102  }
1103  locMaj = localTheme->GetMajorVersion();
1104  locMin = localTheme->GetMinorVersion();
1105  }
1106 
1107  int rmtMaj = remoteTheme->GetMajorVersion();
1108  int rmtMin = remoteTheme->GetMinorVersion();
1109 
1110  delete remoteTheme;
1111  remoteTheme = NULL;
1112 
1113  if ((rmtMaj > locMaj) ||
1114  ((rmtMaj == locMaj) &&
1115  (rmtMin > locMin)))
1116  {
1118  QString("%1-%2.%3").arg(GetMythUI()->GetThemeName())
1119  .arg(rmtMaj).arg(rmtMin);
1120 
1121  QString status = gCoreContext->GetSetting
1122  ("ThemeUpdateStatus");
1123  QString currentLocation = GetMythUI()->GetCurrentLocation
1124  (false, true);
1125 
1126  if ((!status.startsWith(m_lastKnownThemeVersion)) &&
1127  (currentLocation == "mainmenu"))
1128  {
1129  m_currentVersion = QString("%1.%2")
1130  .arg(locMaj).arg(locMin);
1131  m_newVersion = QString("%1.%2").arg(rmtMaj).arg(rmtMin);
1132 
1133  gCoreContext->SaveSetting("ThemeUpdateStatus",
1135  + " notified");
1136 
1137  QString message = tr("Version %1 of the %2 theme is now "
1138  "available in the Theme Chooser. "
1139  "The currently installed version "
1140  "is %3.")
1141  .arg(m_newVersion)
1142  .arg(GetMythUI()->GetThemeName())
1143  .arg(m_currentVersion);
1144 
1145  ShowOkPopup(message);
1146  break;
1147  }
1148  }
1149  }
1150  }
1151  }
1152 
1153  delete localTheme;
1154 }
1155 
1156 /* 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:27
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:147
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:143
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
return strftime a b e
Definition: yrnoxml.pl:329
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:148
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="")