MythTV  master
recordingselector.cpp
Go to the documentation of this file.
1 
2 // c++
3 #include <cstdlib>
4 #include <unistd.h>
5 
6 // qt
7 #include <QDir>
8 #include <QKeyEvent>
9 #include <QTimer>
10 #include <QApplication>
11 
12 // mythtv
13 #include <mythcontext.h>
14 #include <mythdb.h>
15 #include <mthread.h>
16 #include <programinfo.h>
17 #include <remoteutil.h>
18 #include <mythtimer.h>
19 #include <mythuitext.h>
20 #include <mythuibutton.h>
21 #include <mythuiimage.h>
22 #include <mythuibuttonlist.h>
23 #include <mythmainwindow.h>
24 #include <mythprogressdialog.h>
25 #include <mythdialogbox.h>
26 #include <mythlogging.h>
27 #include <mythdate.h>
28 
29 // mytharchive
30 #include "recordingselector.h"
31 #include "archiveutil.h"
32 
34 {
35  public:
37  MThread("GetRecordingList"), m_parent(parent)
38  {
39  start();
40  }
41 
42  void run(void) override // MThread
43  {
44  RunProlog();
46  RunEpilog();
47  }
48 
50 };
51 
53 {
54  delete m_recordingList;
55  while (!m_selectedList.isEmpty())
56  delete m_selectedList.takeFirst();
57 }
58 
60 {
61  // Load the theme for this screen
62  bool foundtheme = LoadWindowFromXML("mytharchive-ui.xml", "recording_selector", this);
63  if (!foundtheme)
64  return false;
65 
66  bool err = false;
67  UIUtilE::Assign(this, m_okButton, "ok_button", &err);
68  UIUtilE::Assign(this, m_cancelButton, "cancel_button", &err);
69  UIUtilE::Assign(this, m_categorySelector, "category_selector", &err);
70  UIUtilE::Assign(this, m_recordingButtonList, "recordinglist", &err);
71 
72  UIUtilW::Assign(this, m_titleText, "progtitle", &err);
73  UIUtilW::Assign(this, m_datetimeText, "progdatetime", &err);
74  UIUtilW::Assign(this, m_descriptionText, "progdescription", &err);
75  UIUtilW::Assign(this, m_filesizeText, "filesize", &err);
76  UIUtilW::Assign(this, m_previewImage, "preview_image", &err);
77  UIUtilW::Assign(this, m_cutlistImage, "cutlist_image", &err);
78 
79  if (err)
80  {
81  LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'recording_selector'");
82  return false;
83  }
84 
85  connect(m_okButton, SIGNAL(Clicked()), this, SLOT(OKPressed()));
86  connect(m_cancelButton, SIGNAL(Clicked()), this, SLOT(cancelPressed()));
87 
88  new MythUIButtonListItem(m_categorySelector, tr("All Recordings"));
89  connect(m_categorySelector, SIGNAL(itemSelected(MythUIButtonListItem *)),
90  this, SLOT(setCategory(MythUIButtonListItem *)));
91 
92  connect(m_recordingButtonList, SIGNAL(itemSelected(MythUIButtonListItem *)),
93  this, SLOT(titleChanged(MythUIButtonListItem *)));
94  connect(m_recordingButtonList, SIGNAL(itemClicked(MythUIButtonListItem *)),
95  this, SLOT(toggleSelected(MythUIButtonListItem *)));
96 
97  if (m_cutlistImage)
99 
100  BuildFocusList();
101 
103 
104  return true;
105 }
106 
108 {
109  QString message = tr("Retrieving Recording List.\nPlease Wait...");
110 
111  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
112 
113  auto *busyPopup = new
114  MythUIBusyDialog(message, popupStack, "recordingselectorbusydialog");
115 
116  if (busyPopup->Create())
117  popupStack->AddScreen(busyPopup, false);
118  else
119  {
120  delete busyPopup;
121  busyPopup = nullptr;
122  }
123 
124  auto *thread = new GetRecordingListThread(this);
125  while (thread->isRunning())
126  {
127  qApp->processEvents();
128  usleep(2000);
129  }
130 
131  if (!m_recordingList || m_recordingList->empty())
132  {
133  ShowOkPopup(tr("Either you don't have any recordings or "
134  "no recordings are available locally!"));
135  if (busyPopup)
136  busyPopup->Close();
137 
138  Close();
139  return;
140  }
141 
145 
146  if (busyPopup)
147  busyPopup->Close();
148 }
149 
150 bool RecordingSelector::keyPressEvent(QKeyEvent *event)
151 {
152  if (GetFocusWidget()->keyPressEvent(event))
153  return true;
154 
155  QStringList actions;
156  bool handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions);
157 
158  for (int i = 0; i < actions.size() && !handled; i++)
159  {
160  QString action = actions[i];
161  handled = true;
162 
163  if (action == "MENU")
164  {
165  ShowMenu();
166  }
167  else
168  handled = false;
169  }
170 
171  if (!handled && MythScreenType::keyPressEvent(event))
172  handled = true;
173 
174  return handled;
175 }
176 
178 {
179  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
180 
181  auto *menuPopup = new MythDialogBox(tr("Menu"), popupStack, "actionmenu");
182 
183  if (menuPopup->Create())
184  popupStack->AddScreen(menuPopup);
185 
186  menuPopup->SetReturnEvent(this, "action");
187 
188  menuPopup->AddButton(tr("Clear All"), SLOT(clearAll()));
189  menuPopup->AddButton(tr("Select All"), SLOT(selectAll()));
190 }
191 
193 {
194  while (!m_selectedList.isEmpty())
195  m_selectedList.takeFirst();
196  m_selectedList.clear();
197 
198  for (auto p : *m_recordingList)
199  m_selectedList.append(p);
200 
202 }
203 
205 {
206  while (!m_selectedList.isEmpty())
207  m_selectedList.takeFirst();
208  m_selectedList.clear();
209 
211 }
212 
214 {
216  {
217  int index = m_selectedList.indexOf(item->GetData().value<ProgramInfo *>());
218  if (index != -1)
219  m_selectedList.takeAt(index);
221  }
222  else
223  {
224  int index = m_selectedList.indexOf(item->GetData().value<ProgramInfo *>());
225  if (index == -1)
226  m_selectedList.append(item->GetData().value<ProgramInfo *>());
227 
229  }
230 }
231 
233 {
234  auto *p = item->GetData().value<ProgramInfo *>();
235 
236  if (!p)
237  return;
238 
239  if (m_titleText)
240  m_titleText->SetText(p->GetTitle());
241 
242  if (m_datetimeText)
243  m_datetimeText->SetText(p->GetScheduledStartTime().toLocalTime()
244  .toString("dd MMM yy (hh:mm)"));
245 
246  if (m_descriptionText)
247  {
249  ((!p->GetSubtitle().isEmpty()) ? p->GetSubtitle() + "\n" : "") +
250  p->GetDescription());
251  }
252 
253  if (m_filesizeText)
254  {
255  m_filesizeText->SetText(formatSize(p->GetFilesize() / 1024));
256  }
257 
258  if (m_cutlistImage)
259  {
260  if (p->HasCutlist())
261  m_cutlistImage->Show();
262  else
263  m_cutlistImage->Hide();
264  }
265 
266  if (m_previewImage)
267  {
268  // try to locate a preview image
269  if (QFile::exists(p->GetPathname() + ".png"))
270  {
271  m_previewImage->SetFilename(p->GetPathname() + ".png");
272  m_previewImage->Load();
273  }
274  else
275  {
276  m_previewImage->SetFilename("blank.png");
277  m_previewImage->Load();
278  }
279  }
280 }
281 
283 {
284  // loop though selected recordings and add them to the list
285  // remove any items that have been removed from the list
286  QList<ArchiveItem *> tempAList;
287  foreach (auto a, *m_archiveList)
288  {
289  bool found = false;
290 
291  foreach (auto p, m_selectedList)
292  {
293  if (a->type != "Recording" || a->filename == p->GetPlaybackURL(false, true))
294  {
295  found = true;
296  break;
297  }
298  }
299 
300  if (!found)
301  tempAList.append(a);
302  }
303 
304  foreach (auto x, tempAList)
305  m_archiveList->removeAll(x);
306 
307  // remove any items that are already in the list
308  QList<ProgramInfo *> tempSList;
309  foreach (auto p, m_selectedList)
310  {
311  foreach (auto a, *m_archiveList)
312  {
313  if (a->filename == p->GetPlaybackURL(false, true))
314  {
315  tempSList.append(p);
316  break;
317  }
318  }
319  }
320 
321  foreach (auto x, tempSList)
322  m_selectedList.removeAll(x);
323 
324  // add all that are left
325  foreach (auto p, m_selectedList)
326  {
327  auto *a = new ArchiveItem;
328  a->type = "Recording";
329  a->title = p->GetTitle();
330  a->subtitle = p->GetSubtitle();
331  a->description = p->GetDescription();
332  a->startDate = p->GetScheduledStartTime()
333  .toLocalTime().toString("dd MMM yy");
334  a->startTime = p->GetScheduledStartTime()
335  .toLocalTime().toString("(hh:mm)");
336  a->size = p->GetFilesize();
337  a->filename = p->GetPlaybackURL(false, true);
338  a->hasCutlist = p->HasCutlist();
339  a->useCutlist = false;
340  a->duration = 0;
341  a->cutDuration = 0;
342  a->videoWidth = 0;
343  a->videoHeight = 0;
344  a->fileCodec = "";
345  a->videoCodec = "";
346  a->encoderProfile = nullptr;
347  a->editedDetails = false;
348  m_archiveList->append(a);
349  }
350 
351  emit haveResult(true);
352  Close();
353 }
354 
356 {
357  emit haveResult(false);
358  Close();
359 }
360 
362 {
363  if (!m_recordingList || m_recordingList->empty())
364  return;
365 
367 
368  if (m_categorySelector)
369  {
370  for (auto p : *m_recordingList)
371  {
372  if (p->GetTitle() == m_categorySelector->GetValue() ||
373  m_categorySelector->GetValue() == tr("All Recordings"))
374  {
375  auto* item = new MythUIButtonListItem(
377  p->GetTitle() + " ~ " +
378  p->GetScheduledStartTime().toLocalTime()
379  .toString("dd MMM yy (hh:mm)"));
380  item->setCheckable(true);
381  if (m_selectedList.indexOf(p) != -1)
382  {
383  item->setChecked(MythUIButtonListItem::FullChecked);
384  }
385  else
386  {
387  item->setChecked(MythUIButtonListItem::NotChecked);
388  }
389 
390  QString title = p->GetTitle();
391  QString subtitle = p->GetSubtitle();
392 
393  QDateTime recstartts = p->GetScheduledStartTime();
394  QDateTime recendts = p->GetScheduledEndTime();
395 
396  QString timedate = QString("%1 - %2")
398  .arg(MythDate::toString(recendts, MythDate::kTime));
399 
400  uint season = p->GetSeason();
401  uint episode = p->GetEpisode();
402  QString seasone;
403  QString seasonx;
404 
405  if (season && episode)
406  {
407  seasone = QString("s%1e%2")
408  .arg(format_season_and_episode(season, 2))
409  .arg(format_season_and_episode(episode, 2));
410  seasonx = QString("%1x%2")
411  .arg(format_season_and_episode(season, 1))
412  .arg(format_season_and_episode(episode, 2));
413  }
414 
415  item->SetText(title, "title");
416  item->SetText(subtitle, "subtitle");
417  if (subtitle.isEmpty())
418  item->SetText(title, "titlesubtitle");
419  else
420  item->SetText(title + " - \"" + subtitle + '"',
421  "titlesubtitle");
422 
423  item->SetText(timedate, "timedate");
424  item->SetText(p->GetDescription(), "description");
425  item->SetText(formatSize(p->GetFilesize() / 1024),
426  "filesize_str");
427 
428  item->SetText(QString::number(season), "season");
429  item->SetText(QString::number(episode), "episode");
430  item->SetText(seasonx, "00x00");
431  item->SetText(seasone, "s00e00");
432 
433  item->DisplayState(p->HasCutlist() ? "yes" : "no", "cutlist");
434 
435  item->SetData(QVariant::fromValue(p));
436  }
437  qApp->processEvents();
438  }
439  }
440 
443 }
444 
446 {
448  m_categories.clear();
449 
450  if (m_recordingList && !m_recordingList->empty())
451  {
452  auto i = m_recordingList->begin();
453  for ( ; i != m_recordingList->end(); ++i)
454  {
455  ProgramInfo *p = *i;
456  // ignore live tv and deleted recordings
457  if (p->GetRecordingGroup() == "LiveTV" ||
458  p->GetRecordingGroup() == "Deleted")
459  {
460  i = m_recordingList->erase(i);
461  --i;
462  continue;
463  }
464 
465  if (m_categories.indexOf(p->GetTitle()) == -1)
466  m_categories.append(p->GetTitle());
467  }
468  }
469 }
470 
472 {
473  // sort and add categories to selector
474  m_categories.sort();
475 
476  if (m_categorySelector)
477  {
478  new MythUIButtonListItem(m_categorySelector, tr("All Recordings"));
480 
481  for (int x = 0; x < m_categories.count(); x++)
482  {
484  }
485  }
486 }
487 
489 {
490  (void)item;
492 }
493 
495 {
496  if (!m_recordingList)
497  return;
498 
499  m_selectedList.clear();
500 
501  foreach (auto a, *m_archiveList)
502  {
503  for (auto p : *m_recordingList)
504  {
505  if (p->GetPlaybackURL(false, true) == a->filename)
506  {
507  if (m_selectedList.indexOf(p) == -1)
508  m_selectedList.append(p);
509  break;
510  }
511 
512  qApp->processEvents();
513  }
514  }
515 }
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:215
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
void Show(void)
void toggleSelected(MythUIButtonListItem *item)
RecordingSelector * m_parent
bool TranslateKeyPress(const QString &context, QKeyEvent *e, QStringList &actions, bool allowJumps=true)
Get a list of actions for a keypress in the given context.
MythConfirmationDialog * ShowOkPopup(const QString &message, QObject *parent, const char *slot, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
void ShowMenu(void) override
void haveResult(bool ok)
Basic menu dialog, message and a list of options.
MPUBLIC QString format_season_and_episode(int seasEp, int digits)
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:135
MythScreenStack * GetStack(const QString &stackname)
bool Create() override
bool Load(bool allowLoadInBackground=true, bool forceStat=false)
Load the image(s), wraps ImageLoader::LoadImage()
void BuildFocusList(void)
MythUIImage * m_previewImage
MythUIButtonList * m_categorySelector
void Hide(void)
virtual void Close()
void setCheckable(bool flag)
MythUIText * m_titleText
Default local time.
Definition: mythdate.h:20
bool keyPressEvent(QKeyEvent *e) override
Key event handler.
Holds information on recordings and videos.
Definition: programinfo.h:67
void titleChanged(MythUIButtonListItem *item)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
MythUIText * m_filesizeText
void updateCategorySelector(void)
MythUIButtonList * m_recordingButtonList
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
friend class GetRecordingListThread
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27
void updateRecordingList(void)
std::vector< ProgramInfo * > * m_recordingList
unsigned int uint
Definition: compat.h:140
void Init(void) override
Used after calling Load() to assign data to widgets and other UI initilisation which is prohibited in...
MythUIButton * m_okButton
CheckState state() const
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:101
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
QList< ProgramInfo * > m_selectedList
~RecordingSelector(void) override
MythUIType * GetFocusWidget(void) const
QString type
Definition: archiveutil.h:54
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
MythMainWindow * GetMythMainWindow(void)
virtual QString GetValue() const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
static QString formatSize(int64_t sizeKB, int prec)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
MythUIText * m_descriptionText
void SetFilename(const QString &filename)
Must be followed by a call to Load() to load the image.
MythUIText * m_datetimeText
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:202
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:294
void SetItemCurrent(MythUIButtonListItem *item)
bool SetFocusWidget(MythUIType *widget=nullptr)
GetRecordingListThread(RecordingSelector *parent)
QList< ArchiveItem * > * m_archiveList
MythUIButtonListItem * GetItemFirst() const
MythUIButton * m_cancelButton
vector< ProgramInfo * > * RemoteGetRecordedList(int sort)
Definition: remoteutil.cpp:16
MythUIImage * m_cutlistImage
void setChecked(CheckState state)
void setCategory(MythUIButtonListItem *item)
QStringList m_categories
MythUIButtonListItem * GetItemCurrent() const
Default local time.
Definition: mythdate.h:19