MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
guidegrid.cpp
Go to the documentation of this file.
1 
2 #include "guidegrid.h"
3 
4 // c/c++
5 #include <math.h>
6 #include <unistd.h>
7 #include <iostream>
8 #include <algorithm>
9 using namespace std;
10 
11 //qt
12 #include <QCoreApplication>
13 #include <QKeyEvent>
14 #include <QDateTime>
15 
16 // myth
17 #include "mythcorecontext.h"
18 #include "mythdbcon.h"
19 #include "mythlogging.h"
20 #include "channelinfo.h"
21 #include "programinfo.h"
22 #include "recordingrule.h"
23 #include "tv_play.h"
24 #include "tv_rec.h"
25 #include "customedit.h"
26 #include "mythdate.h"
27 #include "remoteutil.h"
28 #include "channelutil.h"
29 #include "cardutil.h"
30 #include "mythuibuttonlist.h"
31 #include "mythuiguidegrid.h"
32 #include "mythdialogbox.h"
33 #include "progfind.h"
34 
35 QWaitCondition epgIsVisibleCond;
36 
37 #define LOC QString("GuideGrid: ")
38 #define LOC_ERR QString("GuideGrid, Error: ")
39 #define LOC_WARN QString("GuideGrid, Warning: ")
40 
41 const QString kUnknownTitle = QObject::tr("Unknown");
42 const QString kUnknownCategory = QObject::tr("Unknown");
43 
45  JumpToChannelListener *parent, const QString &start_entry,
46  int start_chan_idx, int cur_chan_idx, uint rows_disp) :
47  m_listener(parent),
48  m_entry(start_entry),
49  m_previous_start_channel_index(start_chan_idx),
50  m_previous_current_channel_index(cur_chan_idx),
51  m_rows_displayed(rows_disp),
52  m_timer(new QTimer(this))
53 {
54  if (parent && m_timer)
55  {
56  connect(m_timer, SIGNAL(timeout()), SLOT(deleteLater()));
57  m_timer->setSingleShot(true);
58  }
59  Update();
60 }
61 
62 
64 {
65  if (m_listener)
66  {
68  m_listener = NULL;
69  }
70 
71  if (m_timer)
72  {
73  m_timer->stop();
74  m_timer = NULL;
75  }
76 
77  QObject::deleteLater();
78 }
79 
80 
81 static bool has_action(QString action, const QStringList &actions)
82 {
83  QStringList::const_iterator it;
84  for (it = actions.begin(); it != actions.end(); ++it)
85  {
86  if (action == *it)
87  return true;
88  }
89  return false;
90 }
91 
92 bool JumpToChannel::ProcessEntry(const QStringList &actions, const QKeyEvent *e)
93 {
94  if (!m_listener)
95  return false;
96 
97  if (has_action("ESCAPE", actions))
98  {
101  deleteLater();
102  return true;
103  }
104 
105  if (has_action("DELETE", actions))
106  {
107  if (!m_entry.isEmpty())
108  m_entry = m_entry.left(m_entry.length()-1);
109  Update();
110  return true;
111  }
112 
113  if (has_action(ACTION_SELECT, actions))
114  {
115  if (Update())
116  deleteLater();
117  return true;
118  }
119 
120  QString txt = e->text();
121  bool isUInt;
122  txt.toUInt(&isUInt);
123  if (isUInt)
124  {
125  m_entry += txt;
126  Update();
127  return true;
128  }
129 
130  if (!m_entry.isEmpty() && (txt=="_" || txt=="-" || txt=="#" || txt=="."))
131  {
132  m_entry += txt;
133  Update();
134  return true;
135  }
136 
137  return false;
138 }
139 
141 {
142  if (!m_timer || !m_listener)
143  return false;
144 
145  m_timer->stop();
146 
147  // find the closest channel ...
148  int i = m_listener->FindChannel(0, m_entry, false);
149  if (i >= 0)
150  {
151  // setup the timeout timer for jump mode
153 
154  // rows_displayed to center
155  int start = i - m_rows_displayed/2;
156  int cur = m_rows_displayed/2;
157  m_listener->GoTo(start, cur);
158  return true;
159  }
160  else
161  { // prefix must be invalid.. reset entry..
162  deleteLater();
163  return false;
164  }
165 }
166 
167 void GuideGrid::RunProgramGuide(uint chanid, const QString &channum,
168  const QDateTime startTime,
169  TV *player, bool embedVideo,
170  bool allowFinder, int changrpid)
171 {
172  // which channel group should we default to
173  if (changrpid == -2)
174  changrpid = gCoreContext->GetNumSetting("ChannelGroupDefault", -1);
175 
176  // check there are some channels setup
178  0, true, "", (changrpid<0) ? 0 : changrpid);
179  if (channels.empty())
180  {
181  QString message;
182  if (changrpid == -1)
183  {
184  message = tr("You don't have any channels defined in the database."
185  "\n\t\t\tThe program guide will have nothing to show you.");
186  }
187  else
188  {
189  message = tr("Channel group '%1' doesn't have any channels defined."
190  "\n\t\t\tThe program guide will have nothing to show you.")
191  .arg(ChannelGroup::GetChannelGroupName(changrpid));
192  }
193 
194  LOG(VB_GENERAL, LOG_WARNING, LOC + message);
195 
196  if (!player)
197  ShowOkPopup(message);
198  else
199  {
200  if (player && allowFinder)
201  {
202  message = QString("EPG_EXITING");
203  qApp->postEvent(player, new MythEvent(message));
204  }
205  }
206 
207  return;
208  }
209 
211  GuideGrid *gg = new GuideGrid(mainStack,
212  chanid, channum, startTime,
213  player, embedVideo, allowFinder,
214  changrpid);
215 
216  if (gg->Create())
217  mainStack->AddScreen(gg, (player == NULL));
218  else
219  delete gg;
220 }
221 
223  uint chanid, QString channum, const QDateTime startTime,
224  TV *player, bool embedVideo,
225  bool allowFinder, int changrpid)
226  : ScheduleCommon(parent, "guidegrid"),
227  m_allowFinder(allowFinder),
228  m_player(player),
229  m_usingNullVideo(false), m_embedVideo(embedVideo),
230  m_previewVideoRefreshTimer(new QTimer(this)),
231  m_updateTimer(NULL),
232  m_jumpToChannelLock(QMutex::Recursive),
233  m_jumpToChannel(NULL)
234 {
235  connect(m_previewVideoRefreshTimer, SIGNAL(timeout()),
236  this, SLOT(refreshVideo()));
237 
238  m_channelCount = 5;
239  m_timeCount = 30;
241  m_changrpid = changrpid;
243 
244  m_sortReverse = gCoreContext->GetNumSetting("EPGSortReverse", 0);
245  m_selectRecThreshold = gCoreContext->GetNumSetting("SelChangeRecThreshold", 16);
246 
247  m_channelOrdering = gCoreContext->GetSetting("ChannelOrdering", "channum");
248 
249  for (uint i = 0; i < MAX_DISPLAY_CHANS; i++)
250  m_programs.push_back(NULL);
251 
252  for (int x = 0; x < MAX_DISPLAY_TIMES; ++x)
253  {
254  for (int y = 0; y < MAX_DISPLAY_CHANS; ++y)
255  m_programInfos[y][x] = NULL;
256  }
257 
259  if (startTime.isValid() &&
260  startTime > m_originalStartTime.addSecs(-8 * 3600))
261  m_originalStartTime = startTime;
262 
263  int secsoffset = -((m_originalStartTime.time().minute() % 30) * 60 +
264  m_originalStartTime.time().second());
265  m_currentStartTime = m_originalStartTime.addSecs(secsoffset);
266  m_startChanID = chanid;
268 }
269 
271 {
272  QString windowName = "programguide";
273 
274  if (m_embedVideo)
275  windowName = "programguide-video";
276 
277  if (!LoadWindowFromXML("schedule-ui.xml", windowName, this))
278  return false;
279 
280  bool err = false;
281  UIUtilE::Assign(this, m_timeList, "timelist", &err);
282  UIUtilE::Assign(this, m_channelList, "channellist", &err);
283  UIUtilE::Assign(this, m_guideGrid, "guidegrid", &err);
284  UIUtilW::Assign(this, m_dateText, "datetext");
285  UIUtilW::Assign(this, m_longdateText, "longdatetext");
286  UIUtilW::Assign(this, m_changroupname, "channelgroup");
287  UIUtilW::Assign(this, m_channelImage, "channelicon");
288  UIUtilW::Assign(this, m_jumpToText, "jumptotext");
289 
290  if (err)
291  {
292  LOG(VB_GENERAL, LOG_ERR,
293  QString("Cannot load screen '%1'").arg(windowName));
294  return false;
295  }
296 
297  BuildFocusList();
298 
299  MythUIImage *videoImage = dynamic_cast<MythUIImage *>(GetChild("video"));
300  if (videoImage && m_embedVideo)
301  m_videoRect = videoImage->GetArea();
302  else
303  m_videoRect = QRect(0,0,0,0);
304 
308 
309  m_currentEndTime = m_currentStartTime.addSecs(m_timeCount * 60 * 5);
310 
312  return true;
313 }
314 
315 void GuideGrid::Load(void)
316 {
319 
320  int maxchannel = max((int)GetChannelCount() - 1, 0);
322  m_channelCount = min(m_channelCount, maxchannel + 1);
323 
324  for (int y = 0; y < m_channelCount; ++y)
325  {
326  int chanNum = y + m_currentStartChannel;
327  if (chanNum >= (int) m_channelInfos.size())
328  chanNum -= (int) m_channelInfos.size();
329  if (chanNum >= (int) m_channelInfos.size())
330  continue;
331 
332  if (chanNum < 0)
333  chanNum = 0;
334 
335  delete m_programs[y];
337  }
338 }
339 
340 void GuideGrid::Init(void)
341 {
343  m_currentCol = 0;
344 
345  fillTimeInfos();
346 
347  updateChannels();
348 
349  fillProgramInfos(true);
350  updateInfo();
351 
352  m_updateTimer = new QTimer(this);
353  connect(m_updateTimer, SIGNAL(timeout()), SLOT(updateTimeout()) );
354  m_updateTimer->start(60 * 1000);
355 
356  updateDateText();
357 
358  QString changrpname = ChannelGroup::GetChannelGroupName(m_changrpid);
359 
360  if (m_changroupname)
361  m_changroupname->SetText(changrpname);
362 
363  gCoreContext->addListener(this);
364 }
365 
367 {
369 
370  while (!m_programs.empty())
371  {
372  if (m_programs.back())
373  delete m_programs.back();
374  m_programs.pop_back();
375  }
376 
377  m_channelInfos.clear();
378 
379  if (m_updateTimer)
380  {
381  m_updateTimer->disconnect(this);
382  m_updateTimer = NULL;
383  }
384 
386  {
387  m_previewVideoRefreshTimer->disconnect(this);
389  }
390 
391  gCoreContext->SaveSetting("EPGSortReverse", m_sortReverse ? "1" : "0");
392 
393  // if we have a player and we are returning to it we need
394  // to tell it to stop embedding and return to fullscreen
395  if (m_player && m_allowFinder)
396  {
397  QString message = QString("EPG_EXITING");
398  qApp->postEvent(m_player, new MythEvent(message));
399  }
400 
401  // maybe the user selected a different channel group,
402  // tell the player to update its channel list just in case
403  if (m_player)
405 
406  if (gCoreContext->GetNumSetting("ChannelGroupRememberLast", 0))
407  gCoreContext->SaveSetting("ChannelGroupDefault", m_changrpid);
408 }
409 
410 bool GuideGrid::keyPressEvent(QKeyEvent *event)
411 {
412  QStringList actions;
413  bool handled = false;
414  handled = GetMythMainWindow()->TranslateKeyPress("TV Frontend", event, actions);
415 
416  if (handled)
417  return true;
418 
419  if (actions.size())
420  {
421  QMutexLocker locker(&m_jumpToChannelLock);
422 
423  if (!m_jumpToChannel)
424  {
425  QString chanNum = actions[0];
426  bool isNum;
427  chanNum.toInt(&isNum);
428  if (isNum)
429  {
430  // see if we can find a matching channel before creating the JumpToChannel otherwise
431  // JumpToChannel will delete itself in the ctor leading to a segfault
432  int i = FindChannel(0, chanNum, false);
433  if (i >= 0)
434  {
435  m_jumpToChannel = new JumpToChannel(this, chanNum,
439  }
440 
441  handled = true;
442  }
443  }
444 
445  if (m_jumpToChannel && !handled)
446  handled = m_jumpToChannel->ProcessEntry(actions, event);
447  }
448 
449  for (int i = 0; i < actions.size() && !handled; ++i)
450  {
451  QString action = actions[i];
452  handled = true;
453  if (action == ACTION_UP)
454  {
455  if (m_verticalLayout)
456  cursorLeft();
457  else
458  cursorUp();
459  }
460  else if (action == ACTION_DOWN)
461  {
462  if (m_verticalLayout)
463  cursorRight();
464  else
465  cursorDown();
466  }
467  else if (action == ACTION_LEFT)
468  {
469  if (m_verticalLayout)
470  cursorUp();
471  else
472  cursorLeft();
473  }
474  else if (action == ACTION_RIGHT)
475  {
476  if (m_verticalLayout)
477  cursorDown();
478  else
479  cursorRight();
480  }
481  else if (action == "PAGEUP")
482  {
483  if (m_verticalLayout)
485  else
487  }
488  else if (action == "PAGEDOWN")
489  {
490  if (m_verticalLayout)
492  else
494  }
495  else if (action == ACTION_PAGELEFT)
496  {
497  if (m_verticalLayout)
499  else
501  }
502  else if (action == ACTION_PAGERIGHT)
503  {
504  if (m_verticalLayout)
506  else
508  }
509  else if (action == ACTION_DAYLEFT)
511  else if (action == ACTION_DAYRIGHT)
513  else if (action == "NEXTFAV")
515  else if (action == ACTION_FINDER)
516  showProgFinder();
517  else if (action == "MENU")
518  ShowMenu();
519  else if (action == "ESCAPE" || action == ACTION_GUIDE)
520  Close();
521  else if (action == ACTION_SELECT)
522  {
524  {
525  // See if this show is far enough into the future that it's
526  // probable that the user wanted to schedule it to record
527  // instead of changing the channel.
528  ProgramInfo *pginfo =
530  int secsTillStart =
531  (pginfo) ? MythDate::current().secsTo(
532  pginfo->GetScheduledStartTime()) : 0;
533  if (pginfo && (pginfo->GetTitle() != kUnknownTitle) &&
534  ((secsTillStart / 60) >= m_selectRecThreshold))
535  {
536  editRecSchedule();
537  }
538  else
539  {
540  enter();
541  }
542  }
543  else
544  editRecSchedule();
545  }
546  else if (action == "EDIT")
547  editSchedule();
548  else if (action == "CUSTOMEDIT")
549  customEdit();
550  else if (action == "DELETE")
551  deleteRule();
552  else if (action == "UPCOMING")
553  upcoming();
554  else if (action == "DETAILS" || action == "INFO")
555  details();
556  else if (action == ACTION_TOGGLERECORD)
557  quickRecord();
558  else if (action == ACTION_TOGGLEFAV)
559  {
560  if (m_changrpid == -1)
561  ChannelGroupMenu(0);
562  else
564  }
565  else if (action == "CHANUPDATE")
566  channelUpdate();
567  else if (action == ACTION_VOLUMEUP)
568  volumeUpdate(true);
569  else if (action == ACTION_VOLUMEDOWN)
570  volumeUpdate(false);
571  else if (action == "CYCLEAUDIOCHAN")
572  toggleMute(true);
573  else if (action == ACTION_MUTEAUDIO)
574  toggleMute();
575  else if (action == ACTION_TOGGLEPGORDER)
576  {
579  updateChannels();
580  }
581  else
582  handled = false;
583  }
584 
585  if (!handled && MythScreenType::keyPressEvent(event))
586  handled = true;
587 
588  return handled;
589 }
590 
592 {
593  QString label = tr("Guide Options");
594 
595  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
596  MythDialogBox *menuPopup = new MythDialogBox(label, popupStack,
597  "guideMenuPopup");
598 
599  if (menuPopup->Create())
600  {
601  menuPopup->SetReturnEvent(this, "guidemenu");
602 
604  menuPopup->AddButton(tr("Change to Channel"));
605 
606  menuPopup->AddButton(tr("Record This"));
607 
608  menuPopup->AddButton(tr("Recording Options"), NULL, true);
609 
610  menuPopup->AddButton(tr("Program Details"));
611 
612  menuPopup->AddButton(tr("Jump to Time"), NULL, true);
613 
614  menuPopup->AddButton(tr("Reverse Channel Order"));
615 
616  if (!m_changrplist.empty())
617  {
618  menuPopup->AddButton(tr("Choose Channel Group"));
619 
620  if (m_changrpid == -1)
621  menuPopup->AddButton(tr("Add To Channel Group"), NULL, true);
622  else
623  menuPopup->AddButton(tr("Remove from Channel Group"), NULL, true);
624  }
625 
626  popupStack->AddScreen(menuPopup);
627  }
628  else
629  {
630  delete menuPopup;
631  }
632 }
633 
635 {
636  QString label = tr("Recording Options");
637 
638  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
639  MythDialogBox *menuPopup = new MythDialogBox(label, popupStack,
640  "recMenuPopup");
641 
642  if (menuPopup->Create())
643  {
644  menuPopup->SetReturnEvent(this, "recmenu");
645 
647 
648  if (pginfo && pginfo->GetRecordingRuleID())
649  menuPopup->AddButton(tr("Edit Recording Status"));
650  menuPopup->AddButton(tr("Edit Schedule"));
651  menuPopup->AddButton(tr("Show Upcoming"));
652  menuPopup->AddButton(tr("Custom Edit"));
653 
654  if (pginfo && pginfo->GetRecordingRuleID())
655  menuPopup->AddButton(tr("Delete Rule"));
656 
657  popupStack->AddScreen(menuPopup);
658  }
659  else
660  {
661  delete menuPopup;
662  }
663 }
664 
666 {
667  sel = (sel >= 0) ? sel : m_channelInfoIdx[chan_idx];
668 
669  if (chan_idx >= GetChannelCount())
670  return NULL;
671 
672  if (sel >= (int) m_channelInfos[chan_idx].size())
673  return NULL;
674 
675  return &(m_channelInfos[chan_idx][sel]);
676 }
677 
678 const ChannelInfo *GuideGrid::GetChannelInfo(uint chan_idx, int sel) const
679 {
680  return ((GuideGrid*)this)->GetChannelInfo(chan_idx, sel);
681 }
682 
684 {
685  return m_channelInfos.size();
686 }
687 
689 {
690  uint cnt = GetChannelCount();
691  if (!cnt)
692  return -1;
693 
694  row = (row < 0) ? m_currentRow : row;
695  return (row + m_currentStartChannel) % cnt;
696 }
697 
699 {
700  ProgramList proglist;
701  MSqlBindings bindings;
702  QString querystr =
703  "WHERE program.chanid = :CHANID AND "
704  " program.endtime >= :STARTTS AND "
705  " program.starttime <= :ENDTS AND "
706  " program.manualid = 0 ";
707  bindings[":STARTTS"] =
708  m_currentStartTime.addSecs(0 - m_currentStartTime.time().second());
709  bindings[":ENDTS"] =
710  m_currentEndTime.addSecs(0 - m_currentEndTime.time().second());
711  bindings[":CHANID"] = chanid;
712 
714  LoadFromProgram(proglist, querystr, bindings, dummy);
715 
716  return proglist;
717 }
718 
720  uint chan_idx, bool with_same_channum) const
721 {
722  uint si = m_channelInfoIdx[chan_idx];
723  const ChannelInfo *chinfo = GetChannelInfo(chan_idx, si);
724 
725  PlayerContext *ctx = m_player->GetPlayerReadLock(-1, __FILE__, __LINE__);
726 
727  const uint cnt = (ctx && chinfo) ? m_channelInfos[chan_idx].size() : 0;
728  for (uint i = 0; i < cnt; ++i)
729  {
730  if (i == si)
731  continue;
732 
733  const ChannelInfo *ciinfo = GetChannelInfo(chan_idx, i);
734  if (!ciinfo)
735  continue;
736 
737  bool same_channum = ciinfo->channum == chinfo->channum;
738 
739  if (with_same_channum != same_channum)
740  continue;
741 
742  if (!m_player->IsTunable(ctx, ciinfo->chanid, true))
743  continue;
744 
745  if (with_same_channum)
746  {
747  si = i;
748  break;
749  }
750 
751  ProgramList proglist = GetProgramList(chinfo->chanid);
752  ProgramList ch_proglist = GetProgramList(ciinfo->chanid);
753 
754  if (proglist.empty() ||
755  proglist.size() != ch_proglist.size())
756  continue;
757 
758  bool isAlt = true;
759  for (uint j = 0; j < proglist.size(); ++j)
760  {
761  isAlt &= proglist[j]->IsSameProgramTimeslot(*ch_proglist[j]);
762  }
763 
764  if (isAlt)
765  {
766  si = i;
767  break;
768  }
769  }
770 
772 
773  return si;
774 }
775 
776 
777 #define MKKEY(IDX,SEL) ((((uint64_t)IDX) << 32) | SEL)
779 {
780  ChannelInfoList selected;
781 
782  int idx = GetStartChannelOffset();
783  if (idx < 0)
784  return selected;
785 
786  uint si = m_channelInfoIdx[idx];
787 
788  vector<uint64_t> sel;
789  sel.push_back( MKKEY(idx, si) );
790 
791  const ChannelInfo *ch = GetChannelInfo(sel[0]>>32, sel[0]&0xffff);
792  if (!ch)
793  return selected;
794 
795  selected.push_back(*ch);
796  if (m_channelInfos[idx].size() <= 1)
797  return selected;
798 
799  ProgramList proglist = GetProgramList(selected[0].chanid);
800 
801  if (proglist.empty())
802  return selected;
803 
804  for (uint i = 0; i < m_channelInfos[idx].size(); ++i)
805  {
806  const ChannelInfo *ci = GetChannelInfo(idx, i);
807  if (ci && (i != si) &&
808  (ci->callsign == ch->callsign) && (ci->channum == ch->channum))
809  {
810  sel.push_back( MKKEY(idx, i) );
811  }
812  }
813 
814  for (uint i = 0; i < m_channelInfos[idx].size(); ++i)
815  {
816  const ChannelInfo *ci = GetChannelInfo(idx, i);
817  if (ci && (i != si) &&
818  (ci->callsign == ch->callsign) && (ci->channum != ch->channum))
819  {
820  sel.push_back( MKKEY(idx, i) );
821  }
822  }
823 
824  for (uint i = 0; i < m_channelInfos[idx].size(); ++i)
825  {
826  const ChannelInfo *ci = GetChannelInfo(idx, i);
827  if ((i != si) && (ci->callsign != ch->callsign))
828  {
829  sel.push_back( MKKEY(idx, i) );
830  }
831  }
832 
833  for (uint i = 1; i < sel.size(); ++i)
834  {
835  const ChannelInfo *ci = GetChannelInfo(sel[i]>>32, sel[i]&0xffff);
836  const ProgramList ch_proglist = GetProgramList(ch->chanid);
837 
838  if (!ci || proglist.size() != ch_proglist.size())
839  continue;
840 
841  bool isAlt = true;
842  for (uint j = 0; j < proglist.size(); ++j)
843  {
844  isAlt &= proglist[j]->IsSameProgramTimeslot(*ch_proglist[j]);
845  }
846 
847  if (isAlt)
848  selected.push_back(*ci);
849  }
850 
851  return selected;
852 }
853 #undef MKKEY
854 
856 {
857  m_updateTimer->stop();
859  m_updateTimer->start((int)(60 * 1000));
860 }
861 
862 void GuideGrid::fillChannelInfos(bool gotostartchannel)
863 {
864  m_channelInfos.clear();
865  m_channelInfoIdx.clear();
867 
869  0, true, "", (m_changrpid < 0) ? 0 : m_changrpid);
871 
872  typedef vector<uint> uint_list_t;
873  QMap<QString,uint_list_t> channum_to_index_map;
874  QMap<QString,uint_list_t> callsign_to_index_map;
875 
876  for (uint i = 0; i < channels.size(); ++i)
877  {
878  uint chan = i;
879  if (m_sortReverse)
880  {
881  chan = channels.size() - i - 1;
882  }
883 
884  bool ndup = channum_to_index_map[channels[chan].channum].size();
885  bool cdup = callsign_to_index_map[channels[chan].callsign].size();
886 
887  if (ndup && cdup)
888  continue;
889 
890  ChannelInfo val(channels[chan]);
891 
892  channum_to_index_map[val.channum].push_back(GetChannelCount());
893  callsign_to_index_map[val.callsign].push_back(GetChannelCount());
894 
895  // add the new channel to the list
897  tmp.push_back(val);
898  m_channelInfos.push_back(tmp);
899  }
900 
901  // handle duplicates
902  for (uint i = 0; i < channels.size(); ++i)
903  {
904  const uint_list_t &ndups = channum_to_index_map[channels[i].channum];
905  for (uint j = 0; j < ndups.size(); ++j)
906  {
907  if (channels[i].chanid != m_channelInfos[ndups[j]][0].chanid &&
908  channels[i].callsign == m_channelInfos[ndups[j]][0].callsign)
909  m_channelInfos[ndups[j]].push_back(channels[i]);
910  }
911 
912  const uint_list_t &cdups = callsign_to_index_map[channels[i].callsign];
913  for (uint j = 0; j < cdups.size(); ++j)
914  {
915  if (channels[i].chanid != m_channelInfos[cdups[j]][0].chanid)
916  m_channelInfos[cdups[j]].push_back(channels[i]);
917  }
918  }
919 
920  if (gotostartchannel)
921  {
923  m_currentStartChannel = (uint) max(0, ch);
924  }
925 
926  if (m_channelInfos.empty())
927  {
928  LOG(VB_GENERAL, LOG_ERR, "GuideGrid: "
929  "\n\t\t\tYou don't have any channels defined in the database."
930  "\n\t\t\tGuide grid will have nothing to show you.");
931  }
932 }
933 
934 int GuideGrid::FindChannel(uint chanid, const QString &channum,
935  bool exact) const
936 {
937  static QMutex chanSepRegExpLock;
938  static QRegExp chanSepRegExp(ChannelUtil::kATSCSeparators);
939 
940  // first check chanid
941  uint i = (chanid) ? 0 : GetChannelCount();
942  for (; i < GetChannelCount(); ++i)
943  {
944  if (m_channelInfos[i][0].chanid == chanid)
945  return i;
946  }
947 
948  // then check for chanid in duplicates
949  i = (chanid) ? 0 : GetChannelCount();
950  for (; i < GetChannelCount(); ++i)
951  {
952  for (uint j = 1; j < m_channelInfos[i].size(); ++j)
953  {
954  if (m_channelInfos[i][j].chanid == chanid)
955  return i;
956  }
957  }
958 
959  // then check channum, first only
960  i = (channum.isEmpty()) ? GetChannelCount() : 0;
961  for (; i < GetChannelCount(); ++i)
962  {
963  if (m_channelInfos[i][0].channum == channum)
964  return i;
965  }
966 
967  // then check channum duplicates
968  i = (channum.isEmpty()) ? GetChannelCount() : 0;
969  for (; i < GetChannelCount(); ++i)
970  {
971  for (uint j = 1; j < m_channelInfos[i].size(); ++j)
972  {
973  if (m_channelInfos[i][j].channum == channum)
974  return i;
975  }
976  }
977 
978  if (exact || channum.isEmpty())
979  return -1;
980 
981  // then check partial channum, first only
982  for (i = 0; i < GetChannelCount(); ++i)
983  {
984  if (m_channelInfos[i][0].channum.left(channum.length()) == channum)
985  return i;
986  }
987 
988  // then check all partial channum
989  for (i = 0; i < GetChannelCount(); ++i)
990  {
991  for (uint j = 0; j < m_channelInfos[i].size(); ++j)
992  {
993  if (m_channelInfos[i][j].channum.left(channum.length()) == channum)
994  return i;
995  }
996  }
997 
998  // then check all channum with "_" for subchannels
999  QMutexLocker locker(&chanSepRegExpLock);
1000  QString tmpchannum = channum;
1001  if (tmpchannum.contains(chanSepRegExp))
1002  {
1003  tmpchannum.replace(chanSepRegExp, "_");
1004  }
1005  else if (channum.length() >= 2)
1006  {
1007  tmpchannum = channum.left(channum.length() - 1) + '_' +
1008  channum.right(1);
1009  }
1010  else
1011  {
1012  return -1;
1013  }
1014 
1015  for (i = 0; i < GetChannelCount(); ++i)
1016  {
1017  for (uint j = 0; j < m_channelInfos[i].size(); ++j)
1018  {
1019  QString tmp = m_channelInfos[i][j].channum;
1020  tmp.replace(chanSepRegExp, "_");
1021  if (tmp == tmpchannum)
1022  return i;
1023  }
1024  }
1025 
1026  return -1;
1027 }
1028 
1030 {
1031  m_timeList->Reset();
1032 
1033  QDateTime starttime = m_currentStartTime;
1034 
1036  m_lastTime = m_firstTime.addSecs(m_timeCount * 60 * 4);
1037 
1038  for (int x = 0; x < m_timeCount; ++x)
1039  {
1040  int mins = starttime.time().minute();
1041  mins = 5 * (mins / 5);
1042  if (mins % 30 == 0)
1043  {
1044  QString timeStr = MythDate::toString(starttime, MythDate::kTime);
1045 
1046  InfoMap infomap;
1047  infomap["starttime"] = timeStr;
1048 
1049  QDateTime endtime = starttime.addSecs(60 * 30);
1050 
1051  infomap["endtime"] = MythDate::toString(endtime, MythDate::kTime);
1052 
1053  MythUIButtonListItem *item =
1054  new MythUIButtonListItem(m_timeList, timeStr);
1055 
1056  item->SetTextFromMap(infomap);
1057  }
1058 
1059  starttime = starttime.addSecs(5 * 60);
1060  }
1061  m_currentEndTime = starttime;
1062 }
1063 
1064 void GuideGrid::fillProgramInfos(bool useExistingData)
1065 {
1067 
1068  for (int y = 0; y < m_channelCount; ++y)
1069  {
1070  fillProgramRowInfos(y, useExistingData);
1071  }
1072 }
1073 
1075 {
1076  ProgramList *proglist = new ProgramList();
1077 
1078  if (proglist)
1079  {
1080  MSqlBindings bindings;
1081  QString querystr = "WHERE program.chanid = :CHANID "
1082  " AND program.endtime >= :STARTTS "
1083  " AND program.starttime <= :ENDTS "
1084  " AND program.manualid = 0 ";
1085  bindings[":CHANID"] = GetChannelInfo(chanNum)->chanid;
1086  bindings[":STARTTS"] =
1087  m_currentStartTime.addSecs(0 - m_currentStartTime.time().second());
1088  bindings[":ENDTS"] =
1089  m_currentEndTime.addSecs(0 - m_currentEndTime.time().second());
1090 
1091  LoadFromProgram(*proglist, querystr, bindings, m_recList);
1092  }
1093 
1094  return proglist;
1095 }
1096 
1097 void GuideGrid::fillProgramRowInfos(unsigned int row, bool useExistingData)
1098 {
1099  m_guideGrid->ResetRow(row);
1100 
1101  // never divide by zero..
1103  return;
1104 
1105  for (int x = 0; x < m_timeCount; ++x)
1106  {
1107  m_programInfos[row][x] = NULL;
1108  }
1109 
1110  if (m_channelInfos.empty())
1111  return;
1112 
1113  int chanNum = row + m_currentStartChannel;
1114  if (chanNum >= (int) m_channelInfos.size())
1115  chanNum -= (int) m_channelInfos.size();
1116  if (chanNum >= (int) m_channelInfos.size())
1117  return;
1118 
1119  if (chanNum < 0)
1120  chanNum = 0;
1121 
1122  if (!useExistingData)
1123  {
1124  delete m_programs[row];
1125  m_programs[row] = getProgramListFromProgram(chanNum);
1126  }
1127 
1128  ProgramList *proglist = m_programs[row];
1129  if (!proglist)
1130  return;
1131 
1132  QDateTime ts = m_currentStartTime;
1133 
1134  QDateTime tnow = MythDate::current();
1135  int progPast = 0;
1136  if (tnow > m_currentEndTime)
1137  progPast = 100;
1138  else if (tnow < m_currentStartTime)
1139  progPast = 0;
1140  else
1141  {
1142  int played = m_currentStartTime.secsTo(tnow);
1144  if (length)
1145  progPast = played * 100 / length;
1146  }
1147 
1148  m_guideGrid->SetProgPast(progPast);
1149 
1150  ProgramList::iterator program = proglist->begin();
1151  vector<ProgramInfo*> unknownlist;
1152  bool unknown = false;
1153  ProgramInfo *proginfo = NULL;
1154  for (int x = 0; x < m_timeCount; ++x)
1155  {
1156  if (program != proglist->end() &&
1157  (ts >= (*program)->GetScheduledEndTime()))
1158  {
1159  ++program;
1160  }
1161 
1162  if ((program == proglist->end()) ||
1163  (ts < (*program)->GetScheduledStartTime()))
1164  {
1165  if (unknown)
1166  {
1167  proginfo->spread++;
1168  proginfo->SetScheduledEndTime(
1169  proginfo->GetScheduledEndTime().addSecs(5 * 60));
1170  }
1171  else
1172  {
1173  proginfo = new ProgramInfo(kUnknownTitle, kUnknownCategory,
1174  ts, ts.addSecs(5*60));
1175  unknownlist.push_back(proginfo);
1176  proginfo->startCol = x;
1177  proginfo->spread = 1;
1178  unknown = true;
1179  }
1180  }
1181  else
1182  {
1183  if (proginfo && proginfo == *program)
1184  {
1185  proginfo->spread++;
1186  }
1187  else
1188  {
1189  proginfo = *program;
1190  if (proginfo)
1191  {
1192  proginfo->startCol = x;
1193  proginfo->spread = 1;
1194  unknown = false;
1195  }
1196  }
1197  }
1198  m_programInfos[row][x] = proginfo;
1199  ts = ts.addSecs(5 * 60);
1200  }
1201 
1202  vector<ProgramInfo*>::iterator it = unknownlist.begin();
1203  for (; it != unknownlist.end(); ++it)
1204  proglist->push_back(*it);
1205 
1206  MythRect programRect = m_guideGrid->GetArea();
1207 
1209  double ydifference = 0.0, xdifference = 0.0;
1210 
1211  if (m_verticalLayout)
1212  {
1213  ydifference = programRect.width() /
1214  (double) m_guideGrid->getChannelCount();
1215  xdifference = programRect.height() /
1216  (double) m_timeCount;
1217  }
1218  else
1219  {
1220  ydifference = programRect.height() /
1221  (double) m_guideGrid->getChannelCount();
1222  xdifference = programRect.width() /
1223  (double) m_timeCount;
1224  }
1225 
1226  int arrow = 0;
1227  int cnt = 0;
1228  int spread = 1;
1229  QDateTime lastprog;
1230  QRect tempRect;
1231  bool isCurrent = false;
1232 
1233  for (int x = 0; x < m_timeCount; ++x)
1234  {
1235  ProgramInfo *pginfo = m_programInfos[row][x];
1236  if (!pginfo)
1237  continue;
1238 
1239  spread = 1;
1240  if (pginfo->GetScheduledStartTime() != lastprog)
1241  {
1242  arrow = 0;
1243  if (pginfo->GetScheduledStartTime() < m_firstTime.addSecs(-300))
1244  arrow = arrow + 1;
1245  if (pginfo->GetScheduledEndTime() > m_lastTime.addSecs(2100))
1246  arrow = arrow + 2;
1247 
1248  if (pginfo->spread != -1)
1249  {
1250  spread = pginfo->spread;
1251  }
1252  else
1253  {
1254  for (int z = x + 1; z < m_timeCount; ++z)
1255  {
1256  ProgramInfo *test = m_programInfos[row][z];
1257  if (test && (test->GetScheduledStartTime() ==
1258  pginfo->GetScheduledStartTime()))
1259  spread++;
1260  }
1261  pginfo->spread = spread;
1262  pginfo->startCol = x;
1263 
1264  for (int z = x + 1; z < x + spread; ++z)
1265  {
1266  ProgramInfo *test = m_programInfos[row][z];
1267  if (test)
1268  {
1269  test->spread = spread;
1270  test->startCol = x;
1271  }
1272  }
1273  }
1274 
1275  if (m_verticalLayout)
1276  {
1277  tempRect = QRect((int)(row * ydifference),
1278  (int)(x * xdifference),
1279  (int)(ydifference),
1280  (int)(xdifference * pginfo->spread));
1281  }
1282  else
1283  {
1284  tempRect = QRect((int)(x * xdifference),
1285  (int)(row * ydifference),
1286  (int)(xdifference * pginfo->spread),
1287  (int)ydifference);
1288  }
1289 
1290  // snap to right edge for last entry.
1291  if (tempRect.right() + 2 >= programRect.width())
1292  tempRect.setRight(programRect.width());
1293  if (tempRect.bottom() + 2 >= programRect.bottom())
1294  tempRect.setBottom(programRect.bottom());
1295 
1296  if (m_currentRow == (int)row && (m_currentCol >= x) &&
1297  (m_currentCol < (x + spread)))
1298  isCurrent = true;
1299  else
1300  isCurrent = false;
1301 
1302  int recFlag;
1303  switch (pginfo->GetRecordingRuleType())
1304  {
1305  case kSingleRecord:
1306  recFlag = 1;
1307  break;
1308  case kDailyRecord:
1309  recFlag = 2;
1310  break;
1311  case kAllRecord:
1312  recFlag = 4;
1313  break;
1314  case kWeeklyRecord:
1315  recFlag = 5;
1316  break;
1317  case kOneRecord:
1318  recFlag = 6;
1319  break;
1320  case kOverrideRecord:
1321  case kDontRecord:
1322  recFlag = 7;
1323  break;
1324  case kNotRecording:
1325  default:
1326  recFlag = 0;
1327  break;
1328  }
1329 
1330  int recStat;
1331  if (pginfo->GetRecordingStatus() == rsConflict ||
1332  pginfo->GetRecordingStatus() == rsOffLine)
1333  recStat = 2;
1334  else if (pginfo->GetRecordingStatus() <= rsWillRecord)
1335  recStat = 1;
1336  else
1337  recStat = 0;
1338 
1340  row, cnt, tempRect, pginfo->GetTitle(),
1341  pginfo->GetCategory(), arrow, recFlag,
1342  recStat, isCurrent);
1343 
1344  cnt++;
1345  }
1346 
1347  lastprog = pginfo->GetScheduledStartTime();
1348  }
1349 }
1350 
1351 void GuideGrid::customEvent(QEvent *event)
1352 {
1353  if ((MythEvent::Type)(event->type()) == MythEvent::MythEventMessage)
1354  {
1355  MythEvent *me = (MythEvent *)event;
1356  QString message = me->Message();
1357 
1358  if (message == "SCHEDULE_CHANGE")
1359  {
1361  fillProgramInfos();
1362  updateInfo();
1363  }
1364  else if (message == "STOP_VIDEO_REFRESH_TIMER")
1365  {
1367  }
1368  else if (message == "START_VIDEO_REFRESH_TIMER")
1369  {
1370  m_previewVideoRefreshTimer->start(66);
1371  }
1372  }
1373  else if (event->type() == DialogCompletionEvent::kEventType)
1374  {
1376 
1377  QString resultid = dce->GetId();
1378  QString resulttext = dce->GetResultText();
1379  int buttonnum = dce->GetResult();
1380 
1381  if (resultid == "deleterule")
1382  {
1383  RecordingRule *record =
1384  qVariantValue<RecordingRule *>(dce->GetData());
1385  if (record)
1386  {
1387  if ((buttonnum > 0) && !record->Delete())
1388  LOG(VB_GENERAL, LOG_ERR, "Failed to delete recording rule");
1389  delete record;
1390  }
1391  }
1392  else if (resultid == "guidemenu")
1393  {
1394  if (resulttext == tr("Record This"))
1395  {
1396  quickRecord();
1397  }
1398  else if (resulttext == tr("Change to Channel"))
1399  {
1400  enter();
1401  }
1402  else if (resulttext == tr("Program Details"))
1403  {
1404  details();
1405  }
1406  else if (resulttext == tr("Reverse Channel Order"))
1407  {
1409  generateListings();
1410  updateChannels();
1411  }
1412  else if (resulttext == tr("Add To Channel Group"))
1413  {
1414  if (m_changrpid == -1)
1415  ChannelGroupMenu(0);
1416  }
1417  else if (resulttext == tr("Remove from Channel Group"))
1418  {
1420  }
1421  else if (resulttext == tr("Choose Channel Group"))
1422  {
1423  ChannelGroupMenu(1);
1424  }
1425  else if (resulttext == tr("Recording Options"))
1426  {
1428  }
1429  else if (resulttext == tr("Jump to Time"))
1430  {
1431  ShowJumpToTime();
1432  }
1433  }
1434  else if (resultid == "recmenu")
1435  {
1436  if (resulttext == tr("Edit Recording Status"))
1437  {
1438  editRecSchedule();
1439  }
1440  else if (resulttext == tr("Edit Schedule"))
1441  {
1442  editSchedule();
1443  }
1444  else if (resulttext == tr("Show Upcoming"))
1445  {
1446  upcoming();
1447  }
1448  else if (resulttext == tr("Custom Edit"))
1449  {
1450  customEdit();
1451  }
1452  else if (resulttext == tr("Delete Rule"))
1453  {
1454  deleteRule();
1455  }
1456 
1457  }
1458  else if (resultid == "channelgrouptogglemenu")
1459  {
1460  int changroupid;
1461  changroupid = ChannelGroup::GetChannelGroupId(resulttext);
1462 
1463  if (changroupid > 0)
1464  toggleChannelFavorite(changroupid);
1465  }
1466  else if (resultid == "channelgroupmenu")
1467  {
1468  if (buttonnum >= 0)
1469  {
1470  int changroupid;
1471 
1472  if (resulttext == QObject::tr("All Channels"))
1473  changroupid = -1;
1474  else
1475  changroupid = ChannelGroup::GetChannelGroupId(resulttext);
1476 
1477  m_changrpid = changroupid;
1478  generateListings();
1479  updateChannels();
1480  updateInfo();
1481 
1482  QString changrpname;
1484 
1485  if (m_changroupname)
1486  m_changroupname->SetText(changrpname);
1487  }
1488  }
1489  else if (resultid == "jumptotime")
1490  {
1491  QDateTime datetime = dce->GetData().toDateTime();
1492  moveToTime(datetime);
1493  }
1494  else
1496  }
1497 }
1498 
1500 {
1501  if (m_dateText)
1503  if (m_longdateText)
1506 }
1507 
1509 {
1510  m_channelList->Reset();
1511 
1513 
1514  if (m_player)
1516 
1517  for (unsigned int y = 0; (y < (unsigned int)m_channelCount) && chinfo; ++y)
1518  {
1519  unsigned int chanNumber = y + m_currentStartChannel;
1520  if (chanNumber >= m_channelInfos.size())
1521  chanNumber -= m_channelInfos.size();
1522  if (chanNumber >= m_channelInfos.size())
1523  break;
1524 
1525  chinfo = GetChannelInfo(chanNumber);
1526 
1527  bool unavailable = false, try_alt = false;
1528 
1529  if (m_player)
1530  {
1532  -1, __FILE__, __LINE__);
1533  if (ctx && chinfo)
1534  try_alt = !m_player->IsTunable(ctx, chinfo->chanid, true);
1535  m_player->ReturnPlayerLock(ctx);
1536  }
1537 
1538  if (try_alt)
1539  {
1540  unavailable = true;
1541 
1542  // Try alternates with same channum if applicable
1543  uint alt = GetAlternateChannelIndex(chanNumber, true);
1544  if (alt != m_channelInfoIdx[chanNumber])
1545  {
1546  unavailable = false;
1547  m_channelInfoIdx[chanNumber] = alt;
1548  chinfo = GetChannelInfo(chanNumber);
1549  }
1550 
1551  // Try alternates with different channum if applicable
1552  if (unavailable && chinfo &&
1553  !GetProgramList(chinfo->chanid).empty())
1554  {
1555  alt = GetAlternateChannelIndex(chanNumber, false);
1556  unavailable = (alt == m_channelInfoIdx[chanNumber]);
1557  }
1558  }
1559 
1560  MythUIButtonListItem *item =
1562  chinfo ? chinfo->GetFormatted(ChannelInfo::kChannelShort) : QString());
1563 
1564  QString state = "available";
1565  if (unavailable)
1566  state = (m_changrpid == -1) ? "unavailable" : "favunavailable";
1567  else
1568  state = (m_changrpid == -1) ? "available" : "favourite";
1569 
1570  item->SetFontState(state);
1571  item->DisplayState(state, "chanstatus");
1572 
1573  if (chinfo)
1574  {
1575  InfoMap infomap;
1576  chinfo->ToMap(infomap);
1577  item->SetTextFromMap(infomap);
1578 
1579  if (!chinfo->icon.isEmpty())
1580  {
1581  QString iconurl =
1582  gCoreContext->GetMasterHostPrefix("ChannelIcons",
1583  chinfo->icon);
1584  item->SetImage(iconurl, "channelicon");
1585  }
1586  }
1587  }
1588 }
1589 
1591 {
1592  if (m_currentRow < 0 || m_currentCol < 0)
1593  return;
1594 
1596  if (!pginfo)
1597  return;
1598 
1599  InfoMap infoMap;
1600 
1601  int chanNum = m_currentRow + m_currentStartChannel;
1602  if (chanNum >= (int)m_channelInfos.size())
1603  chanNum -= (int)m_channelInfos.size();
1604  if (chanNum >= (int)m_channelInfos.size())
1605  return;
1606  if (chanNum < 0)
1607  chanNum = 0;
1608 
1609  ChannelInfo *chinfo = GetChannelInfo(chanNum);
1610 
1611  if (m_channelImage)
1612  {
1613  m_channelImage->Reset();
1614  if (!chinfo->icon.isEmpty())
1615  {
1616  QString iconurl = gCoreContext->GetMasterHostPrefix("ChannelIcons",
1617  chinfo->icon);
1618 
1619  m_channelImage->SetFilename(iconurl);
1620  m_channelImage->Load();
1621  }
1622  }
1623 
1624  chinfo->ToMap(infoMap);
1625  pginfo->ToMap(infoMap);
1626  SetTextFromMap(infoMap);
1627 
1628  MythUIStateType *ratingState = dynamic_cast<MythUIStateType*>
1629  (GetChild("ratingstate"));
1630  if (ratingState)
1631  {
1632  QString rating = QString::number(pginfo->GetStars(10));
1633  ratingState->DisplayState(rating);
1634  }
1635 }
1636 
1638 {
1639  int oldchangrpid = m_changrpid;
1640 
1642 
1643  if (oldchangrpid != m_changrpid)
1644  generateListings();
1645 
1646  updateChannels();
1647  updateInfo();
1648 
1649  QString changrpname = ChannelGroup::GetChannelGroupName(m_changrpid);
1650 
1651  if (m_changroupname)
1652  m_changroupname->SetText(changrpname);
1653 }
1654 
1656 {
1658  m_currentRow = 0;
1659 
1660  int maxchannel = 0;
1661  fillChannelInfos();
1662  maxchannel = max((int)GetChannelCount() - 1, 0);
1663  m_channelCount = min(m_guideGrid->getChannelCount(), maxchannel + 1);
1664 
1666  fillProgramInfos();
1667 }
1668 
1670 {
1672 
1673  if (channels.empty())
1674  {
1675  QString message = tr("You don't have any channel groups defined");
1676 
1677  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1678 
1679  MythConfirmationDialog *okPopup = new MythConfirmationDialog(popupStack,
1680  message, false);
1681  if (okPopup->Create())
1682  popupStack->AddScreen(okPopup);
1683  else
1684  delete okPopup;
1685 
1686  return;
1687  }
1688 
1689  QString label = tr("Select Channel Group");
1690 
1691  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1692  MythDialogBox *menuPopup = new MythDialogBox(label, popupStack, "menuPopup");
1693 
1694  if (menuPopup->Create())
1695  {
1696  if (mode == 0)
1697  {
1698  // add channel to group menu
1699  menuPopup->SetReturnEvent(this, "channelgrouptogglemenu");
1700  }
1701  else
1702  {
1703  // switch to channel group menu
1704  menuPopup->SetReturnEvent(this, "channelgroupmenu");
1705  menuPopup->AddButton(QObject::tr("All Channels"));
1706  }
1707 
1708  for (uint i = 0; i < channels.size(); ++i)
1709  {
1710  menuPopup->AddButton(channels[i].name);
1711  }
1712 
1713  popupStack->AddScreen(menuPopup);
1714  }
1715  else
1716  {
1717  delete menuPopup;
1718  }
1719 }
1720 
1722 {
1723  MSqlQuery query(MSqlQuery::InitCon());
1724 
1725  if (grpid == -1)
1726  {
1727  if (m_changrpid == -1)
1728  return;
1729  else
1730  grpid = m_changrpid;
1731  }
1732 
1733  // Get current channel id, and make sure it exists...
1734  int chanNum = m_currentRow + m_currentStartChannel;
1735  if (chanNum >= (int)m_channelInfos.size())
1736  chanNum -= (int)m_channelInfos.size();
1737  if (chanNum >= (int)m_channelInfos.size())
1738  return;
1739  if (chanNum < 0)
1740  chanNum = 0;
1741 
1742  ChannelInfo *ch = GetChannelInfo(chanNum);
1743  uint chanid = ch->chanid;
1744 
1745  if (m_changrpid == -1)
1746  // If currently viewing all channels, allow to add only not delete
1747  ChannelGroup::ToggleChannel(chanid, grpid, false);
1748  else
1749  // Only allow delete if viewing the favorite group in question
1750  ChannelGroup::ToggleChannel(chanid, grpid, true);
1751 
1752  //regenerate the list of non empty group in case it did change
1754 
1755  // If viewing favorites, refresh because a channel was removed
1756  if (m_changrpid != -1)
1757  {
1758  generateListings();
1759  updateChannels();
1760  updateInfo();
1761  }
1762 }
1763 
1765 {
1767 
1768  if (!test)
1769  {
1771  return;
1772  }
1773 
1774  int startCol = test->startCol;
1775  m_currentCol = startCol - 1;
1776 
1777  if (m_currentCol < 0)
1778  {
1779  m_currentCol = 0;
1781  }
1782  else
1783  {
1786  updateInfo();
1787  }
1788 }
1789 
1791 {
1793 
1794  if (!test)
1795  {
1797  return;
1798  }
1799 
1800  int spread = test->spread;
1801  int startCol = test->startCol;
1802 
1803  m_currentCol = startCol + spread;
1804 
1805  if (m_currentCol > m_timeCount - 1)
1806  {
1807  m_currentCol = m_timeCount - 1;
1809  }
1810  else
1811  {
1814  updateInfo();
1815  }
1816 }
1817 
1819 {
1820  m_currentRow++;
1821 
1822  if (m_currentRow > m_channelCount - 1)
1823  {
1826  }
1827  else
1828  {
1831  updateInfo();
1832  updateChannels();
1833  }
1834 }
1835 
1837 {
1838  m_currentRow--;
1839 
1840  if (m_currentRow < 0)
1841  {
1842  m_currentRow = 0;
1844  }
1845  else
1846  {
1849  updateInfo();
1850  updateChannels();
1851  }
1852 }
1853 
1855 {
1856  switch (movement)
1857  {
1858  case kScrollLeft :
1859  m_currentStartTime = m_currentStartTime.addSecs(-30 * 60);
1860  break;
1861  case kScrollRight :
1862  m_currentStartTime = m_currentStartTime.addSecs(30 * 60);
1863  break;
1864  case kPageLeft :
1865  m_currentStartTime = m_currentStartTime.addSecs(-5 * 60 * m_timeCount);
1866  break;
1867  case kPageRight :
1869  break;
1870  case kDayLeft :
1871  m_currentStartTime = m_currentStartTime.addSecs(-24 * 60 * 60);
1872  break;
1873  case kDayRight :
1874  m_currentStartTime = m_currentStartTime.addSecs(24 * 60 * 60);
1875  break;
1876  default :
1877  break;
1878  }
1879 
1880  fillTimeInfos();
1881  fillProgramInfos();
1883  updateInfo();
1884  updateDateText();
1885 }
1886 
1888 {
1889  switch (movement)
1890  {
1891  case kScrollDown :
1893  break;
1894  case kScrollUp :
1896  break;
1897  case kPageDown :
1899  break;
1900  case kPageUp :
1902  break;
1903  default :
1904  break;
1905  }
1906 
1907  fillProgramInfos();
1909  updateInfo();
1910  updateChannels();
1911 }
1912 
1913 void GuideGrid::moveToTime(QDateTime datetime)
1914 {
1915  if (!datetime.isValid())
1916  return;
1917 
1918  m_currentStartTime = datetime;
1919 
1920  fillTimeInfos();
1921  fillProgramInfos();
1923  updateInfo();
1924  updateDateText();
1925 }
1926 
1927 void GuideGrid::setStartChannel(int newStartChannel)
1928 {
1929  if (newStartChannel < 0)
1930  m_currentStartChannel = newStartChannel + GetChannelCount();
1931  else if (newStartChannel >= (int) GetChannelCount())
1932  m_currentStartChannel = newStartChannel - GetChannelCount();
1933  else
1934  m_currentStartChannel = newStartChannel;
1935 }
1936 
1938 {
1939  if (m_allowFinder)
1941 }
1942 
1944 {
1945  if (!m_player)
1946  return;
1947 
1948  if (m_updateTimer)
1949  m_updateTimer->stop();
1950 
1951  channelUpdate();
1952 
1953  // Don't perform transition effects when guide is being used during playback
1954  GetScreenStack()->PopScreen(this, false);
1955 
1956  epgIsVisibleCond.wakeAll();
1957 }
1958 
1960 {
1961  // HACK: Do not allow exit if we have a popup menu open, not convinced
1962  // that this is the right solution
1963  if (GetMythMainWindow()->GetStack("popup stack")->TotalScreens() > 0)
1964  return;
1965 
1966  if (m_updateTimer)
1967  m_updateTimer->stop();
1968 
1969  // don't fade the screen if we are returning to the player
1970  if (m_player)
1971  GetScreenStack()->PopScreen(this, false);
1972  else
1973  GetScreenStack()->PopScreen(this, true);
1974 
1975  epgIsVisibleCond.wakeAll();
1976 }
1977 
1979 {
1981 
1982  if (!pginfo)
1983  return;
1984 
1985  if (pginfo->GetTitle() == kUnknownTitle)
1986  return;
1987 
1988  QuickRecord(pginfo);
1990  fillProgramInfos();
1991  updateInfo();
1992 }
1993 
1995 {
1997 
1998  if (!pginfo)
1999  return;
2000 
2001  if (pginfo->GetTitle() == kUnknownTitle)
2002  return;
2003 
2004  EditRecording(pginfo);
2005 }
2006 
2008 {
2010 
2011  if (!pginfo)
2012  return;
2013 
2014  if (pginfo->GetTitle() == kUnknownTitle)
2015  return;
2016 
2017  EditScheduled(pginfo);
2018 }
2019 
2021 {
2023 
2024  EditCustom(pginfo);
2025 }
2026 
2028 {
2030 
2031  if (!pginfo || !pginfo->GetRecordingRuleID())
2032  return;
2033 
2034  RecordingRule *record = new RecordingRule();
2035  if (!record->LoadByProgram(pginfo))
2036  {
2037  delete record;
2038  return;
2039  }
2040 
2041  QString message = tr("Delete '%1' %2 rule?").arg(record->m_title)
2042  .arg(toString(pginfo->GetRecordingRuleType()));
2043 
2044  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
2045 
2046  MythConfirmationDialog *okPopup = new MythConfirmationDialog(popupStack,
2047  message, true);
2048 
2049  okPopup->SetReturnEvent(this, "deleterule");
2050  okPopup->SetData(qVariantFromValue(record));
2051 
2052  if (okPopup->Create())
2053  popupStack->AddScreen(okPopup);
2054  else
2055  delete okPopup;
2056 }
2057 
2059 {
2061 
2062  if (!pginfo)
2063  return;
2064 
2065  if (pginfo->GetTitle() == kUnknownTitle)
2066  return;
2067 
2068  ShowUpcoming(pginfo);
2069 }
2070 
2072 {
2074 
2075  if (!pginfo)
2076  return;
2077 
2078  if (pginfo->GetTitle() == kUnknownTitle)
2079  return;
2080 
2081  ShowDetails(pginfo);
2082 }
2083 
2085 {
2086  if (!m_player)
2087  return;
2088 
2089  ChannelInfoList sel = GetSelection();
2090 
2091  if (sel.size())
2092  {
2093  PlayerContext *ctx = m_player->GetPlayerReadLock(-1, __FILE__, __LINE__);
2094  m_player->ChangeChannel(ctx, sel);
2095  m_player->ReturnPlayerLock(ctx);
2096  }
2097 }
2098 
2100 {
2101  if (m_player)
2102  {
2103  PlayerContext *ctx = m_player->GetPlayerReadLock(-1, __FILE__, __LINE__);
2104  m_player->ChangeVolume(ctx, up);
2105  m_player->ReturnPlayerLock(ctx);
2106  }
2107 }
2108 
2109 void GuideGrid::toggleMute(const bool muteIndividualChannels)
2110 {
2111  if (m_player)
2112  {
2113  PlayerContext *ctx = m_player->GetPlayerReadLock(-1, __FILE__, __LINE__);
2114  m_player->ToggleMute(ctx, muteIndividualChannels);
2115  m_player->ReturnPlayerLock(ctx);
2116  }
2117 }
2118 
2119 void GuideGrid::GoTo(int start, int cur_row)
2120 {
2121  setStartChannel(start);
2122  m_currentRow = cur_row % m_channelCount;
2123  updateChannels();
2124  fillProgramInfos();
2125  updateInfo();
2127 }
2128 
2130 {
2131  QString txt;
2132  {
2133  QMutexLocker locker(&m_jumpToChannelLock);
2134  if (m_jumpToChannel)
2135  txt = m_jumpToChannel->GetEntry();
2136  }
2137 
2138  if (txt.isEmpty())
2139  return;
2140 
2141  if (m_jumpToText)
2142  m_jumpToText->SetText(txt);
2143 }
2144 
2146 {
2147  QMutexLocker locker(&m_jumpToChannelLock);
2148  m_jumpToChannel = ptr;
2149 
2150  if (!m_jumpToChannel)
2151  {
2152  if (m_jumpToText)
2153  m_jumpToText->Reset();
2154 
2155  updateDateText();
2156  }
2157 }
2158 
2160 {
2161  GetMythMainWindow()->GetPaintWindow()->clearMask();
2162 }
2163 
2165 {
2166  MythEvent *me = new MythEvent("STOP_VIDEO_REFRESH_TIMER");
2167  qApp->postEvent(this, me);
2168 
2170  if (!m_usingNullVideo)
2171  {
2172  QRegion r1 = QRegion(m_Area);
2173  QRegion r2 = QRegion(m_videoRect);
2174  GetMythMainWindow()->GetPaintWindow()->setMask(r1.xored(r2));
2176  }
2177  else
2178  {
2179  me = new MythEvent("START_VIDEO_REFRESH_TIMER");
2180  qApp->postEvent(this, me);
2181  }
2182 }
2183 
2185 {
2186  if (m_player && m_usingNullVideo)
2187  {
2189  }
2190 }
2191 
2193 {
2194  if (m_player)
2195  HideTVWindow();
2196 
2198 }
2199 
2201 {
2202  if (m_player)
2203  EmbedTVWindow();
2204 
2206 }
2207 
2209 {
2210  QString message = tr("Jump to a specific date and time in the guide");
2213 
2214  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
2215  MythTimeInputDialog *timedlg = new MythTimeInputDialog(popupStack, message,
2216  flags);
2217 
2218  if (timedlg->Create())
2219  {
2220  timedlg->SetReturnEvent(this, "jumptotime");
2221  popupStack->AddScreen(timedlg);
2222  }
2223  else
2224  delete timedlg;
2225 }
2226