vdr  1.7.31
menu.c
Go to the documentation of this file.
1 /*
2  * menu.c: The actual menu implementations
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * $Id: menu.c 2.61 2012/09/15 11:45:28 kls Exp $
8  */
9 
10 #include "menu.h"
11 #include <ctype.h>
12 #include <limits.h>
13 #include <math.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include "channels.h"
18 #include "config.h"
19 #include "cutter.h"
20 #include "eitscan.h"
21 #include "filetransfer.h"
22 #include "i18n.h"
23 #include "interface.h"
24 #include "plugin.h"
25 #include "recording.h"
26 #include "remote.h"
27 #include "shutdown.h"
28 #include "sourceparams.h"
29 #include "sources.h"
30 #include "status.h"
31 #include "themes.h"
32 #include "timers.h"
33 #include "transfer.h"
34 #include "videodir.h"
35 
36 #define MAXWAIT4EPGINFO 3 // seconds
37 #define MODETIMEOUT 3 // seconds
38 #define NEWTIMERLIMIT 120 // seconds until the start time of a new timer created from the Schedule menu,
39  // within which it will go directly into the "Edit timer" menu to allow
40  // further parameter settings
41 #define DEFERTIMER 60 // seconds by which a timer is deferred in case of problems
42 
43 #define MAXRECORDCONTROLS (MAXDEVICES * MAXRECEIVERS)
44 #define MAXINSTANTRECTIME (24 * 60 - 1) // 23:59 hours
45 #define MAXWAITFORCAMMENU 10 // seconds to wait for the CAM menu to open
46 #define CAMMENURETYTIMEOUT 3 // seconds after which opening the CAM menu is retried
47 #define CAMRESPONSETIMEOUT 5 // seconds to wait for a response from a CAM
48 #define MINFREEDISK 300 // minimum free disk space (in MB) required to start recording
49 #define NODISKSPACEDELTA 300 // seconds between "Not enough disk space to start recording!" messages
50 #define MAXCHNAMWIDTH 16 // maximum number of characters of channels' short names shown in schedules menus
51 
52 #define CHNUMWIDTH (numdigits(Channels.MaxNumber()) + 1)
53 #define CHNAMWIDTH (min(MAXCHNAMWIDTH, Channels.MaxShortChannelNameLength() + 1))
54 
55 // --- cMenuEditCaItem -------------------------------------------------------
56 
58 protected:
59  virtual void Set(void);
60 public:
61  cMenuEditCaItem(const char *Name, int *Value);
63  };
64 
65 cMenuEditCaItem::cMenuEditCaItem(const char *Name, int *Value)
66 :cMenuEditIntItem(Name, Value, 0)
67 {
68  Set();
69 }
70 
72 {
73  if (*value == CA_FTA)
74  SetValue(tr("Free To Air"));
75  else if (*value >= CA_ENCRYPTED_MIN)
76  SetValue(tr("encrypted"));
77  else
79 }
80 
82 {
84 
85  if (state == osUnknown) {
86  if (NORMALKEY(Key) == kLeft && *value >= CA_ENCRYPTED_MIN)
87  *value = CA_FTA;
88  else
89  return cMenuEditIntItem::ProcessKey(Key);
90  Set();
91  state = osContinue;
92  }
93  return state;
94 }
95 
96 // --- cMenuEditSrcItem ------------------------------------------------------
97 
99 private:
100  const cSource *source;
101 protected:
102  virtual void Set(void);
103 public:
104  cMenuEditSrcItem(const char *Name, int *Value);
106  };
107 
108 cMenuEditSrcItem::cMenuEditSrcItem(const char *Name, int *Value)
109 :cMenuEditIntItem(Name, Value, 0)
110 {
111  source = Sources.Get(*Value);
112  Set();
113 }
114 
116 {
117  if (source)
119  else
121 }
122 
124 {
126 
127  if (state == osUnknown) {
128  bool IsRepeat = Key & k_Repeat;
129  Key = NORMALKEY(Key);
130  if (Key == kLeft) { // TODO might want to increase the delta if repeated quickly?
131  if (source) {
132  if (source->Prev())
133  source = (cSource *)source->Prev();
134  else if (!IsRepeat)
135  source = Sources.Last();
136  *value = source->Code();
137  }
138  }
139  else if (Key == kRight) {
140  if (source) {
141  if (source->Next())
142  source = (cSource *)source->Next();
143  else if (!IsRepeat)
144  source = Sources.First();
145  }
146  else
147  source = Sources.First();
148  if (source)
149  *value = source->Code();
150  }
151  else
152  return state; // we don't call cMenuEditIntItem::ProcessKey(Key) here since we don't accept numerical input
153  Set();
154  state = osContinue;
155  }
156  return state;
157 }
158 
159 // --- cMenuEditChannel ------------------------------------------------------
160 
161 class cMenuEditChannel : public cOsdMenu {
162 private:
166  char name[256];
167  void Setup(void);
168 public:
169  cMenuEditChannel(cChannel *Channel, bool New = false);
170  virtual eOSState ProcessKey(eKeys Key);
171  };
172 
174 :cOsdMenu(tr("Edit channel"), 16)
175 {
177  channel = Channel;
178  sourceParam = NULL;
179  *name = 0;
180  if (channel) {
181  data = *channel;
182  strn0cpy(name, data.name, sizeof(name));
183  if (New) {
184  channel = NULL;
185  data.nid = 0;
186  data.tid = 0;
187  data.rid = 0;
188  }
189  }
190  Setup();
191 }
192 
194 {
195  int current = Current();
196 
197  Clear();
198 
199  // Parameters for all types of sources:
200  Add(new cMenuEditStrItem( tr("Name"), name, sizeof(name)));
201  Add(new cMenuEditSrcItem( tr("Source"), &data.source));
202  Add(new cMenuEditIntItem( tr("Frequency"), &data.frequency));
203  Add(new cMenuEditIntItem( tr("Vpid"), &data.vpid, 0, 0x1FFF));
204  Add(new cMenuEditIntItem( tr("Ppid"), &data.ppid, 0, 0x1FFF));
205  Add(new cMenuEditIntItem( tr("Apid1"), &data.apids[0], 0, 0x1FFF));
206  Add(new cMenuEditIntItem( tr("Apid2"), &data.apids[1], 0, 0x1FFF));
207  Add(new cMenuEditIntItem( tr("Dpid1"), &data.dpids[0], 0, 0x1FFF));
208  Add(new cMenuEditIntItem( tr("Dpid2"), &data.dpids[1], 0, 0x1FFF));
209  Add(new cMenuEditIntItem( tr("Spid1"), &data.spids[0], 0, 0x1FFF));
210  Add(new cMenuEditIntItem( tr("Spid2"), &data.spids[1], 0, 0x1FFF));
211  Add(new cMenuEditIntItem( tr("Tpid"), &data.tpid, 0, 0x1FFF));
212  Add(new cMenuEditCaItem( tr("CA"), &data.caids[0]));
213  Add(new cMenuEditIntItem( tr("Sid"), &data.sid, 1, 0xFFFF));
214  /* XXX not yet used
215  Add(new cMenuEditIntItem( tr("Nid"), &data.nid, 0));
216  Add(new cMenuEditIntItem( tr("Tid"), &data.tid, 0));
217  Add(new cMenuEditIntItem( tr("Rid"), &data.rid, 0));
218  XXX*/
219  // Parameters for specific types of sources:
221  if (sourceParam) {
223  cOsdItem *Item;
224  while ((Item = sourceParam->GetOsdItem()) != NULL)
225  Add(Item);
226  }
227 
228  SetCurrent(Get(current));
229  Display();
230 }
231 
233 {
234  int oldSource = data.source;
235  eOSState state = cOsdMenu::ProcessKey(Key);
236 
237  if (state == osUnknown) {
238  if (Key == kOk) {
239  if (sourceParam)
243  if (channel) {
244  *channel = data;
245  isyslog("edited channel %d %s", channel->Number(), *data.ToText());
246  state = osBack;
247  }
248  else {
249  channel = new cChannel;
250  *channel = data;
252  Channels.ReNumber();
253  isyslog("added channel %d %s", channel->Number(), *data.ToText());
254  state = osUser1;
255  }
256  Channels.SetModified(true);
257  }
258  else {
259  Skins.Message(mtError, tr("Channel settings are not unique!"));
260  state = osContinue;
261  }
262  }
263  }
264  if (Key != kNone && (data.source & cSource::st_Mask) != (oldSource & cSource::st_Mask)) {
265  if (sourceParam)
267  Setup();
268  }
269  return state;
270 }
271 
272 // --- cMenuChannelItem ------------------------------------------------------
273 
274 class cMenuChannelItem : public cOsdItem {
275 public:
277 private:
280 public:
284  static eChannelSortMode SortMode(void) { return sortMode; }
285  virtual int Compare(const cListObject &ListObject) const;
286  virtual void Set(void);
287  cChannel *Channel(void) { return channel; }
288  };
289 
291 
293 {
294  channel = Channel;
295  if (channel->GroupSep())
296  SetSelectable(false);
297  Set();
298 }
299 
300 int cMenuChannelItem::Compare(const cListObject &ListObject) const
301 {
302  cMenuChannelItem *p = (cMenuChannelItem *)&ListObject;
303  int r = -1;
304  if (sortMode == csmProvider)
305  r = strcoll(channel->Provider(), p->channel->Provider());
306  if (sortMode == csmName || r == 0)
307  r = strcoll(channel->Name(), p->channel->Name());
308  if (sortMode == csmNumber || r == 0)
309  r = channel->Number() - p->channel->Number();
310  return r;
311 }
312 
314 {
315  cString buffer;
316  const cEvent *Event = NULL;
317  if (!channel->GroupSep()) {
318  cSchedulesLock SchedulesLock;
319  const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
320  const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
321  if (Schedule)
322  Event = Schedule->GetPresentEvent();
323 
324  if (sortMode == csmProvider)
325  buffer = cString::sprintf("%d\t%s - %s %c%s%c", channel->Number(), channel->Provider(), channel->Name(),
326  Event ? '(' : ' ', Event ? Event->Title() : "", Event ? ')' : ' ');
327  else
328  buffer = cString::sprintf("%d\t%s %c%s%c", channel->Number(), channel->Name(),
329  Event ? '(' : ' ', Event ? Event->Title() : "", Event ? ')' : ' ');
330  }
331  else
332  buffer = cString::sprintf("---\t%s ----------------------------------------------------------------", channel->Name());
333  SetText(buffer);
334 }
335 
336 // --- cMenuChannels ---------------------------------------------------------
337 
338 #define CHANNELNUMBERTIMEOUT 1000 //ms
339 
340 class cMenuChannels : public cOsdMenu {
341 private:
342  int number;
344  void Setup(void);
345  cChannel *GetChannel(int Index);
346  void Propagate(void);
347 protected:
348  eOSState Number(eKeys Key);
349  eOSState Switch(void);
350  eOSState Edit(void);
351  eOSState New(void);
352  eOSState Delete(void);
353  virtual void Move(int From, int To);
354 public:
355  cMenuChannels(void);
356  ~cMenuChannels();
357  virtual eOSState ProcessKey(eKeys Key);
358  };
359 
361 :cOsdMenu(tr("Channels"), CHNUMWIDTH)
362 {
364  number = 0;
365  Setup();
367 }
368 
370 {
372 }
373 
375 {
376  cChannel *currentChannel = GetChannel(Current());
377  if (!currentChannel)
378  currentChannel = Channels.GetByNumber(cDevice::CurrentChannel());
379  cMenuChannelItem *currentItem = NULL;
380  Clear();
381  for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
382  if (!channel->GroupSep() || cMenuChannelItem::SortMode() == cMenuChannelItem::csmNumber && *channel->Name()) {
383  cMenuChannelItem *item = new cMenuChannelItem(channel);
384  Add(item);
385  if (channel == currentChannel)
386  currentItem = item;
387  }
388  }
390  Sort();
391  SetCurrent(currentItem);
392  SetHelp(tr("Button$Edit"), tr("Button$New"), tr("Button$Delete"), tr("Button$Mark"));
393  Display();
394 }
395 
397 {
398  cMenuChannelItem *p = (cMenuChannelItem *)Get(Index);
399  return p ? (cChannel *)p->Channel() : NULL;
400 }
401 
403 {
404  Channels.ReNumber();
405  for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next())
406  ci->Set();
407  Display();
408  Channels.SetModified(true);
409 }
410 
412 {
413  if (HasSubMenu())
414  return osContinue;
415  if (numberTimer.TimedOut())
416  number = 0;
417  if (!number && Key == k0) {
419  Setup();
420  }
421  else {
422  number = number * 10 + Key - k0;
423  for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) {
424  if (!ci->Channel()->GroupSep() && ci->Channel()->Number() == number) {
425  SetCurrent(ci);
426  Display();
427  break;
428  }
429  }
431  }
432  return osContinue;
433 }
434 
436 {
437  if (HasSubMenu())
438  return osContinue;
439  cChannel *ch = GetChannel(Current());
440  if (ch)
441  return cDevice::PrimaryDevice()->SwitchChannel(ch, true) ? osEnd : osContinue;
442  return osEnd;
443 }
444 
446 {
447  if (HasSubMenu() || Count() == 0)
448  return osContinue;
449  cChannel *ch = GetChannel(Current());
450  if (ch)
451  return AddSubMenu(new cMenuEditChannel(ch));
452  return osContinue;
453 }
454 
456 {
457  if (HasSubMenu())
458  return osContinue;
459  return AddSubMenu(new cMenuEditChannel(GetChannel(Current()), true));
460 }
461 
463 {
464  if (!HasSubMenu() && Count() > 0) {
465  int CurrentChannelNr = cDevice::CurrentChannel();
466  cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
467  int Index = Current();
468  cChannel *channel = GetChannel(Current());
469  int DeletedChannel = channel->Number();
470  // Check if there is a timer using this channel:
471  if (channel->HasTimer()) {
472  Skins.Message(mtError, tr("Channel is being used by a timer!"));
473  return osContinue;
474  }
475  if (Interface->Confirm(tr("Delete channel?"))) {
476  if (CurrentChannel && channel == CurrentChannel) {
477  int n = Channels.GetNextNormal(CurrentChannel->Index());
478  if (n < 0)
479  n = Channels.GetPrevNormal(CurrentChannel->Index());
480  CurrentChannel = Channels.Get(n);
481  CurrentChannelNr = 0; // triggers channel switch below
482  }
483  Channels.Del(channel);
484  cOsdMenu::Del(Index);
485  Propagate();
486  Channels.SetModified(true);
487  isyslog("channel %d deleted", DeletedChannel);
488  if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
490  Channels.SwitchTo(CurrentChannel->Number());
491  else
492  cDevice::SetCurrentChannel(CurrentChannel);
493  }
494  }
495  }
496  return osContinue;
497 }
498 
499 void cMenuChannels::Move(int From, int To)
500 {
501  int CurrentChannelNr = cDevice::CurrentChannel();
502  cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
503  cChannel *FromChannel = GetChannel(From);
504  cChannel *ToChannel = GetChannel(To);
505  if (FromChannel && ToChannel) {
506  int FromNumber = FromChannel->Number();
507  int ToNumber = ToChannel->Number();
508  Channels.Move(FromChannel, ToChannel);
509  cOsdMenu::Move(From, To);
510  Propagate();
511  Channels.SetModified(true);
512  isyslog("channel %d moved to %d", FromNumber, ToNumber);
513  if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
515  Channels.SwitchTo(CurrentChannel->Number());
516  else
517  cDevice::SetCurrentChannel(CurrentChannel);
518  }
519  }
520 }
521 
523 {
524  eOSState state = cOsdMenu::ProcessKey(Key);
525 
526  switch (state) {
527  case osUser1: {
528  cChannel *channel = Channels.Last();
529  if (channel) {
530  Add(new cMenuChannelItem(channel), true);
531  return CloseSubMenu();
532  }
533  }
534  break;
535  default:
536  if (state == osUnknown) {
537  switch (Key) {
538  case k0 ... k9:
539  return Number(Key);
540  case kOk: return Switch();
541  case kRed: return Edit();
542  case kGreen: return New();
543  case kYellow: return Delete();
544  case kBlue: if (!HasSubMenu())
545  Mark();
546  break;
547  default: break;
548  }
549  }
550  }
551  return state;
552 }
553 
554 // --- cMenuText -------------------------------------------------------------
555 
556 cMenuText::cMenuText(const char *Title, const char *Text, eDvbFont Font)
557 :cOsdMenu(Title)
558 {
560  text = NULL;
561  font = Font;
562  SetText(Text);
563 }
564 
566 {
567  free(text);
568 }
569 
570 void cMenuText::SetText(const char *Text)
571 {
572  free(text);
573  text = Text ? strdup(Text) : NULL;
574 }
575 
577 {
579  DisplayMenu()->SetText(text, font == fontFix); //XXX define control character in text to choose the font???
580  if (text)
582 }
583 
585 {
586  switch (int(Key)) {
587  case kUp|k_Repeat:
588  case kUp:
589  case kDown|k_Repeat:
590  case kDown:
591  case kLeft|k_Repeat:
592  case kLeft:
593  case kRight|k_Repeat:
594  case kRight:
595  DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight);
596  cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft);
597  return osContinue;
598  default: break;
599  }
600 
601  eOSState state = cOsdMenu::ProcessKey(Key);
602 
603  if (state == osUnknown) {
604  switch (Key) {
605  case kOk: return osBack;
606  default: state = osContinue;
607  }
608  }
609  return state;
610 }
611 
612 // --- cMenuFolderItem -------------------------------------------------------
613 
614 class cMenuFolderItem : public cOsdItem {
615 private:
617 public:
619  cNestedItem *Folder(void) { return folder; }
620  };
621 
623 :cOsdItem(Folder->Text())
624 {
625  folder = Folder;
626  if (folder->SubItems())
627  SetText(cString::sprintf("%s...", folder->Text()));
628 }
629 
630 // --- cMenuEditFolder -------------------------------------------------------
631 
632 class cMenuEditFolder : public cOsdMenu {
633 private:
636  char name[PATH_MAX];
638  eOSState Confirm(void);
639 public:
640  cMenuEditFolder(const char *Dir, cList<cNestedItem> *List, cNestedItem *Folder = NULL);
641  cString GetFolder(void);
642  virtual eOSState ProcessKey(eKeys Key);
643  };
644 
646 :cOsdMenu(Folder ? tr("Edit folder") : tr("New folder"), 12)
647 {
649  list = List;
650  folder = Folder;
651  if (folder) {
652  strn0cpy(name, folder->Text(), sizeof(name));
653  subFolder = folder->SubItems() != NULL;
654  }
655  else {
656  *name = 0;
657  subFolder = 0;
658  cRemote::Put(kRight, true); // go right into string editing mode
659  }
660  if (!isempty(Dir)) {
661  cOsdItem *DirItem = new cOsdItem(Dir);
662  DirItem->SetSelectable(false);
663  Add(DirItem);
664  }
665  Add(new cMenuEditStrItem( tr("Name"), name, sizeof(name)));
666  Add(new cMenuEditBoolItem(tr("Sub folder"), &subFolder));
667 }
668 
670 {
671  return folder ? folder->Text() : "";
672 }
673 
675 {
676  if (!folder || strcmp(folder->Text(), name) != 0) {
677  // each name may occur only once in a folder list
678  for (cNestedItem *Folder = list->First(); Folder; Folder = list->Next(Folder)) {
679  if (strcmp(Folder->Text(), name) == 0) {
680  Skins.Message(mtError, tr("Folder name already exists!"));
681  return osContinue;
682  }
683  }
684  char *p = strpbrk(name, "\\{}#~"); // FOLDERDELIMCHAR
685  if (p) {
686  Skins.Message(mtError, cString::sprintf(tr("Folder name must not contain '%c'!"), *p));
687  return osContinue;
688  }
689  }
690  if (folder) {
691  folder->SetText(name);
693  }
694  else
696  return osEnd;
697 }
698 
700 {
701  eOSState state = cOsdMenu::ProcessKey(Key);
702 
703  if (state == osUnknown) {
704  switch (Key) {
705  case kOk: return Confirm();
706  case kRed:
707  case kGreen:
708  case kYellow:
709  case kBlue: return osContinue;
710  default: break;
711  }
712  }
713  return state;
714 }
715 
716 // --- cMenuFolder -----------------------------------------------------------
717 
718 cMenuFolder::cMenuFolder(const char *Title, cNestedItemList *NestedItemList, const char *Path)
719 :cOsdMenu(Title)
720 {
722  list = nestedItemList = NestedItemList;
723  firstFolder = NULL;
724  editing = false;
725  Set();
726  SetHelpKeys();
727  DescendPath(Path);
728 }
729 
730 cMenuFolder::cMenuFolder(const char *Title, cList<cNestedItem> *List, cNestedItemList *NestedItemList, const char *Dir, const char *Path)
731 :cOsdMenu(Title)
732 {
734  list = List;
735  nestedItemList = NestedItemList;
736  dir = Dir;
737  firstFolder = NULL;
738  editing = false;
739  Set();
740  SetHelpKeys();
741  DescendPath(Path);
742 }
743 
745 {
746  SetHelp(firstFolder ? tr("Button$Select") : NULL, tr("Button$New"), firstFolder ? tr("Button$Delete") : NULL, firstFolder ? tr("Button$Edit") : NULL);
747 }
748 
749 void cMenuFolder::Set(const char *CurrentFolder)
750 {
751  firstFolder = NULL;
752  Clear();
753  if (!isempty(dir)) {
754  cOsdItem *DirItem = new cOsdItem(dir);
755  DirItem->SetSelectable(false);
756  Add(DirItem);
757  }
758  list->Sort();
759  for (cNestedItem *Folder = list->First(); Folder; Folder = list->Next(Folder)) {
760  cOsdItem *FolderItem = new cMenuFolderItem(Folder);
761  Add(FolderItem, CurrentFolder ? strcmp(Folder->Text(), CurrentFolder) == 0 : false);
762  if (!firstFolder)
763  firstFolder = FolderItem;
764  }
765 }
766 
767 void cMenuFolder::DescendPath(const char *Path)
768 {
769  if (Path) {
770  const char *p = strchr(Path, FOLDERDELIMCHAR);
771  if (p) {
772  for (cMenuFolderItem *Folder = (cMenuFolderItem *)firstFolder; Folder; Folder = (cMenuFolderItem *)Next(Folder)) {
773  if (strncmp(Folder->Folder()->Text(), Path, p - Path) == 0) {
774  SetCurrent(Folder);
775  if (Folder->Folder()->SubItems())
776  AddSubMenu(new cMenuFolder(Title(), Folder->Folder()->SubItems(), nestedItemList, !isempty(dir) ? *cString::sprintf("%s%c%s", *dir, FOLDERDELIMCHAR, Folder->Folder()->Text()) : Folder->Folder()->Text(), p + 1));
777  break;
778  }
779  }
780  }
781  }
782 }
783 
785 {
786  if (firstFolder) {
787  cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current());
788  if (Folder) {
789  if (Folder->Folder()->SubItems())
790  return AddSubMenu(new cMenuFolder(Title(), Folder->Folder()->SubItems(), nestedItemList, !isempty(dir) ? *cString::sprintf("%s%c%s", *dir, FOLDERDELIMCHAR, Folder->Folder()->Text()) : Folder->Folder()->Text()));
791  else
792  return osEnd;
793  }
794  }
795  return osContinue;
796 }
797 
799 {
800  editing = true;
801  return AddSubMenu(new cMenuEditFolder(dir, list));
802 }
803 
805 {
806  if (!HasSubMenu() && firstFolder) {
807  cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current());
808  if (Folder && Interface->Confirm(Folder->Folder()->SubItems() ? tr("Delete folder and all sub folders?") : tr("Delete folder?"))) {
809  list->Del(Folder->Folder());
810  Del(Folder->Index());
811  firstFolder = Get(isempty(dir) ? 0 : 1);
812  Display();
813  SetHelpKeys();
814  nestedItemList->Save();
815  }
816  }
817  return osContinue;
818 }
819 
821 {
822  if (!HasSubMenu() && firstFolder) {
823  cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current());
824  if (Folder) {
825  editing = true;
826  return AddSubMenu(new cMenuEditFolder(dir, list, Folder->Folder()));
827  }
828  }
829  return osContinue;
830 }
831 
833 {
835  if (mef) {
836  Set(mef->GetFolder());
837  SetHelpKeys();
838  Display();
839  nestedItemList->Save();
840  }
841  return CloseSubMenu();
842 }
843 
845 {
846  if (firstFolder) {
847  cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current());
848  if (Folder) {
849  cMenuFolder *mf = (cMenuFolder *)SubMenu();
850  if (mf)
851  return cString::sprintf("%s%c%s", Folder->Folder()->Text(), FOLDERDELIMCHAR, *mf->GetFolder());
852  return Folder->Folder()->Text();
853  }
854  }
855  return "";
856 }
857 
859 {
860  if (!HasSubMenu())
861  editing = false;
862  eOSState state = cOsdMenu::ProcessKey(Key);
863 
864  if (state == osUnknown) {
865  switch (Key) {
866  case kOk:
867  case kRed: return Select();
868  case kGreen: return New();
869  case kYellow: return Delete();
870  case kBlue: return Edit();
871  default: state = osContinue;
872  }
873  }
874  else if (state == osEnd && HasSubMenu() && editing)
875  state = SetFolder();
876  return state;
877 }
878 
879 // --- cMenuEditTimer --------------------------------------------------------
880 
882 :cOsdMenu(tr("Edit timer"), 12)
883 {
885  file = NULL;
886  day = firstday = NULL;
887  timer = Timer;
888  addIfConfirmed = New;
889  if (timer) {
890  data = *timer;
891  if (New)
893  channel = data.Channel()->Number();
894  Add(new cMenuEditBitItem( tr("Active"), &data.flags, tfActive));
895  Add(new cMenuEditChanItem(tr("Channel"), &channel));
896  Add(day = new cMenuEditDateItem(tr("Day"), &data.day, &data.weekdays));
897  Add(new cMenuEditTimeItem(tr("Start"), &data.start));
898  Add(new cMenuEditTimeItem(tr("Stop"), &data.stop));
899  Add(new cMenuEditBitItem( tr("VPS"), &data.flags, tfVps));
900  Add(new cMenuEditIntItem( tr("Priority"), &data.priority, 0, MAXPRIORITY));
901  Add(new cMenuEditIntItem( tr("Lifetime"), &data.lifetime, 0, MAXLIFETIME));
902  Add(file = new cMenuEditStrItem( tr("File"), data.file, sizeof(data.file)));
903  SetFirstDayItem();
904  }
905  SetHelpKeys();
907 }
908 
910 {
911  if (timer && addIfConfirmed)
912  delete timer; // apparently it wasn't confirmed
914 }
915 
917 {
918  SetHelp(tr("Button$Folder"), data.weekdays ? tr("Button$Single") : tr("Button$Repeating"));
919 }
920 
922 {
923  if (!firstday && !data.IsSingleEvent()) {
924  Add(firstday = new cMenuEditDateItem(tr("First day"), &data.day));
925  Display();
926  }
927  else if (firstday && data.IsSingleEvent()) {
928  Del(firstday->Index());
929  firstday = NULL;
930  Display();
931  }
932 }
933 
935 {
936  cMenuFolder *mf = (cMenuFolder *)SubMenu();
937  if (mf) {
938  cString Folder = mf->GetFolder();
939  char *p = strrchr(data.file, FOLDERDELIMCHAR);
940  if (p)
941  p++;
942  else
943  p = data.file;
944  if (!isempty(*Folder))
945  strn0cpy(data.file, cString::sprintf("%s%c%s", *Folder, FOLDERDELIMCHAR, p), sizeof(data.file));
946  else if (p != data.file)
947  memmove(data.file, p, strlen(p) + 1);
948  SetCurrent(file);
949  Display();
950  }
951  return CloseSubMenu();
952 }
953 
955 {
956  eOSState state = cOsdMenu::ProcessKey(Key);
957 
958  if (state == osUnknown) {
959  switch (Key) {
960  case kOk: {
962  if (ch)
963  data.channel = ch;
964  else {
965  Skins.Message(mtError, tr("*** Invalid Channel ***"));
966  break;
967  }
968  if (!*data.file)
969  strcpy(data.file, data.Channel()->ShortName(true));
970  if (timer) {
971  if (memcmp(timer, &data, sizeof(data)) != 0)
972  *timer = data;
973  if (addIfConfirmed)
974  Timers.Add(timer);
976  timer->Matches();
978  isyslog("timer %s %s (%s)", *timer->ToDescr(), addIfConfirmed ? "added" : "modified", timer->HasFlags(tfActive) ? "active" : "inactive");
979  addIfConfirmed = false;
980  }
981  }
982  return osBack;
983  case kRed: return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, data.file));
984  case kGreen: if (day) {
985  day->ToggleRepeating();
986  SetCurrent(day);
987  SetFirstDayItem();
988  SetHelpKeys();
989  Display();
990  }
991  return osContinue;
992  case kYellow:
993  case kBlue: return osContinue;
994  default: break;
995  }
996  }
997  else if (state == osEnd && HasSubMenu())
998  state = SetFolder();
999  if (Key != kNone)
1000  SetFirstDayItem();
1001  return state;
1002 }
1003 
1004 // --- cMenuTimerItem --------------------------------------------------------
1005 
1006 class cMenuTimerItem : public cOsdItem {
1007 private:
1010 public:
1012  void SetDiskStatus(char DiskStatus);
1013  virtual int Compare(const cListObject &ListObject) const;
1014  virtual void Set(void);
1015  cTimer *Timer(void) { return timer; }
1016  };
1017 
1019 {
1020  timer = Timer;
1021  diskStatus = ' ';
1022  Set();
1023 }
1024 
1025 int cMenuTimerItem::Compare(const cListObject &ListObject) const
1026 {
1027  return timer->Compare(*((cMenuTimerItem *)&ListObject)->timer);
1028 }
1029 
1031 {
1032  cString day, name("");
1033  if (timer->WeekDays())
1034  day = timer->PrintDay(0, timer->WeekDays(), false);
1035  else if (timer->Day() - time(NULL) < 28 * SECSINDAY) {
1036  day = itoa(timer->GetMDay(timer->Day()));
1037  name = WeekDayName(timer->Day());
1038  }
1039  else {
1040  struct tm tm_r;
1041  time_t Day = timer->Day();
1042  localtime_r(&Day, &tm_r);
1043  char buffer[16];
1044  strftime(buffer, sizeof(buffer), "%Y%m%d", &tm_r);
1045  day = buffer;
1046  }
1047  const char *File = Setup.FoldersInTimerMenu ? NULL : strrchr(timer->File(), FOLDERDELIMCHAR);
1048  if (File && strcmp(File + 1, TIMERMACRO_TITLE) && strcmp(File + 1, TIMERMACRO_EPISODE))
1049  File++;
1050  else
1051  File = timer->File();
1052  cCharSetConv csc("ISO-8859-1", cCharSetConv::SystemCharacterTable());
1053  char diskStatusString[2] = { diskStatus, 0 };
1054  SetText(cString::sprintf("%s%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s",
1055  csc.Convert(diskStatusString),
1056  !(timer->HasFlags(tfActive)) ? ' ' : timer->FirstDay() ? '!' : timer->Recording() ? '#' : '>',
1057  timer->Channel()->Number(),
1058  *name,
1059  *name && **name ? " " : "",
1060  *day,
1061  timer->Start() / 100,
1062  timer->Start() % 100,
1063  timer->Stop() / 100,
1064  timer->Stop() % 100,
1065  File));
1066 }
1067 
1068 void cMenuTimerItem::SetDiskStatus(char DiskStatus)
1069 {
1070  diskStatus = DiskStatus;
1071  Set();
1072 }
1073 
1074 // --- cTimerEntry -----------------------------------------------------------
1075 
1076 class cTimerEntry : public cListObject {
1077 private:
1079  const cTimer *timer;
1080  time_t start;
1081 public:
1082  cTimerEntry(cMenuTimerItem *item) : item(item), timer(item->Timer()), start(timer->StartTime()) {}
1083  cTimerEntry(const cTimer *timer, time_t start) : item(NULL), timer(timer), start(start) {}
1084  virtual int Compare(const cListObject &ListObject) const;
1085  bool active(void) const { return timer->HasFlags(tfActive); }
1086  time_t startTime(void) const { return start; }
1087  int priority(void) const { return timer->Priority(); }
1088  int duration(void) const;
1089  bool repTimer(void) const { return !timer->IsSingleEvent(); }
1090  bool isDummy(void) const { return item == NULL; }
1091  const cTimer *Timer(void) const { return timer; }
1092  void SetDiskStatus(char DiskStatus);
1093  };
1094 
1095 int cTimerEntry::Compare(const cListObject &ListObject) const
1096 {
1097  cTimerEntry *entry = (cTimerEntry *)&ListObject;
1098  int r = startTime() - entry->startTime();
1099  if (r == 0)
1100  r = entry->priority() - priority();
1101  return r;
1102 }
1103 
1104 int cTimerEntry::duration(void) const
1105 {
1106  int dur = (timer->Stop() / 100 * 60 + timer->Stop() % 100) -
1107  (timer->Start() / 100 * 60 + timer->Start() % 100);
1108  if (dur < 0)
1109  dur += 24 * 60;
1110  return dur;
1111 }
1112 
1113 void cTimerEntry::SetDiskStatus(char DiskStatus)
1114 {
1115  if (item)
1116  item->SetDiskStatus(DiskStatus);
1117 }
1118 
1119 // --- cMenuTimers -----------------------------------------------------------
1120 
1121 class cMenuTimers : public cOsdMenu {
1122 private:
1123  eOSState Commands(eKeys Key = kNone);
1125  eOSState Edit(void);
1126  eOSState New(void);
1127  eOSState Delete(void);
1128  eOSState OnOff(void);
1129  eOSState Info(void);
1130  cTimer *CurrentTimer(void);
1131  void SetHelpKeys(void);
1132  void ActualiseDiskStatus(void);
1134 public:
1135  cMenuTimers(void);
1136  virtual ~cMenuTimers();
1137  virtual void Display(void);
1138  virtual eOSState ProcessKey(eKeys Key);
1139  };
1140 
1142 :cOsdMenu(tr("Timers"), 3, CHNUMWIDTH, 10, 6, 6)
1143 {
1145  helpKeys = -1;
1146  for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) {
1147  timer->SetEventFromSchedule(); // make sure the event is current
1148  Add(new cMenuTimerItem(timer));
1149  }
1150  Sort();
1151  SetCurrent(First());
1152  SetHelpKeys();
1154  actualiseDiskStatus = true;
1155 }
1156 
1158 {
1160 }
1161 
1163 {
1164  cMenuTimerItem *item = (cMenuTimerItem *)Get(Current());
1165  return item ? item->Timer() : NULL;
1166 }
1167 
1169 {
1170  int NewHelpKeys = 0;
1171  cTimer *timer = CurrentTimer();
1172  if (timer) {
1173  if (timer->Event())
1174  NewHelpKeys = 2;
1175  else
1176  NewHelpKeys = 1;
1177  }
1178  if (NewHelpKeys != helpKeys) {
1179  helpKeys = NewHelpKeys;
1180  SetHelp(helpKeys > 0 ? tr("Button$On/Off") : NULL, tr("Button$New"), helpKeys > 0 ? tr("Button$Delete") : NULL, helpKeys == 2 ? tr("Button$Info") : NULL);
1181  }
1182 }
1183 
1185 {
1186  if (HasSubMenu())
1187  return osContinue;
1188  cTimer *timer = CurrentTimer();
1189  if (timer) {
1190  timer->OnOff();
1191  timer->SetEventFromSchedule();
1192  RefreshCurrent();
1193  Display();
1194  if (timer->FirstDay())
1195  isyslog("timer %s first day set to %s", *timer->ToDescr(), *timer->PrintFirstDay());
1196  else
1197  isyslog("timer %s %sactivated", *timer->ToDescr(), timer->HasFlags(tfActive) ? "" : "de");
1198  Timers.SetModified();
1199  }
1200  return osContinue;
1201 }
1202 
1204 {
1205  if (HasSubMenu() || Count() == 0)
1206  return osContinue;
1207  isyslog("editing timer %s", *CurrentTimer()->ToDescr());
1208  return AddSubMenu(new cMenuEditTimer(CurrentTimer()));
1209 }
1210 
1212 {
1213  if (HasSubMenu())
1214  return osContinue;
1215  return AddSubMenu(new cMenuEditTimer(new cTimer, true));
1216 }
1217 
1219 {
1220  // Check if this timer is active:
1221  cTimer *ti = CurrentTimer();
1222  if (ti) {
1223  if (Interface->Confirm(tr("Delete timer?"))) {
1224  if (ti->Recording()) {
1225  if (Interface->Confirm(tr("Timer still recording - really delete?"))) {
1226  ti->Skip();
1227  cRecordControls::Process(time(NULL));
1228  }
1229  else
1230  return osContinue;
1231  }
1232  isyslog("deleting timer %s", *ti->ToDescr());
1233  Timers.Del(ti);
1235  Timers.SetModified();
1236  Display();
1237  }
1238  }
1239  return osContinue;
1240 }
1241 
1242 #define CHECK_2PTR_NULL(x_,y_) ((x_)? ((y_)? y_:""):"")
1243 
1245 {
1246  if (HasSubMenu() || Count() == 0)
1247  return osContinue;
1248  cTimer *ti = CurrentTimer();
1249  if (ti) {
1250  char *parameter = NULL;
1251  const cEvent *pEvent = ti->Event();
1252  int iRecNumber=0;
1253 
1254  if(!pEvent) {
1255  Timers.SetEvents();
1256  pEvent = ti->Event();
1257  }
1258  if(pEvent) {
1259 // create a dummy recording to get the real filename
1260  cRecording *rc_dummy = new cRecording(ti, pEvent);
1261  Recordings.Load();
1262  cRecording *rc = Recordings.GetByName(rc_dummy->FileName());
1263 
1264  delete rc_dummy;
1265  if(rc)
1266  iRecNumber=rc->Index() + 1;
1267  }
1268 //Parameter format TimerNumber 'ChannelId' Start Stop 'Titel' 'Subtitel' 'file' RecNumer
1269 // 1 2 3 4 5 6 7 8
1270  asprintf(&parameter, "%d '%s' %d %d '%s' '%s' '%s' %d", ti->Index(),
1271  *ti->Channel()->GetChannelID().ToString(),
1272  (int)ti->StartTime(),
1273  (int)ti->StopTime(),
1274  CHECK_2PTR_NULL(pEvent, pEvent->Title()),
1275  CHECK_2PTR_NULL(pEvent, pEvent->ShortText()),
1276  ti->File(),
1277  iRecNumber);
1278  isyslog("timercmd: %s", parameter);
1279  cMenuCommands *menu;
1280  eOSState state = AddSubMenu(menu = new cMenuCommands(tr("Timer commands"), &TimerCommands, parameter));
1281  free(parameter);
1282  if (Key != kNone)
1283  state = menu->ProcessKey(Key);
1284  return state;
1285  }
1286  return osContinue;
1287 }
1288 
1290 {
1291  if (HasSubMenu() || Count() == 0)
1292  return osContinue;
1293  cTimer *ti = CurrentTimer();
1294  if (ti && ti->Event())
1295  return AddSubMenu(new cMenuEvent(ti->Event()));
1296  return osContinue;
1297 }
1298 
1300 {
1301  if (!actualiseDiskStatus || !Count())
1302  return;
1303 
1304  // compute free disk space
1305  int freeMB, freeMinutes, runshortMinutes;
1306  VideoDiskSpace(&freeMB);
1307  freeMinutes = int(double(freeMB) * 1.1 / 25.75); // overestimate by 10 percent
1308  runshortMinutes = freeMinutes / 5; // 20 Percent
1309 
1310  // fill entries list
1311  cTimerEntry *entry;
1312  cList<cTimerEntry> entries;
1313  for (cOsdItem *item = First(); item; item = Next(item))
1314  entries.Add(new cTimerEntry((cMenuTimerItem *)item));
1315 
1316  // search last start time
1317  time_t last = 0;
1318  for (entry = entries.First(); entry; entry = entries.Next(entry))
1319  last = max(entry->startTime(), last);
1320 
1321  // add entries for repeating timers
1322  for (entry = entries.First(); entry; entry = entries.Next(entry))
1323  if (entry->repTimer() && !entry->isDummy())
1324  for (time_t start = cTimer::IncDay(entry->startTime(), 1);
1325  start <= last;
1326  start = cTimer::IncDay(start, 1))
1327  if (entry->Timer()->DayMatches(start))
1328  entries.Add(new cTimerEntry(entry->Timer(), start));
1329 
1330  // set the disk-status
1331  entries.Sort();
1332  for (entry = entries.First(); entry; entry = entries.Next(entry)) {
1333  char status = ' ';
1334  if (entry->active()) {
1335  freeMinutes -= entry->duration();
1336  status = freeMinutes > runshortMinutes ? '+' : freeMinutes > 0 ? 177 /* +/- */ : '-';
1337  }
1338  entry->SetDiskStatus(status);
1339 #ifdef DEBUG_TIMER_INFO
1340  dsyslog("timer-info: %c | %d | %s | %s | %3d | %+5d -> %+5d",
1341  status,
1342  entry->startTime(),
1343  entry->active() ? "aktiv " : "n.akt.",
1344  entry->repTimer() ? entry->isDummy() ? " dummy " : "mehrmalig" : "einmalig ",
1345  entry->duration(),
1346  entry->active() ? freeMinutes + entry->duration() : freeMinutes,
1347  freeMinutes);
1348 #endif
1349  }
1350 
1351  actualiseDiskStatus = false;
1352 }
1353 
1355 {
1358 }
1359 
1361 {
1362  int TimerNumber = HasSubMenu() ? Count() : -1;
1363  eOSState state = cOsdMenu::ProcessKey(Key);
1364 
1365  if (state == osUnknown) {
1366  switch (Key) {
1367  case kOk: return Edit();
1368  case kRed: actualiseDiskStatus = true;
1369  state = OnOff(); break; // must go through SetHelpKeys()!
1370  case kGreen: return New();
1371  case kYellow: actualiseDiskStatus = true;
1372  state = Delete(); break;
1373  case kInfo:
1374  case kBlue: return Info();
1375  break;
1376  case k1...k9: return Commands(Key);
1377  case k0: return (TimerCommands.Count()? Commands():osContinue);
1378  default: break;
1379  }
1380  }
1381  if (TimerNumber >= 0 && !HasSubMenu()) {
1382  if (Timers.Get(TimerNumber)) // a newly created timer was confirmed with Ok
1383  Add(new cMenuTimerItem(Timers.Get(TimerNumber)), true);
1384  Sort();
1385  actualiseDiskStatus = true;
1386  Display();
1387  }
1388  if (Key != kNone)
1389  SetHelpKeys();
1390  return state;
1391 }
1392 
1393 // --- cMenuEvent ------------------------------------------------------------
1394 
1395 cMenuEvent::cMenuEvent(const cEvent *Event, bool CanSwitch, bool Buttons)
1396 :cOsdMenu(tr("Event"))
1397 {
1399  event = Event;
1400  if (event) {
1401  cChannel *channel = Channels.GetByChannelID(event->ChannelID(), true);
1402  if (channel) {
1403  SetTitle(channel->Name());
1404  int TimerMatch = tmNone;
1405  Timers.GetMatch(event, &TimerMatch);
1406  if (Buttons)
1407  SetHelp(TimerMatch == tmFull ? tr("Button$Timer") : tr("Button$Record"), NULL, NULL, CanSwitch ? tr("Button$Switch") : NULL);
1408  }
1409  }
1410 }
1411 
1413 {
1416  if (event->Description())
1418 }
1419 
1421 {
1422  switch (int(Key)) {
1423  case kUp|k_Repeat:
1424  case kUp:
1425  case kDown|k_Repeat:
1426  case kDown:
1427  case kLeft|k_Repeat:
1428  case kLeft:
1429  case kRight|k_Repeat:
1430  case kRight:
1431  DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight);
1432  cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft);
1433  return osContinue;
1434  case kInfo: return osBack;
1435  default: break;
1436  }
1437 
1438  eOSState state = cOsdMenu::ProcessKey(Key);
1439 
1440  if (state == osUnknown) {
1441  switch (Key) {
1442  case kGreen:
1443  case kYellow: return osContinue;
1444  case kOk: return osBack;
1445  default: break;
1446  }
1447  }
1448  return state;
1449 }
1450 
1451 // --- cMenuScheduleItem -----------------------------------------------------
1452 
1453 class cMenuScheduleItem : public cOsdItem {
1454 public:
1455  enum eScheduleSortMode { ssmAllThis, ssmThisThis, ssmThisAll, ssmAllAll }; // "which event(s) on which channel(s)"
1456 private:
1458 public:
1459  const cEvent *event;
1461  bool withDate;
1463  cMenuScheduleItem(const cEvent *Event, cChannel *Channel = NULL, bool WithDate = false);
1466  static eScheduleSortMode SortMode(void) { return sortMode; }
1467  virtual int Compare(const cListObject &ListObject) const;
1468  bool Update(bool Force = false);
1469  };
1470 
1472 
1473 cMenuScheduleItem::cMenuScheduleItem(const cEvent *Event, cChannel *Channel, bool WithDate)
1474 {
1475  event = Event;
1476  channel = Channel;
1477  withDate = WithDate;
1478  timerMatch = tmNone;
1479  Update(true);
1480 }
1481 
1482 int cMenuScheduleItem::Compare(const cListObject &ListObject) const
1483 {
1484  cMenuScheduleItem *p = (cMenuScheduleItem *)&ListObject;
1485  int r = -1;
1486  if (sortMode != ssmAllThis)
1487  r = strcoll(event->Title(), p->event->Title());
1488  if (sortMode == ssmAllThis || r == 0)
1489  r = event->StartTime() - p->event->StartTime();
1490  return r;
1491 }
1492 
1493 static const char *TimerMatchChars = " tT";
1494 
1496 {
1497  bool result = false;
1498  int OldTimerMatch = timerMatch;
1500  if (Force || timerMatch != OldTimerMatch) {
1501  cString buffer;
1502  char t = TimerMatchChars[timerMatch];
1503  char v = event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' ';
1504  char r = event->SeenWithin(30) && event->IsRunning() ? '*' : ' ';
1505  const char *csn = channel ? channel->ShortName(true) : NULL;
1506  cString eds = event->GetDateString();
1507  if (channel && withDate)
1508  buffer = cString::sprintf("%d\t%.*s\t%.*s\t%s\t%c%c%c\t%s", channel->Number(), Utf8SymChars(csn, 999), csn, Utf8SymChars(eds, 6), *eds, *event->GetTimeString(), t, v, r, event->Title());
1509  else if (channel)
1510  buffer = cString::sprintf("%d\t%.*s\t%s\t%c%c%c\t%s", channel->Number(), Utf8SymChars(csn, 999), csn, *event->GetTimeString(), t, v, r, event->Title());
1511  else
1512  buffer = cString::sprintf("%.*s\t%s\t%c%c%c\t%s", Utf8SymChars(eds, 6), *eds, *event->GetTimeString(), t, v, r, event->Title());
1513  SetText(buffer);
1514  result = true;
1515  }
1516  return result;
1517 }
1518 
1519 // --- cMenuWhatsOn ----------------------------------------------------------
1520 
1521 class cMenuWhatsOn : public cOsdMenu {
1522 private:
1523  bool now;
1526  eOSState Record(void);
1527  eOSState Switch(void);
1528  static int currentChannel;
1529  static const cEvent *scheduleEvent;
1530  bool Update(void);
1531  void SetHelpKeys(void);
1532 public:
1533  cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr);
1534  static int CurrentChannel(void) { return currentChannel; }
1535  static void SetCurrentChannel(int ChannelNr) { currentChannel = ChannelNr; }
1536  static const cEvent *ScheduleEvent(void);
1537  virtual eOSState ProcessKey(eKeys Key);
1538  };
1539 
1541 const cEvent *cMenuWhatsOn::scheduleEvent = NULL;
1542 
1543 cMenuWhatsOn::cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr)
1544 :cOsdMenu(Now ? tr("What's on now?") : tr("What's on next?"), CHNUMWIDTH, CHNAMWIDTH, 6, 4)
1545 {
1547  now = Now;
1548  helpKeys = -1;
1549  timerState = 0;
1551  for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
1552  if (!Channel->GroupSep()) {
1553  const cSchedule *Schedule = Schedules->GetSchedule(Channel);
1554  if (Schedule) {
1555  const cEvent *Event = Now ? Schedule->GetPresentEvent() : Schedule->GetFollowingEvent();
1556  if (Event)
1557  Add(new cMenuScheduleItem(Event, Channel), Channel->Number() == CurrentChannelNr);
1558  }
1559  }
1560  }
1561  currentChannel = CurrentChannelNr;
1562  Display();
1563  SetHelpKeys();
1564 }
1565 
1567 {
1568  bool result = false;
1569  if (Timers.Modified(timerState)) {
1570  for (cOsdItem *item = First(); item; item = Next(item)) {
1571  if (((cMenuScheduleItem *)item)->Update())
1572  result = true;
1573  }
1574  }
1575  return result;
1576 }
1577 
1579 {
1581  int NewHelpKeys = 0;
1582  if (item) {
1583  if (item->timerMatch == tmFull)
1584  NewHelpKeys = 2;
1585  else
1586  NewHelpKeys = 1;
1587  }
1588  if (NewHelpKeys != helpKeys) {
1589  const char *Red[] = { NULL, tr("Button$Record"), tr("Button$Timer") };
1590  SetHelp(Red[NewHelpKeys], now ? tr("Button$Next") : tr("Button$Now"), tr("Button$Schedule"), tr("Button$Switch"));
1591  helpKeys = NewHelpKeys;
1592  }
1593 }
1594 
1596 {
1597  const cEvent *ei = scheduleEvent;
1598  scheduleEvent = NULL;
1599  return ei;
1600 }
1601 
1603 {
1605  if (item) {
1606  cChannel *channel = Channels.GetByChannelID(item->event->ChannelID(), true);
1607  if (channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true))
1608  return osEnd;
1609  }
1610  Skins.Message(mtError, tr("Can't switch channel!"));
1611  return osContinue;
1612 }
1613 
1615 {
1617  if (item) {
1618  if (item->timerMatch == tmFull) {
1619  int tm = tmNone;
1620  cTimer *timer = Timers.GetMatch(item->event, &tm);
1621  if (timer)
1622  return AddSubMenu(new cMenuEditTimer(timer));
1623  }
1624  cTimer *timer = new cTimer(item->event);
1625  cTimer *t = Timers.GetTimer(timer);
1626  if (t) {
1627  delete timer;
1628  timer = t;
1629  return AddSubMenu(new cMenuEditTimer(timer));
1630  }
1631  else {
1632  Timers.Add(timer);
1633  Timers.SetModified();
1634  isyslog("timer %s added (active)", *timer->ToDescr());
1635  if (timer->Matches(0, false, NEWTIMERLIMIT))
1636  return AddSubMenu(new cMenuEditTimer(timer));
1637  if (HasSubMenu())
1638  CloseSubMenu();
1639  if (Update())
1640  Display();
1641  SetHelpKeys();
1642  }
1643  }
1644  return osContinue;
1645 }
1646 
1648 {
1649  bool HadSubMenu = HasSubMenu();
1650  eOSState state = cOsdMenu::ProcessKey(Key);
1651 
1652  if (state == osUnknown) {
1653  switch (Key) {
1654  case kRecord:
1655  case kRed: return Record();
1656  case kYellow: state = osBack;
1657  // continue with kGreen
1658  case kGreen: {
1660  if (mi) {
1661  scheduleEvent = mi->event;
1662  currentChannel = mi->channel->Number();
1663  }
1664  }
1665  break;
1666  case kBlue: return Switch();
1667  case kInfo:
1668  case kOk: if (Count())
1669  return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->event, true, true));
1670  break;
1671  default: break;
1672  }
1673  }
1674  else if (!HasSubMenu()) {
1675  if (HadSubMenu && Update())
1676  Display();
1677  if (Key != kNone)
1678  SetHelpKeys();
1679  }
1680  return state;
1681 }
1682 
1683 // --- cMenuSchedule ---------------------------------------------------------
1684 
1685 class cMenuSchedule : public cOsdMenu {
1686 private:
1689  bool now, next;
1693  eOSState Number(void);
1694  eOSState Record(void);
1695  eOSState Switch(void);
1696  void PrepareScheduleAllThis(const cEvent *Event, const cChannel *Channel);
1697  void PrepareScheduleThisThis(const cEvent *Event, const cChannel *Channel);
1698  void PrepareScheduleThisAll(const cEvent *Event, const cChannel *Channel);
1699  void PrepareScheduleAllAll(const cEvent *Event, const cChannel *Channel);
1700  bool Update(void);
1701  void SetHelpKeys(void);
1702 public:
1703  cMenuSchedule(void);
1704  virtual ~cMenuSchedule();
1705  virtual eOSState ProcessKey(eKeys Key);
1706  };
1707 
1709 :cOsdMenu("")
1710 {
1712  now = next = false;
1713  otherChannel = 0;
1714  helpKeys = -1;
1715  timerState = 0;
1719  if (channel) {
1722  PrepareScheduleAllThis(NULL, channel);
1723  SetHelpKeys();
1724  }
1725 }
1726 
1728 {
1729  cMenuWhatsOn::ScheduleEvent(); // makes sure any posted data is cleared
1730 }
1731 
1732 void cMenuSchedule::PrepareScheduleAllThis(const cEvent *Event, const cChannel *Channel)
1733 {
1734  Clear();
1735  SetCols(7, 6, 4);
1736  SetTitle(cString::sprintf(tr("Schedule - %s"), Channel->Name()));
1737  if (schedules && Channel) {
1738  const cSchedule *Schedule = schedules->GetSchedule(Channel);
1739  if (Schedule) {
1740  const cEvent *PresentEvent = Event ? Event : Schedule->GetPresentEvent();
1741  time_t now = time(NULL) - Setup.EPGLinger * 60;
1742  for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
1743  if (ev->EndTime() > now || ev == PresentEvent)
1744  Add(new cMenuScheduleItem(ev), ev == PresentEvent);
1745  }
1746  }
1747  }
1748 }
1749 
1750 void cMenuSchedule::PrepareScheduleThisThis(const cEvent *Event, const cChannel *Channel)
1751 {
1752  Clear();
1753  SetCols(7, 6, 4);
1754  SetTitle(cString::sprintf(tr("This event - %s"), Channel->Name()));
1755  if (schedules && Channel && Event) {
1756  const cSchedule *Schedule = schedules->GetSchedule(Channel);
1757  if (Schedule) {
1758  time_t now = time(NULL) - Setup.EPGLinger * 60;
1759  for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
1760  if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title()))
1761  Add(new cMenuScheduleItem(ev), ev == Event);
1762  }
1763  }
1764  }
1765 }
1766 
1767 void cMenuSchedule::PrepareScheduleThisAll(const cEvent *Event, const cChannel *Channel)
1768 {
1769  Clear();
1770  SetCols(CHNUMWIDTH, CHNAMWIDTH, 7, 6, 4);
1771  SetTitle(tr("This event - all channels"));
1772  if (schedules && Event) {
1773  for (cChannel *ch = Channels.First(); ch; ch = Channels.Next(ch)) {
1774  const cSchedule *Schedule = schedules->GetSchedule(ch);
1775  if (Schedule) {
1776  time_t now = time(NULL) - Setup.EPGLinger * 60;
1777  for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
1778  if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title()))
1779  Add(new cMenuScheduleItem(ev, ch, true), ev == Event && ch == Channel);
1780  }
1781  }
1782  }
1783  }
1784 }
1785 
1786 void cMenuSchedule::PrepareScheduleAllAll(const cEvent *Event, const cChannel *Channel)
1787 {
1788  Clear();
1789  SetCols(CHNUMWIDTH, CHNAMWIDTH, 7, 6, 4);
1790  SetTitle(tr("All events - all channels"));
1791  if (schedules) {
1792  for (cChannel *ch = Channels.First(); ch; ch = Channels.Next(ch)) {
1793  const cSchedule *Schedule = schedules->GetSchedule(ch);
1794  if (Schedule) {
1795  time_t now = time(NULL) - Setup.EPGLinger * 60;
1796  for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
1797  if (ev->EndTime() > now || ev == Event)
1798  Add(new cMenuScheduleItem(ev, ch, true), ev == Event && ch == Channel);
1799  }
1800  }
1801  }
1802  }
1803 }
1804 
1806 {
1807  bool result = false;
1808  if (Timers.Modified(timerState)) {
1809  for (cOsdItem *item = First(); item; item = Next(item)) {
1810  if (((cMenuScheduleItem *)item)->Update())
1811  result = true;
1812  }
1813  }
1814  return result;
1815 }
1816 
1818 {
1820  int NewHelpKeys = 0;
1821  if (item) {
1822  if (item->timerMatch == tmFull)
1823  NewHelpKeys = 2;
1824  else
1825  NewHelpKeys = 1;
1826  }
1827  if (NewHelpKeys != helpKeys) {
1828  const char *Red[] = { NULL, tr("Button$Record"), tr("Button$Timer") };
1829  SetHelp(Red[NewHelpKeys], tr("Button$Now"), tr("Button$Next"));
1830  helpKeys = NewHelpKeys;
1831  }
1832 }
1833 
1835 {
1837  cMenuScheduleItem *CurrentItem = (cMenuScheduleItem *)Get(Current());
1838  const cChannel *Channel = NULL;
1839  const cEvent *Event = NULL;
1840  if (CurrentItem) {
1841  Event = CurrentItem->event;
1842  Channel = Channels.GetByChannelID(Event->ChannelID(), true);
1843  }
1844  else
1846  switch (cMenuScheduleItem::SortMode()) {
1847  case cMenuScheduleItem::ssmAllThis: PrepareScheduleAllThis(Event, Channel); break;
1848  case cMenuScheduleItem::ssmThisThis: PrepareScheduleThisThis(Event, Channel); break;
1849  case cMenuScheduleItem::ssmThisAll: PrepareScheduleThisAll(Event, Channel); break;
1850  case cMenuScheduleItem::ssmAllAll: PrepareScheduleAllAll(Event, Channel); break;
1851  default: esyslog("ERROR: unknown SortMode %d (%s %d)", cMenuScheduleItem::SortMode(), __FUNCTION__, __LINE__);
1852  }
1853  CurrentItem = (cMenuScheduleItem *)Get(Current());
1854  Sort();
1855  SetCurrent(CurrentItem);
1856  Display();
1857  return osContinue;
1858 }
1859 
1861 {
1863  if (item) {
1864  if (item->timerMatch == tmFull) {
1865  int tm = tmNone;
1866  cTimer *timer = Timers.GetMatch(item->event, &tm);
1867  if (timer)
1868  return AddSubMenu(new cMenuEditTimer(timer));
1869  }
1870  cTimer *timer = new cTimer(item->event);
1871  cTimer *t = Timers.GetTimer(timer);
1872  if (t) {
1873  delete timer;
1874  timer = t;
1875  return AddSubMenu(new cMenuEditTimer(timer));
1876  }
1877  else {
1878  Timers.Add(timer);
1879  Timers.SetModified();
1880  isyslog("timer %s added (active)", *timer->ToDescr());
1881  if (timer->Matches(0, false, NEWTIMERLIMIT))
1882  return AddSubMenu(new cMenuEditTimer(timer));
1883  if (HasSubMenu())
1884  CloseSubMenu();
1885  if (Update())
1886  Display();
1887  SetHelpKeys();
1888  }
1889  }
1890  return osContinue;
1891 }
1892 
1894 {
1895  if (otherChannel) {
1897  return osEnd;
1898  }
1899  Skins.Message(mtError, tr("Can't switch channel!"));
1900  return osContinue;
1901 }
1902 
1904 {
1905  bool HadSubMenu = HasSubMenu();
1906  eOSState state = cOsdMenu::ProcessKey(Key);
1907 
1908  if (state == osUnknown) {
1909  switch (Key) {
1910  case k0: return Number();
1911  case kRecord:
1912  case kRed: return Record();
1913  case kGreen: if (schedules) {
1914  if (!now && !next) {
1915  int ChannelNr = 0;
1916  if (Count()) {
1917  cChannel *channel = Channels.GetByChannelID(((cMenuScheduleItem *)Get(Current()))->event->ChannelID(), true);
1918  if (channel)
1919  ChannelNr = channel->Number();
1920  }
1921  now = true;
1922  return AddSubMenu(new cMenuWhatsOn(schedules, true, ChannelNr));
1923  }
1924  now = !now;
1925  next = !next;
1927  }
1928  case kYellow: if (schedules)
1930  break;
1931  case kBlue: if (Count() && otherChannel)
1932  return Switch();
1933  break;
1934  case kInfo:
1935  case kOk: if (Count())
1936  return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->event, otherChannel, true));
1937  break;
1938  default: break;
1939  }
1940  }
1941  else if (!HasSubMenu()) {
1942  now = next = false;
1943  const cEvent *ei = cMenuWhatsOn::ScheduleEvent();
1944  if (ei) {
1945  cChannel *channel = Channels.GetByChannelID(ei->ChannelID(), true);
1946  if (channel) {
1948  PrepareScheduleAllThis(NULL, channel);
1949  if (channel->Number() != cDevice::CurrentChannel()) {
1950  otherChannel = channel->Number();
1951  SetHelp(Count() ? tr("Button$Record") : NULL, tr("Button$Now"), tr("Button$Next"), tr("Button$Switch"));
1952  }
1953  Display();
1954  }
1955  }
1956  else if (HadSubMenu && Update())
1957  Display();
1958  if (Key != kNone)
1959  SetHelpKeys();
1960  }
1961  return state;
1962 }
1963 
1964 // --- cMenuCommands ---------------------------------------------------------
1965 
1966 cMenuCommands::cMenuCommands(const char *Title, cList<cNestedItem> *Commands, const char *Parameters)
1967 :cOsdMenu(Title)
1968 {
1970  result = NULL;
1971  SetHasHotkeys();
1972  commands = Commands;
1973  parameters = Parameters;
1974  for (cNestedItem *Command = commands->First(); Command; Command = commands->Next(Command)) {
1975  const char *s = Command->Text();
1976  if (Command->SubItems())
1977  Add(new cOsdItem(hk(cString::sprintf("%s...", s))));
1978  else if (Parse(s))
1979  Add(new cOsdItem(hk(title)));
1980  }
1981 }
1982 
1984 {
1985  free(result);
1986 }
1987 
1988 bool cMenuCommands::Parse(const char *s)
1989 {
1990  const char *p = strchr(s, ':');
1991  if (p) {
1992  int l = p - s;
1993  if (l > 0) {
1994  char t[l + 1];
1995  stripspace(strn0cpy(t, s, l + 1));
1996  l = strlen(t);
1997  if (l > 1 && t[l - 1] == '?') {
1998  t[l - 1] = 0;
1999  confirm = true;
2000  }
2001  else
2002  confirm = false;
2003  title = t;
2004  command = skipspace(p + 1);
2005  return true;
2006  }
2007  }
2008  return false;
2009 }
2010 
2012 {
2013  cNestedItem *Command = commands->Get(Current());
2014  if (Command) {
2015  if (Command->SubItems())
2016  return AddSubMenu(new cMenuCommands(Title(), Command->SubItems(), parameters));
2017  if (Parse(Command->Text())) {
2018  if (!confirm || Interface->Confirm(cString::sprintf("%s?", *title))) {
2020  free(result);
2021  result = NULL;
2022  cString cmdbuf;
2023  if (!isempty(parameters))
2024  cmdbuf = cString::sprintf("%s %s", *command, *parameters);
2025  const char *cmd = *cmdbuf ? *cmdbuf : *command;
2026  dsyslog("executing command '%s'", cmd);
2027  cPipe p;
2028  if (p.Open(cmd, "r")) {
2029  int l = 0;
2030  int c;
2031  while ((c = fgetc(p)) != EOF) {
2032  if (l % 20 == 0) {
2033  if (char *NewBuffer = (char *)realloc(result, l + 21))
2034  result = NewBuffer;
2035  else {
2036  esyslog("ERROR: out of memory");
2037  break;
2038  }
2039  }
2040  result[l++] = char(c);
2041  }
2042  if (result)
2043  result[l] = 0;
2044  p.Close();
2045  }
2046  else
2047  esyslog("ERROR: can't open pipe for command '%s'", cmd);
2048  Skins.Message(mtStatus, NULL);
2049  if (result)
2050  return AddSubMenu(new cMenuText(title, result, fontFix));
2051  return osEnd;
2052  }
2053  }
2054  }
2055  return osContinue;
2056 }
2057 
2059 {
2060  eOSState state = cOsdMenu::ProcessKey(Key);
2061 
2062  if (state == osUnknown) {
2063  switch (Key) {
2064  case kRed:
2065  case kGreen:
2066  case kYellow:
2067  case kBlue: return osContinue;
2068  case kOk: return Execute();
2069  default: break;
2070  }
2071  }
2072  return state;
2073 }
2074 
2075 // --- cMenuCam --------------------------------------------------------------
2076 
2077 static bool CamMenuIsOpen = false;
2078 
2079 class cMenuCam : public cOsdMenu {
2080 private:
2084  char *input;
2085  int offset;
2087  void GenerateTitle(const char *s = NULL);
2088  void QueryCam(void);
2089  void AddMultiLineItem(const char *s);
2090  void Set(void);
2091  eOSState Select(void);
2092 public:
2093  cMenuCam(cCamSlot *CamSlot);
2094  virtual ~cMenuCam();
2095  virtual eOSState ProcessKey(eKeys Key);
2096  };
2097 
2099 :cOsdMenu("", 1) // tab necessary for enquiry!
2100 {
2102  camSlot = CamSlot;
2103  ciMenu = NULL;
2104  ciEnquiry = NULL;
2105  input = NULL;
2106  offset = 0;
2107  lastCamExchange = time(NULL);
2108  SetNeedsFastResponse(true);
2109  QueryCam();
2110  CamMenuIsOpen = true;
2111 }
2112 
2114 {
2115  if (ciMenu)
2116  ciMenu->Abort();
2117  delete ciMenu;
2118  if (ciEnquiry)
2119  ciEnquiry->Abort();
2120  delete ciEnquiry;
2121  free(input);
2122  CamMenuIsOpen = false;
2123 }
2124 
2125 void cMenuCam::GenerateTitle(const char *s)
2126 {
2127  SetTitle(cString::sprintf("CAM %d - %s", camSlot->SlotNumber(), (s && *s) ? s : camSlot->GetCamName()));
2128 }
2129 
2131 {
2132  delete ciMenu;
2133  ciMenu = NULL;
2134  delete ciEnquiry;
2135  ciEnquiry = NULL;
2136  if (camSlot->HasUserIO()) {
2137  ciMenu = camSlot->GetMenu();
2139  }
2140  Set();
2141 }
2142 
2143 void cMenuCam::Set(void)
2144 {
2145  if (ciMenu) {
2146  Clear();
2147  free(input);
2148  input = NULL;
2149  dsyslog("CAM %d: Menu ------------------", camSlot->SlotNumber());
2150  offset = 0;
2153  dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->TitleText());
2154  if (*ciMenu->SubTitleText()) {
2155  dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->SubTitleText());
2157  offset = Count();
2158  }
2159  for (int i = 0; i < ciMenu->NumEntries(); i++) {
2161  dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->Entry(i));
2162  }
2163  if (*ciMenu->BottomText()) {
2165  dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->BottomText());
2166  }
2168  }
2169  else if (ciEnquiry) {
2170  Clear();
2171  int Length = ciEnquiry->ExpectedLength();
2172  free(input);
2173  input = MALLOC(char, Length + 1);
2174  *input = 0;
2175  GenerateTitle();
2176  Add(new cOsdItem(ciEnquiry->Text(), osUnknown, false));
2177  Add(new cOsdItem("", osUnknown, false));
2178  Add(new cMenuEditNumItem("", input, Length, ciEnquiry->Blind()));
2179  }
2180  Display();
2181 }
2182 
2183 void cMenuCam::AddMultiLineItem(const char *s)
2184 {
2185  while (s && *s) {
2186  const char *p = strchr(s, '\n');
2187  int l = p ? p - s : strlen(s);
2188  cOsdItem *item = new cOsdItem;
2189  item->SetSelectable(false);
2190  item->SetText(strndup(s, l), false);
2191  Add(item);
2192  s = p ? p + 1 : p;
2193  }
2194 }
2195 
2197 {
2198  if (ciMenu) {
2199  if (ciMenu->Selectable()) {
2200  ciMenu->Select(Current() - offset);
2201  dsyslog("CAM %d: select %d", camSlot->SlotNumber(), Current() - offset);
2202  }
2203  else
2204  ciMenu->Cancel();
2205  }
2206  else if (ciEnquiry) {
2207  if (ciEnquiry->ExpectedLength() < 0xFF && int(strlen(input)) != ciEnquiry->ExpectedLength()) {
2208  char buffer[64];
2209  snprintf(buffer, sizeof(buffer), tr("Please enter %d digits!"), ciEnquiry->ExpectedLength());
2210  Skins.Message(mtError, buffer);
2211  return osContinue;
2212  }
2213  ciEnquiry->Reply(input);
2214  dsyslog("CAM %d: entered '%s'", camSlot->SlotNumber(), ciEnquiry->Blind() ? "****" : input);
2215  }
2216  QueryCam();
2217  return osContinue;
2218 }
2219 
2221 {
2222  if (!camSlot->HasMMI())
2223  return osBack;
2224 
2225  eOSState state = cOsdMenu::ProcessKey(Key);
2226 
2227  if (ciMenu || ciEnquiry) {
2228  lastCamExchange = time(NULL);
2229  if (state == osUnknown) {
2230  switch (Key) {
2231  case kOk: return Select();
2232  default: break;
2233  }
2234  }
2235  else if (state == osBack) {
2236  if (ciMenu)
2237  ciMenu->Cancel();
2238  if (ciEnquiry)
2239  ciEnquiry->Cancel();
2240  QueryCam();
2241  return osContinue;
2242  }
2243  if (ciMenu && ciMenu->HasUpdate()) {
2244  QueryCam();
2245  return osContinue;
2246  }
2247  }
2248  else if (time(NULL) - lastCamExchange < CAMRESPONSETIMEOUT)
2249  QueryCam();
2250  else {
2251  Skins.Message(mtError, tr("CAM not responding!"));
2252  return osBack;
2253  }
2254  return state;
2255 }
2256 
2257 // --- CamControl ------------------------------------------------------------
2258 
2260 {
2261  for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
2262  if (CamSlot->HasUserIO())
2263  return new cMenuCam(CamSlot);
2264  }
2265  return NULL;
2266 }
2267 
2268 bool CamMenuActive(void)
2269 {
2270  return CamMenuIsOpen;
2271 }
2272 
2273 // --- cMenuRecording --------------------------------------------------------
2274 
2275 class cMenuRecording : public cOsdMenu {
2276 private:
2279 public:
2280  cMenuRecording(const cRecording *Recording, bool WithButtons = false);
2281  virtual void Display(void);
2282  virtual eOSState ProcessKey(eKeys Key);
2283 };
2284 
2285 cMenuRecording::cMenuRecording(const cRecording *Recording, bool WithButtons)
2286 :cOsdMenu(tr("Recording info"))
2287 {
2289  recording = Recording;
2290  withButtons = WithButtons;
2291  if (withButtons)
2292  SetHelp(tr("Button$Play"), tr("Button$Rewind"));
2293 }
2294 
2296 {
2299  if (recording->Info()->Description())
2301 }
2302 
2304 {
2305  switch (int(Key)) {
2306  case kUp|k_Repeat:
2307  case kUp:
2308  case kDown|k_Repeat:
2309  case kDown:
2310  case kLeft|k_Repeat:
2311  case kLeft:
2312  case kRight|k_Repeat:
2313  case kRight:
2314  DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight);
2315  cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft);
2316  return osContinue;
2317  case kInfo: return osBack;
2318  default: break;
2319  }
2320 
2321  eOSState state = cOsdMenu::ProcessKey(Key);
2322 
2323  if (state == osUnknown) {
2324  switch (Key) {
2325  case kRed: if (withButtons)
2326  Key = kOk; // will play the recording, even if recording commands are defined
2327  case kGreen: if (!withButtons)
2328  break;
2329  cRemote::Put(Key, true);
2330  // continue with osBack to close the info menu and process the key
2331  case kOk: return osBack;
2332  default: break;
2333  }
2334  }
2335  return state;
2336 }
2337 
2338 // --- cMenuRecordingItem ----------------------------------------------------
2339 
2341 private:
2342  char *fileName;
2343  char *name;
2345 public:
2346  cMenuRecordingItem(cRecording *Recording, int Level);
2348  void IncrementCounter(bool New);
2349  const char *Name(void) { return name; }
2350  const char *FileName(void) { return fileName; }
2351  bool IsDirectory(void) { return name != NULL; }
2352  };
2353 
2355 {
2356  fileName = strdup(Recording->FileName());
2357  name = NULL;
2358  totalEntries = newEntries = 0;
2359  SetText(Recording->Title('\t', true, Level));
2360  if (*Text() == '\t')
2361  name = strdup(Text() + 2); // 'Text() + 2' to skip the two '\t'
2362 }
2363 
2365 {
2366  free(fileName);
2367  free(name);
2368 }
2369 
2371 {
2372  totalEntries++;
2373  if (New)
2374  newEntries++;
2375  SetText(cString::sprintf("%d\t\t%d\t%s", totalEntries, newEntries, name));
2376 }
2377 
2378 // --- cMenuEditRecording ----------------------------------------------------
2379 
2381 private:
2387  void SetHelpKeys(void);
2388  eOSState SetFolder(void);
2389 public:
2390  cMenuEditRecording(cRecording *Recording);
2391  virtual eOSState ProcessKey(eKeys Key);
2392 };
2393 
2395 :cOsdMenu(tr("Edit recording"), 14)
2396 {
2397  cMarks marks;
2398 
2399  file = NULL;
2400  recording = Recording;
2401 
2402  if (recording) {
2403  Utf8Strn0Cpy(name, recording->Name(), sizeof(name));
2404  Add(file = new cMenuEditStrItem(tr("File"), name, sizeof(name)));
2405 
2406  Add(new cOsdItem("", osUnknown, false));
2407 
2408  Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Date"), *DayDateTime(recording->Start())), osUnknown, false));
2409 
2410  cChannel *channel = Channels.GetByChannelID(((cRecordingInfo *)recording->Info())->ChannelID());
2411  if (channel)
2412  Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Channel"), *ChannelString(channel, 0)), osUnknown, false));
2413 
2414  int recLen = recording->LengthInSeconds();
2415  if (recLen >= 0)
2416  Add(new cOsdItem(cString::sprintf("%s:\t%d:%02d:%02d", tr("Length"), recLen / 3600, recLen / 60 % 60, recLen % 60), osUnknown, false));
2417  else
2418  recLen = 0;
2419 
2420  int dirSize = DirSizeMB(recording->FileName());
2421  cString bitRate = recLen ? cString::sprintf(" (%.2f MBit/s)", 8.0 * dirSize / recLen) : cString("");
2422  Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Format"), recording->IsPesRecording() ? tr("PES") : tr("TS")), osUnknown, false));
2423  Add(new cOsdItem((dirSize > 9999) ? cString::sprintf("%s:\t%.2f GB%s", tr("Size"), dirSize / 1024.0, *bitRate) : cString::sprintf("%s:\t%d MB%s", tr("Size"), dirSize, *bitRate), osUnknown, false));
2424 
2425  Add(new cOsdItem("", osUnknown, false));
2426 
2428  marksItem = new cOsdItem(tr("Delete marks information?"), osUser1, isMarks);
2429  Add(marksItem);
2430 
2432  isResume = (ResumeFile.Read() != -1);
2433  resumeItem = new cOsdItem(tr("Delete resume information?"), osUser2, isResume);
2434  Add(resumeItem);
2435  }
2436 
2437  SetHelpKeys();
2438 }
2439 
2441 {
2442  SetHelp(tr("Button$Folder"), tr("Button$Cut"), tr("Button$Copy"), tr("Button$Rename/Move"));
2443 }
2444 
2446 {
2447  cMenuFolder *mf = (cMenuFolder *)SubMenu();
2448  if (mf) {
2449  cString Folder = mf->GetFolder();
2450  char *p = strrchr(name, FOLDERDELIMCHAR);
2451  if (p)
2452  p++;
2453  else
2454  p = name;
2455  if (!isempty(*Folder))
2456  strn0cpy(name, cString::sprintf("%s%c%s", *Folder, FOLDERDELIMCHAR, p), sizeof(name));
2457  else if (p != name)
2458  memmove(name, p, strlen(p) + 1);
2459  SetCurrent(file);
2460  Display();
2461  }
2462  return CloseSubMenu();
2463 }
2464 
2466 {
2467  eOSState state = cOsdMenu::ProcessKey(Key);
2468 
2469  if (state == osUnknown) {
2470  switch (Key) {
2471  case kRed:
2472  return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, name));
2473  break;
2474  case kGreen:
2475  if (!cCutter::Active()) {
2476  if (!isMarks)
2477  Skins.Message(mtError, tr("No editing marks defined!"));
2478  else if (!cCutter::Start(recording->FileName(), strcmp(recording->Name(), name) ? *NewVideoFileName(recording->FileName(), name) : NULL, false))
2479  Skins.Message(mtError, tr("Can't start editing process!"));
2480  else
2481  Skins.Message(mtInfo, tr("Editing process started"));
2482  }
2483  else
2484  Skins.Message(mtError, tr("Editing process already active!"));
2485  return osContinue;
2486  case kYellow:
2487  case kBlue:
2488  if (strcmp(recording->Name(), name)) {
2489  if (!cFileTransfer::Active()) {
2490  if (cFileTransfer::Start(recording, name, (Key == kYellow)))
2491  Skins.Message(mtInfo, tr("File transfer started"));
2492  else
2493  Skins.Message(mtError, tr("Can't start file transfer!"));
2494  }
2495  else
2496  Skins.Message(mtError, tr("File transfer already active!"));
2497  }
2498  return osRecordings;
2499  default:
2500  break;
2501  }
2502  return osContinue;
2503  }
2504  else if (state == osEnd && HasSubMenu())
2505  state = SetFolder();
2506  else if (state == osUser1) {
2507  if (isMarks && Interface->Confirm(tr("Delete marks information?"))) {
2508  cMarks marks;
2510  cMark *mark = marks.First();
2511  while (mark) {
2512  cMark *nextmark = marks.Next(mark);
2513  marks.Del(mark);
2514  mark = nextmark;
2515  }
2516  marks.Save();
2517  isMarks = false;
2519  SetCurrent(First());
2520  Display();
2521  }
2522  return osContinue;
2523  }
2524  else if (state == osUser2) {
2525  if (isResume && Interface->Confirm(tr("Delete resume information?"))) {
2527  ResumeFile.Delete();
2528  isResume = false;
2530  SetCurrent(First());
2531  Display();
2532  }
2533  return osContinue;
2534  }
2535 
2536  return state;
2537 }
2538 
2539 // --- cMenuRecordings -------------------------------------------------------
2540 
2541 cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus)
2542 :cOsdMenu(Base ? Base : tr("Recordings"), 9, 6, 6)
2543 {
2545  base = Base ? strdup(Base) : NULL;
2546  level = Setup.RecordingDirs ? Level : -1;
2547  Recordings.StateChanged(recordingsState); // just to get the current state
2548  helpKeys = -1;
2549  Display(); // this keeps the higher level menus from showing up briefly when pressing 'Back' during replay
2550  Set();
2551  if (Current() < 0)
2552  SetCurrent(First());
2553  else if (OpenSubMenus && cReplayControl::LastReplayed() && Open(true))
2554  return;
2555  Display();
2556  SetHelpKeys();
2557 }
2558 
2560 {
2561  helpKeys = -1;
2562  free(base);
2563 }
2564 
2566 {
2568  int NewHelpKeys = 0;
2569  if (ri) {
2570  if (ri->IsDirectory())
2571  NewHelpKeys = 1;
2572  else {
2573  NewHelpKeys = 2;
2574  cRecording *recording = GetRecording(ri);
2575  if (recording && recording->Info()->Title())
2576  NewHelpKeys = 3;
2577  }
2578  }
2579  if (NewHelpKeys != helpKeys) {
2580  switch (NewHelpKeys) {
2581  case 0: SetHelp(NULL); break;
2582  case 1: SetHelp(tr("Button$Open")); break;
2583  case 2:
2584  case 3: SetHelp(RecordingCommands.Count() ? tr("Commands") : tr("Button$Play"), tr("Button$Rewind"), tr("Button$Delete"), NewHelpKeys == 3 ? tr("Button$Info") : NULL);
2585  default: ;
2586  }
2587  helpKeys = NewHelpKeys;
2588  }
2589 }
2590 
2591 void cMenuRecordings::Set(bool Refresh)
2592 {
2593  const char *CurrentRecording = cReplayControl::LastReplayed();
2594  cMenuRecordingItem *LastItem = NULL;
2595  char *LastItemText = NULL;
2596  cThreadLock RecordingsLock(&Recordings);
2597  if (Refresh) {
2599  if (ri) {
2600  cRecording *Recording = Recordings.GetByName(ri->FileName());
2601  if (Recording)
2602  CurrentRecording = Recording->FileName();
2603  }
2604  }
2605  Clear();
2607  Recordings.Sort();
2608  for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) {
2609  if (!base || (strstr(recording->Name(), base) == recording->Name() && recording->Name()[strlen(base)] == FOLDERDELIMCHAR)) {
2610  cMenuRecordingItem *Item = new cMenuRecordingItem(recording, level);
2611  if (*Item->Text() && (!Item->IsDirectory() || (!LastItem || !LastItem->IsDirectory() || strcmp(Item->Text(), LastItemText) != 0))) {
2612  Add(Item);
2613  LastItem = Item;
2614  free(LastItemText);
2615  LastItemText = strdup(LastItem->Text()); // must use a copy because of the counters!
2616  }
2617  else
2618  delete Item;
2619  if (LastItem) {
2620  if (CurrentRecording && strcmp(CurrentRecording, recording->FileName()) == 0)
2621  SetCurrent(LastItem);
2622  if (LastItem->IsDirectory())
2623  LastItem->IncrementCounter(recording->IsNew());
2624  }
2625  }
2626  }
2627  free(LastItemText);
2628  if (Refresh)
2629  Display();
2630 }
2631 
2633 {
2635  if (base) {
2636  char *s = ExchangeChars(strdup(base), true);
2637  d = AddDirectory(d, s);
2638  free(s);
2639  }
2640  return d;
2641 }
2642 
2644 {
2645  cRecording *recording = Recordings.GetByName(Item->FileName());
2646  if (!recording)
2647  Skins.Message(mtError, tr("Error while accessing recording!"));
2648  return recording;
2649 }
2650 
2651 bool cMenuRecordings::Open(bool OpenSubMenus)
2652 {
2654  if (ri && ri->IsDirectory()) {
2655  const char *t = ri->Name();
2656  cString buffer;
2657  if (base) {
2658  buffer = cString::sprintf("%s~%s", base, t);
2659  t = buffer;
2660  }
2661  AddSubMenu(new cMenuRecordings(t, level + 1, OpenSubMenus));
2662  return true;
2663  }
2664  return false;
2665 }
2666 
2668 {
2670  if (ri) {
2671  if (ri->IsDirectory())
2672  Open();
2673  else {
2674  cRecording *recording = GetRecording(ri);
2675  if (recording) {
2676  cReplayControl::SetRecording(recording->FileName());
2677  return osReplay;
2678  }
2679  }
2680  }
2681  return osContinue;
2682 }
2683 
2685 {
2686  if (HasSubMenu() || Count() == 0)
2687  return osContinue;
2689  if (ri && !ri->IsDirectory()) {
2690  cRecording *recording = GetRecording(ri);
2691  if (recording) {
2692  cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording
2693  cResumeFile ResumeFile(ri->FileName(), recording->IsPesRecording());
2694  ResumeFile.Delete();
2695  return Play();
2696  }
2697  }
2698  return osContinue;
2699 }
2700 
2702 {
2703  if (HasSubMenu() || Count() == 0)
2704  return osContinue;
2706  if (ri && !ri->IsDirectory()) {
2707  if (Interface->Confirm(tr("Delete recording?"))) {
2709  if (rc) {
2710  if (Interface->Confirm(tr("Timer still recording - really delete?"))) {
2711  cTimer *timer = rc->Timer();
2712  if (timer) {
2713  timer->Skip();
2714  cRecordControls::Process(time(NULL));
2715  if (timer->IsSingleEvent()) {
2716  isyslog("deleting timer %s", *timer->ToDescr());
2717  Timers.Del(timer);
2718  }
2719  Timers.SetModified();
2720  }
2721  }
2722  else
2723  return osContinue;
2724  }
2725  cRecording *recording = GetRecording(ri);
2726  if (recording) {
2727  if (cCutter::Active(ri->FileName())) {
2728  if (Interface->Confirm(tr("Recording is being edited - really delete?"))) {
2729  cCutter::Stop();
2730  recording = Recordings.GetByName(ri->FileName()); // cCutter::Stop() might have deleted it if it was the edited version
2731  // we continue with the code below even if recording is NULL,
2732  // in order to have the menu updated etc.
2733  }
2734  else
2735  return osContinue;
2736  }
2739  if (!recording || recording->Delete()) {
2741  Recordings.DelByName(ri->FileName());
2743  SetHelpKeys();
2745  Display();
2746  if (!Count())
2747  return osBack;
2748  }
2749  else
2750  Skins.Message(mtError, tr("Error while deleting recording!"));
2751  }
2752  }
2753  }
2754  return osContinue;
2755 }
2756 
2758 {
2759  if (HasSubMenu() || Count() == 0)
2760  return osContinue;
2762  if (ri && !ri->IsDirectory()) {
2763  cRecording *recording = GetRecording(ri);
2764  if (recording && recording->Info()->Title())
2765  return AddSubMenu(new cMenuRecording(recording, true));
2766  }
2767  return osContinue;
2768 }
2769 
2771 {
2772  if (HasSubMenu() || Count() == 0)
2773  return osContinue;
2775  if (ri && !ri->IsDirectory()) {
2776  cRecording *recording = GetRecording(ri);
2777  if (recording) {
2778  cMenuCommands *menu;
2779  eOSState state = AddSubMenu(menu = new cMenuCommands(tr("Recording commands"), &RecordingCommands, cString::sprintf("\"%s\"", *strescape(recording->FileName(), "\\\"$"))));
2780  if (Key != kNone)
2781  state = menu->ProcessKey(Key);
2782  return state;
2783  }
2784  }
2785  return osContinue;
2786 }
2787 
2789 {
2790  if (HasSubMenu())
2791  return osContinue;
2793  Set(true);
2794  return osContinue;
2795 }
2796 
2798 {
2799  if (HasSubMenu() || Count() == 0)
2800  return osContinue;
2802  if (ri && !ri->IsDirectory()) {
2803  cRecording *recording = GetRecording(ri);
2804  if (recording)
2805  return AddSubMenu(new cMenuEditRecording(recording));
2806  }
2807  return osContinue;
2808 }
2809 
2811 {
2812  bool HadSubMenu = HasSubMenu();
2813  eOSState state = cOsdMenu::ProcessKey(Key);
2814 
2815  if (state == osUnknown) {
2816  switch (Key) {
2817  case kPlay:
2818  case kOk: return Play();
2819  case kRed: return (helpKeys > 1 && RecordingCommands.Count()) ? Commands() : Play();
2820  case kGreen: return Rewind();
2821  case kYellow: return Delete();
2822  case kInfo: return Edit();
2823  case kBlue: return Info();
2824  case k0: return Sort();
2825  case k1...k9: return Commands(Key);
2827  Set(true);
2828  break;
2829  default: break;
2830  }
2831  }
2832  if (Key == kYellow && HadSubMenu && !HasSubMenu()) {
2833  // the last recording in a subdirectory was deleted, so let's go back up
2835  if (!Count())
2836  return osBack;
2837  Display();
2838  }
2839  if (!HasSubMenu()) {
2840  if (Key != kNone)
2841  SetHelpKeys();
2842  }
2843  return state;
2844 }
2845 
2846 // --- cMenuSetupBase --------------------------------------------------------
2847 
2849 protected:
2851  virtual void Store(void);
2852 public:
2853  cMenuSetupBase(void);
2854  };
2855 
2857 {
2858  data = Setup;
2859 }
2860 
2862 {
2863  Setup = data;
2865  Setup.Save();
2866 }
2867 
2868 // --- cMenuSetupOSD ---------------------------------------------------------
2869 
2871 private:
2872  const char *useSmallFontTexts[3];
2873  const char *keyColorTexts[4];
2878  const char **skinDescriptions;
2884  virtual void Set(void);
2885 public:
2886  cMenuSetupOSD(void);
2887  virtual ~cMenuSetupOSD();
2888  virtual eOSState ProcessKey(eKeys Key);
2889  };
2890 
2892 {
2894  numSkins = Skins.Count();
2896  skinDescriptions = new const char*[numSkins];
2897  themes.Load(Skins.Current()->Name());
2908  Set();
2909 }
2910 
2912 {
2913  delete[] skinDescriptions;
2914 }
2915 
2917 {
2918  int current = Current();
2919  for (cSkin *Skin = Skins.First(); Skin; Skin = Skins.Next(Skin))
2920  skinDescriptions[Skin->Index()] = Skin->Description();
2921  useSmallFontTexts[0] = tr("never");
2922  useSmallFontTexts[1] = tr("skin dependent");
2923  useSmallFontTexts[2] = tr("always");
2924  keyColorTexts[0] = tr("Key$Red");
2925  keyColorTexts[1] = tr("Key$Green");
2926  keyColorTexts[2] = tr("Key$Yellow");
2927  keyColorTexts[3] = tr("Key$Blue");
2928  Clear();
2929  SetSection(tr("OSD"));
2930  Add(new cMenuEditStraItem(tr("Setup.OSD$Language"), &osdLanguageIndex, I18nNumLanguagesWithLocale(), &I18nLanguages()->At(0)));
2931  Add(new cMenuEditStraItem(tr("Setup.OSD$Skin"), &skinIndex, numSkins, skinDescriptions));
2932  if (themes.NumThemes())
2933  Add(new cMenuEditStraItem(tr("Setup.OSD$Theme"), &themeIndex, themes.NumThemes(), themes.Descriptions()));
2934  Add(new cMenuEditPrcItem( tr("Setup.OSD$Left (%)"), &data.OSDLeftP, 0.0, 0.5));
2935  Add(new cMenuEditPrcItem( tr("Setup.OSD$Top (%)"), &data.OSDTopP, 0.0, 0.5));
2936  Add(new cMenuEditPrcItem( tr("Setup.OSD$Width (%)"), &data.OSDWidthP, 0.5, 1.0));
2937  Add(new cMenuEditPrcItem( tr("Setup.OSD$Height (%)"), &data.OSDHeightP, 0.5, 1.0));
2938  Add(new cMenuEditIntItem( tr("Setup.OSD$Message time (s)"), &data.OSDMessageTime, 1, 60));
2939  Add(new cMenuEditStraItem(tr("Setup.OSD$Use small font"), &data.UseSmallFont, 3, useSmallFontTexts));
2940  Add(new cMenuEditBoolItem(tr("Setup.OSD$Anti-alias"), &data.AntiAlias));
2941  Add(new cMenuEditStraItem(tr("Setup.OSD$Default font"), &fontOsdIndex, fontOsdNames.Size(), &fontOsdNames[0]));
2942  Add(new cMenuEditStraItem(tr("Setup.OSD$Small font"), &fontSmlIndex, fontSmlNames.Size(), &fontSmlNames[0]));
2943  Add(new cMenuEditStraItem(tr("Setup.OSD$Fixed font"), &fontFixIndex, fontFixNames.Size(), &fontFixNames[0]));
2944  Add(new cMenuEditPrcItem( tr("Setup.OSD$Default font size (%)"), &data.FontOsdSizeP, 0.01, 0.1, 1));
2945  Add(new cMenuEditPrcItem( tr("Setup.OSD$Small font size (%)"), &data.FontSmlSizeP, 0.01, 0.1, 1));
2946  Add(new cMenuEditPrcItem( tr("Setup.OSD$Fixed font size (%)"), &data.FontFixSizeP, 0.01, 0.1, 1));
2947  Add(new cMenuEditBoolItem(tr("Setup.OSD$Channel info position"), &data.ChannelInfoPos, tr("bottom"), tr("top")));
2948  Add(new cMenuEditIntItem( tr("Setup.OSD$Channel info time (s)"), &data.ChannelInfoTime, 1, 60));
2949  Add(new cMenuEditBoolItem(tr("Setup.OSD$Info on channel switch"), &data.ShowInfoOnChSwitch));
2950  Add(new cMenuEditBoolItem(tr("Setup.OSD$Timeout requested channel info"), &data.TimeoutRequChInfo));
2951  Add(new cMenuEditBoolItem(tr("Setup.OSD$Scroll pages"), &data.MenuScrollPage));
2952  Add(new cMenuEditBoolItem(tr("Setup.OSD$Scroll wraps"), &data.MenuScrollWrap));
2953  Add(new cMenuEditBoolItem(tr("Setup.OSD$Menu key closes"), &data.MenuKeyCloses));
2954  Add(new cMenuEditBoolItem(tr("Setup.OSD$Recording directories"), &data.RecordingDirs));
2955  Add(new cMenuEditBoolItem(tr("Setup.OSD$Folders in timer menu"), &data.FoldersInTimerMenu));
2956  Add(new cMenuEditBoolItem(tr("Setup.OSD$Number keys for characters"), &data.NumberKeysForChars));
2957  Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 0"), &data.ColorKey0, 4, keyColorTexts));
2958  Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 1"), &data.ColorKey1, 4, keyColorTexts));
2959  Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 2"), &data.ColorKey2, 4, keyColorTexts));
2960  Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 3"), &data.ColorKey3, 4, keyColorTexts));
2961  SetCurrent(Get(current));
2962  Display();
2963 }
2964 
2966 {
2967  bool ModifiedAppearance = false;
2968 
2969  if (Key == kOk) {
2971  if (skinIndex != originalSkinIndex) {
2972  cSkin *Skin = Skins.Get(skinIndex);
2973  if (Skin) {
2974  Utf8Strn0Cpy(data.OSDSkin, Skin->Name(), sizeof(data.OSDSkin));
2975  Skins.SetCurrent(Skin->Name());
2976  ModifiedAppearance = true;
2977  }
2978  }
2979  if (themes.NumThemes() && Skins.Current()->Theme()) {
2982  ModifiedAppearance |= themeIndex != originalThemeIndex;
2983  }
2985  ModifiedAppearance = true;
2987  ModifiedAppearance = true;
2992  ModifiedAppearance = true;
2994  ModifiedAppearance = true;
2996  ModifiedAppearance = true;
2997  }
2998 
2999  int oldSkinIndex = skinIndex;
3000  int oldOsdLanguageIndex = osdLanguageIndex;
3001  eOSState state = cMenuSetupBase::ProcessKey(Key);
3002 
3003  if (ModifiedAppearance) {
3005  SetDisplayMenu();
3006  }
3007 
3008  if (osdLanguageIndex != oldOsdLanguageIndex || skinIndex != oldSkinIndex) {
3010  int OriginalOSDLanguage = I18nCurrentLanguage();
3012 
3013  cSkin *Skin = Skins.Get(skinIndex);
3014  if (Skin) {
3015  char *d = themes.NumThemes() ? strdup(themes.Descriptions()[themeIndex]) : NULL;
3016  themes.Load(Skin->Name());
3017  if (skinIndex != oldSkinIndex)
3018  themeIndex = d ? themes.GetThemeIndex(d) : 0;
3019  free(d);
3020  }
3021 
3022  Set();
3023  I18nSetLanguage(OriginalOSDLanguage);
3024  }
3025  return state;
3026 }
3027 
3028 // --- cMenuSetupEPG ---------------------------------------------------------
3029 
3031 private:
3034  void Setup(void);
3035 public:
3036  cMenuSetupEPG(void);
3037  virtual eOSState ProcessKey(eKeys Key);
3038  };
3039 
3041 {
3043  ;
3045  SetSection(tr("EPG"));
3046  SetHelp(tr("Button$Scan"));
3047  Setup();
3048 }
3049 
3051 {
3052  int current = Current();
3053 
3054  Clear();
3055 
3056  Add(new cMenuEditIntItem( tr("Setup.EPG$EPG scan timeout (h)"), &data.EPGScanTimeout));
3057  Add(new cMenuEditIntItem( tr("Setup.EPG$EPG bugfix level"), &data.EPGBugfixLevel, 0, MAXEPGBUGFIXLEVEL));
3058  Add(new cMenuEditIntItem( tr("Setup.EPG$EPG linger time (min)"), &data.EPGLinger, 0));
3059  Add(new cMenuEditBoolItem(tr("Setup.EPG$Set system time"), &data.SetSystemTime));
3060  if (data.SetSystemTime)
3061  Add(new cMenuEditTranItem(tr("Setup.EPG$Use time from transponder"), &data.TimeTransponder, &data.TimeSource));
3062  // TRANSLATORS: note the plural!
3063  Add(new cMenuEditIntItem( tr("Setup.EPG$Preferred languages"), &numLanguages, 0, I18nLanguages()->Size()));
3064  for (int i = 0; i < numLanguages; i++)
3065  // TRANSLATORS: note the singular!
3066  Add(new cMenuEditStraItem(tr("Setup.EPG$Preferred language"), &data.EPGLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0)));
3067 
3068  SetCurrent(Get(current));
3069  Display();
3070 }
3071 
3073 {
3074  if (Key == kOk) {
3075  bool Modified = numLanguages != originalNumLanguages;
3076  if (!Modified) {
3077  for (int i = 0; i < numLanguages; i++) {
3078  if (data.EPGLanguages[i] != ::Setup.EPGLanguages[i]) {
3079  Modified = true;
3080  break;
3081  }
3082  }
3083  }
3084  if (Modified)
3086  }
3087 
3088  int oldnumLanguages = numLanguages;
3089  int oldSetSystemTime = data.SetSystemTime;
3090 
3091  eOSState state = cMenuSetupBase::ProcessKey(Key);
3092  if (Key != kNone) {
3093  if (numLanguages != oldnumLanguages || data.SetSystemTime != oldSetSystemTime) {
3094  for (int i = oldnumLanguages; i < numLanguages; i++) {
3095  data.EPGLanguages[i] = 0;
3096  for (int l = 0; l < I18nLanguages()->Size(); l++) {
3097  int k;
3098  for (k = 0; k < oldnumLanguages; k++) {
3099  if (data.EPGLanguages[k] == l)
3100  break;
3101  }
3102  if (k >= oldnumLanguages) {
3103  data.EPGLanguages[i] = l;
3104  break;
3105  }
3106  }
3107  }
3109  Setup();
3110  }
3111  if (Key == kRed) {
3113  return osEnd;
3114  }
3115  }
3116  return state;
3117 }
3118 
3119 // --- cMenuSetupDVB ---------------------------------------------------------
3120 
3122 private:
3127  void Setup(void);
3128  const char *videoDisplayFormatTexts[3];
3129  const char *updateChannelsTexts[6];
3130  const char *standardComplianceTexts[2];
3131 public:
3132  cMenuSetupDVB(void);
3133  virtual eOSState ProcessKey(eKeys Key);
3134  };
3135 
3137 {
3139  ;
3141  ;
3144  videoDisplayFormatTexts[0] = tr("pan&scan");
3145  videoDisplayFormatTexts[1] = tr("letterbox");
3146  videoDisplayFormatTexts[2] = tr("center cut out");
3147  updateChannelsTexts[0] = tr("no");
3148  updateChannelsTexts[1] = tr("names only");
3149  updateChannelsTexts[2] = tr("PIDs only");
3150  updateChannelsTexts[3] = tr("names and PIDs");
3151  updateChannelsTexts[4] = tr("add new channels");
3152  updateChannelsTexts[5] = tr("add new transponders");
3153  standardComplianceTexts[0] = "DVB";
3154  standardComplianceTexts[1] = "ANSI/SCTE";
3155 
3156  SetSection(tr("DVB"));
3157  SetHelp(NULL, tr("Button$Audio"), tr("Button$Subtitles"), NULL);
3158  Setup();
3159 }
3160 
3162 {
3163  int current = Current();
3164 
3165  Clear();
3166 
3167  Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDevice::NumDevices()));
3168  Add(new cMenuEditStraItem(tr("Setup.DVB$Standard compliance"), &data.StandardCompliance, 2, standardComplianceTexts));
3169  Add(new cMenuEditBoolItem(tr("Setup.DVB$Video format"), &data.VideoFormat, "4:3", "16:9"));
3170  if (data.VideoFormat == 0)
3171  Add(new cMenuEditStraItem(tr("Setup.DVB$Video display format"), &data.VideoDisplayFormat, 3, videoDisplayFormatTexts));
3172  Add(new cMenuEditBoolItem(tr("Setup.DVB$Use Dolby Digital"), &data.UseDolbyDigital));
3173  Add(new cMenuEditStraItem(tr("Setup.DVB$Update channels"), &data.UpdateChannels, 6, updateChannelsTexts));
3174  Add(new cMenuEditIntItem( tr("Setup.DVB$Audio languages"), &numAudioLanguages, 0, I18nLanguages()->Size()));
3175  for (int i = 0; i < numAudioLanguages; i++)
3176  Add(new cMenuEditStraItem(tr("Setup.DVB$Audio language"), &data.AudioLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0)));
3177  Add(new cMenuEditBoolItem(tr("Setup.DVB$Display subtitles"), &data.DisplaySubtitles));
3178  if (data.DisplaySubtitles) {
3179  Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle languages"), &numSubtitleLanguages, 0, I18nLanguages()->Size()));
3180  for (int i = 0; i < numSubtitleLanguages; i++)
3181  Add(new cMenuEditStraItem(tr("Setup.DVB$Subtitle language"), &data.SubtitleLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0)));
3182  Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle offset"), &data.SubtitleOffset, -100, 100));
3183  Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle foreground transparency"), &data.SubtitleFgTransparency, 0, 9));
3184  Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle background transparency"), &data.SubtitleBgTransparency, 0, 10));
3185  }
3186  Add(new cMenuEditBoolItem(tr("Setup.DVB$Enable teletext support"), &data.SupportTeletext));
3187 
3188  SetCurrent(Get(current));
3189  Display();
3190 }
3191 
3193 {
3194  int oldPrimaryDVB = ::Setup.PrimaryDVB;
3195  int oldVideoDisplayFormat = ::Setup.VideoDisplayFormat;
3196  bool oldVideoFormat = ::Setup.VideoFormat;
3197  bool newVideoFormat = data.VideoFormat;
3198  bool oldDisplaySubtitles = ::Setup.DisplaySubtitles;
3199  bool newDisplaySubtitles = data.DisplaySubtitles;
3200  int oldnumAudioLanguages = numAudioLanguages;
3201  int oldnumSubtitleLanguages = numSubtitleLanguages;
3202  eOSState state = cMenuSetupBase::ProcessKey(Key);
3203 
3204  if (Key != kNone) {
3205  switch (Key) {
3206  case kGreen: cRemote::Put(kAudio, true);
3207  state = osEnd;
3208  break;
3209  case kYellow: cRemote::Put(kSubtitles, true);
3210  state = osEnd;
3211  break;
3212  default: {
3213  bool DoSetup = data.VideoFormat != newVideoFormat;
3214  DoSetup |= data.DisplaySubtitles != newDisplaySubtitles;
3215  if (numAudioLanguages != oldnumAudioLanguages) {
3216  for (int i = oldnumAudioLanguages; i < numAudioLanguages; i++) {
3217  data.AudioLanguages[i] = 0;
3218  for (int l = 0; l < I18nLanguages()->Size(); l++) {
3219  int k;
3220  for (k = 0; k < oldnumAudioLanguages; k++) {
3221  if (data.AudioLanguages[k] == l)
3222  break;
3223  }
3224  if (k >= oldnumAudioLanguages) {
3225  data.AudioLanguages[i] = l;
3226  break;
3227  }
3228  }
3229  }
3231  DoSetup = true;
3232  }
3233  if (numSubtitleLanguages != oldnumSubtitleLanguages) {
3234  for (int i = oldnumSubtitleLanguages; i < numSubtitleLanguages; i++) {
3235  data.SubtitleLanguages[i] = 0;
3236  for (int l = 0; l < I18nLanguages()->Size(); l++) {
3237  int k;
3238  for (k = 0; k < oldnumSubtitleLanguages; k++) {
3239  if (data.SubtitleLanguages[k] == l)
3240  break;
3241  }
3242  if (k >= oldnumSubtitleLanguages) {
3243  data.SubtitleLanguages[i] = l;
3244  break;
3245  }
3246  }
3247  }
3249  DoSetup = true;
3250  }
3251  if (DoSetup)
3252  Setup();
3253  }
3254  }
3255  }
3256  if (state == osBack && Key == kOk) {
3257  if (::Setup.PrimaryDVB != oldPrimaryDVB)
3258  state = osSwitchDvb;
3259  if (::Setup.VideoDisplayFormat != oldVideoDisplayFormat)
3261  if (::Setup.VideoFormat != oldVideoFormat)
3262  cDevice::PrimaryDevice()->SetVideoFormat(::Setup.VideoFormat);
3263  if (::Setup.DisplaySubtitles != oldDisplaySubtitles)
3266  }
3267  return state;
3268 }
3269 
3270 // --- cMenuSetupLNB ---------------------------------------------------------
3271 
3273 private:
3275  void Setup(void);
3276 public:
3277  cMenuSetupLNB(void);
3278  virtual eOSState ProcessKey(eKeys Key);
3279  };
3280 
3282 :satCableNumbers(MAXDEVICES)
3283 {
3285  SetSection(tr("LNB"));
3286  Setup();
3287 }
3288 
3290 {
3291  int current = Current();
3292 
3293  Clear();
3294 
3295  Add(new cMenuEditBoolItem(tr("Setup.LNB$Use DiSEqC"), &data.DiSEqC));
3296  if (!data.DiSEqC) {
3297  Add(new cMenuEditIntItem( tr("Setup.LNB$SLOF (MHz)"), &data.LnbSLOF));
3298  Add(new cMenuEditIntItem( tr("Setup.LNB$Low LNB frequency (MHz)"), &data.LnbFrequLo));
3299  Add(new cMenuEditIntItem( tr("Setup.LNB$High LNB frequency (MHz)"), &data.LnbFrequHi));
3300  }
3301 
3302  int NumSatDevices = 0;
3303  for (int i = 0; i < cDevice::NumDevices(); i++) {
3305  NumSatDevices++;
3306  }
3307  if (NumSatDevices > 1) {
3308  for (int i = 0; i < cDevice::NumDevices(); i++) {
3310  Add(new cMenuEditIntItem(cString::sprintf(tr("Setup.LNB$Device %d connected to sat cable"), i + 1), &satCableNumbers.Array()[i], 0, NumSatDevices, tr("Setup.LNB$own")));
3311  }
3312  }
3313 
3314  SetCurrent(Get(current));
3315  Display();
3316 }
3317 
3319 {
3320  int oldDiSEqC = data.DiSEqC;
3321  bool DeviceBondingsChanged = false;
3322  if (Key == kOk) {
3323  cString NewDeviceBondings = satCableNumbers.ToString();
3324  DeviceBondingsChanged = strcmp(data.DeviceBondings, NewDeviceBondings) != 0;
3325  data.DeviceBondings = NewDeviceBondings;
3326  }
3327  eOSState state = cMenuSetupBase::ProcessKey(Key);
3328 
3329  if (Key != kNone && data.DiSEqC != oldDiSEqC)
3330  Setup();
3331  else if (DeviceBondingsChanged)
3333  return state;
3334 }
3335 
3336 // --- cMenuSetupCAM ---------------------------------------------------------
3337 
3338 class cMenuSetupCAMItem : public cOsdItem {
3339 private:
3341 public:
3343  cCamSlot *CamSlot(void) { return camSlot; }
3344  bool Changed(void);
3345  };
3346 
3348 {
3349  camSlot = CamSlot;
3350  SetText("");
3351  Changed();
3352 }
3353 
3355 {
3356  char buffer[32];
3357  const char *CamName = camSlot->GetCamName();
3358  if (!CamName) {
3359  switch (camSlot->ModuleStatus()) {
3360  case msReset: CamName = tr("CAM reset"); break;
3361  case msPresent: CamName = tr("CAM present"); break;
3362  case msReady: CamName = tr("CAM ready"); break;
3363  default: CamName = "-"; break;
3364  }
3365  }
3366  snprintf(buffer, sizeof(buffer), " %d %s", camSlot->SlotNumber(), CamName);
3367  if (strcmp(buffer, Text()) != 0) {
3368  SetText(buffer);
3369  return true;
3370  }
3371  return false;
3372 }
3373 
3375 private:
3376  eOSState Menu(void);
3377  eOSState Reset(void);
3378 public:
3379  cMenuSetupCAM(void);
3380  virtual eOSState ProcessKey(eKeys Key);
3381  };
3382 
3384 {
3385  SetSection(tr("CAM"));
3386  SetCols(15);
3387  SetHasHotkeys();
3388  for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot))
3389  Add(new cMenuSetupCAMItem(CamSlot));
3390  SetHelp(tr("Button$Menu"), tr("Button$Reset"));
3391 }
3392 
3394 {
3396  if (item) {
3397  if (item->CamSlot()->EnterMenu()) {
3398  Skins.Message(mtStatus, tr("Opening CAM menu..."));
3399  time_t t0 = time(NULL);
3400  time_t t1 = t0;
3401  while (time(NULL) - t0 <= MAXWAITFORCAMMENU) {
3402  if (item->CamSlot()->HasUserIO())
3403  break;
3404  if (time(NULL) - t1 >= CAMMENURETYTIMEOUT) {
3405  dsyslog("CAM %d: retrying to enter CAM menu...", item->CamSlot()->SlotNumber());
3406  item->CamSlot()->EnterMenu();
3407  t1 = time(NULL);
3408  }
3409  cCondWait::SleepMs(100);
3410  }
3411  Skins.Message(mtStatus, NULL);
3412  if (item->CamSlot()->HasUserIO())
3413  return AddSubMenu(new cMenuCam(item->CamSlot()));
3414  }
3415  Skins.Message(mtError, tr("Can't open CAM menu!"));
3416  }
3417  return osContinue;
3418 }
3419 
3421 {
3423  if (item) {
3424  if (!item->CamSlot()->Device() || Interface->Confirm(tr("CAM is in use - really reset?"))) {
3425  if (!item->CamSlot()->Reset())
3426  Skins.Message(mtError, tr("Can't reset CAM!"));
3427  }
3428  }
3429  return osContinue;
3430 }
3431 
3433 {
3435 
3436  if (!HasSubMenu()) {
3437  switch (Key) {
3438  case kOk:
3439  case kRed: return Menu();
3440  case kGreen: state = Reset(); break;
3441  default: break;
3442  }
3443  for (cMenuSetupCAMItem *ci = (cMenuSetupCAMItem *)First(); ci; ci = (cMenuSetupCAMItem *)ci->Next()) {
3444  if (ci->Changed())
3445  DisplayItem(ci);
3446  }
3447  }
3448  return state;
3449 }
3450 
3451 // --- cMenuSetupRecord ------------------------------------------------------
3452 
3454 private:
3455  const char *pauseKeyHandlingTexts[3];
3456  const char *delTimeshiftRecTexts[3];
3457 public:
3458  cMenuSetupRecord(void);
3459  };
3460 
3462 {
3463  pauseKeyHandlingTexts[0] = tr("do not pause live video");
3464  pauseKeyHandlingTexts[1] = tr("confirm pause live video");
3465  pauseKeyHandlingTexts[2] = tr("pause live video");
3466  delTimeshiftRecTexts[0] = tr("no");
3467  delTimeshiftRecTexts[1] = tr("confirm");
3468  delTimeshiftRecTexts[2] = tr("yes");
3469  SetSection(tr("Recording"));
3470  Add(new cMenuEditIntItem( tr("Setup.Recording$Margin at start (min)"), &data.MarginStart));
3471  Add(new cMenuEditIntItem( tr("Setup.Recording$Margin at stop (min)"), &data.MarginStop));
3472  Add(new cMenuEditIntItem( tr("Setup.Recording$Default priority"), &data.DefaultPriority, 0, MAXPRIORITY));
3473  Add(new cMenuEditIntItem( tr("Setup.Recording$Default lifetime (d)"), &data.DefaultLifetime, 0, MAXLIFETIME));
3474  Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"), &data.PauseKeyHandling, 3, pauseKeyHandlingTexts));
3475  Add(new cMenuEditIntItem( tr("Setup.Recording$Pause priority"), &data.PausePriority, 0, MAXPRIORITY));
3476  Add(new cMenuEditIntItem( tr("Setup.Recording$Pause lifetime (d)"), &data.PauseLifetime, 0, MAXLIFETIME));
3477  Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle));
3478  Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps));
3479  Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0));
3480  Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord));
3481  Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord)));
3482  Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 0, MAXINSTANTRECTIME, tr("Setup.Recording$present event")));
3483  Add(new cMenuEditIntItem( tr("Setup.Recording$Max. video file size (MB)"), &data.MaxVideoFileSize, MINVIDEOFILESIZE, MAXVIDEOFILESIZETS));
3484  Add(new cMenuEditIntItem( tr("Setup.Recording$Max. recording size (GB)"), &data.MaxRecordingSize, MINRECORDINGSIZE, MAXRECORDINGSIZE));
3485  Add(new cMenuEditBoolItem(tr("Setup.Recording$Split edited files"), &data.SplitEditedFiles));
3486  Add(new cMenuEditStraItem(tr("Setup.Recording$Delete timeshift recording"),&data.DelTimeshiftRec, 3, delTimeshiftRecTexts));
3487  Add(new cMenuEditBoolItem(tr("Setup.Recording$Hard Link Cutter"), &data.HardLinkCutter));
3488 }
3489 
3490 // --- cMenuSetupReplay ------------------------------------------------------
3491 
3493 protected:
3494  virtual void Store(void);
3495 public:
3496  cMenuSetupReplay(void);
3497  };
3498 
3500 {
3501  SetSection(tr("Replay"));
3502  Add(new cMenuEditBoolItem(tr("Setup.Replay$Multi speed mode"), &data.MultiSpeedMode));
3503  Add(new cMenuEditBoolItem(tr("Setup.Replay$Show replay mode"), &data.ShowReplayMode));
3504  Add(new cMenuEditBoolItem(tr("Setup.Replay$Show remaining time"), &data.ShowRemainingTime));
3505  Add(new cMenuEditIntItem(tr("Setup.Replay$Resume ID"), &data.ResumeID, 0, 99));
3506  Add(new cMenuEditBoolItem(tr("Setup.Replay$Jump&Play"), &data.JumpPlay));
3507  Add(new cMenuEditBoolItem(tr("Setup.Replay$Play&Jump"), &data.PlayJump));
3508  Add(new cMenuEditBoolItem(tr("Setup.Replay$Pause at last mark"), &data.PauseLastMark));
3509 }
3510 
3512 {
3513  if (Setup.ResumeID != data.ResumeID)
3516 }
3517 
3518 // --- cMenuSetupMisc --------------------------------------------------------
3519 
3521 public:
3522  cMenuSetupMisc(void);
3523  };
3524 
3526 {
3527  SetSection(tr("Miscellaneous"));
3528  Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. event timeout (min)"), &data.MinEventTimeout));
3529  Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. user inactivity (min)"), &data.MinUserInactivity));
3530  Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$SVDRP timeout (s)"), &data.SVDRPTimeout));
3531  Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Zap timeout (s)"), &data.ZapTimeout));
3532  Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Channel entry timeout (ms)"), &data.ChannelEntryTimeout, 0));
3533  Add(new cMenuEditChanItem(tr("Setup.Miscellaneous$Initial channel"), &data.InitialChannel, tr("Setup.Miscellaneous$as before")));
3534  Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Initial volume"), &data.InitialVolume, -1, 255, tr("Setup.Miscellaneous$as before")));
3535  Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Channels wrap"), &data.ChannelsWrap));
3536  Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Show channel names with source"), &data.ShowChannelNamesWithSource));
3537  Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Emergency exit"), &data.EmergencyExit));
3538 }
3539 
3540 // --- cMenuSetupPluginItem --------------------------------------------------
3541 
3543 private:
3545 public:
3546  cMenuSetupPluginItem(const char *Name, int Index);
3547  int PluginIndex(void) { return pluginIndex; }
3548  };
3549 
3551 :cOsdItem(Name)
3552 {
3553  pluginIndex = Index;
3554 }
3555 
3556 // --- cMenuSetupPlugins -----------------------------------------------------
3557 
3559 public:
3560  cMenuSetupPlugins(void);
3561  virtual eOSState ProcessKey(eKeys Key);
3562  };
3563 
3565 {
3566  SetSection(tr("Plugins"));
3567  SetHasHotkeys();
3568  for (int i = 0; ; i++) {
3570  if (p)
3571  Add(new cMenuSetupPluginItem(hk(cString::sprintf("%s (%s) - %s", p->Name(), p->Version(), p->Description())), i));
3572  else
3573  break;
3574  }
3575 }
3576 
3578 {
3580 
3581  if (Key == kOk) {
3582  if (state == osUnknown) {
3584  if (item) {
3586  if (p) {
3587  cMenuSetupPage *menu = p->SetupMenu();
3588  if (menu) {
3589  menu->SetPlugin(p);
3590  return AddSubMenu(menu);
3591  }
3592  Skins.Message(mtInfo, tr("This plugin has no setup parameters!"));
3593  }
3594  }
3595  }
3596  else if (state == osContinue) {
3597  Store();
3598  // Reinitialize OSD and skin, in case any plugin setup change has an influence on these:
3600  SetDisplayMenu();
3601  Display();
3602  }
3603  }
3604  return state;
3605 }
3606 
3607 // --- cMenuSetup ------------------------------------------------------------
3608 
3609 class cMenuSetup : public cOsdMenu {
3610 private:
3611  virtual void Set(void);
3612  eOSState Restart(void);
3613 public:
3614  cMenuSetup(void);
3615  virtual eOSState ProcessKey(eKeys Key);
3616  };
3617 
3619 :cOsdMenu("")
3620 {
3622  Set();
3623 }
3624 
3626 {
3627  Clear();
3628  char buffer[64];
3629  snprintf(buffer, sizeof(buffer), "%s - VDR %s", tr("Setup"), VDRVERSION);
3630  SetTitle(buffer);
3631  SetHasHotkeys();
3632  Add(new cOsdItem(hk(tr("OSD")), osUser1));
3633  Add(new cOsdItem(hk(tr("EPG")), osUser2));
3634  Add(new cOsdItem(hk(tr("DVB")), osUser3));
3635  Add(new cOsdItem(hk(tr("LNB")), osUser4));
3636  Add(new cOsdItem(hk(tr("CAM")), osUser5));
3637  Add(new cOsdItem(hk(tr("Recording")), osUser6));
3638  Add(new cOsdItem(hk(tr("Replay")), osUser7));
3639  Add(new cOsdItem(hk(tr("Miscellaneous")), osUser8));
3641  Add(new cOsdItem(hk(tr("Plugins")), osUser9));
3642  Add(new cOsdItem(hk(tr("Restart")), osUser10));
3643 }
3644 
3646 {
3647  if (Interface->Confirm(tr("Really restart?")) && ShutdownHandler.ConfirmRestart(true)) {
3648  ShutdownHandler.Exit(1);
3649  return osEnd;
3650  }
3651  return osContinue;
3652 }
3653 
3655 {
3656  int osdLanguage = I18nCurrentLanguage();
3657  eOSState state = cOsdMenu::ProcessKey(Key);
3658 
3659  switch (state) {
3660  case osUser1: return AddSubMenu(new cMenuSetupOSD);
3661  case osUser2: return AddSubMenu(new cMenuSetupEPG);
3662  case osUser3: return AddSubMenu(new cMenuSetupDVB);
3663  case osUser4: return AddSubMenu(new cMenuSetupLNB);
3664  case osUser5: return AddSubMenu(new cMenuSetupCAM);
3665  case osUser6: return AddSubMenu(new cMenuSetupRecord);
3666  case osUser7: return AddSubMenu(new cMenuSetupReplay);
3667  case osUser8: return AddSubMenu(new cMenuSetupMisc);
3668  case osUser9: return AddSubMenu(new cMenuSetupPlugins);
3669  case osUser10: return Restart();
3670  default: ;
3671  }
3672  if (I18nCurrentLanguage() != osdLanguage) {
3673  Set();
3674  if (!HasSubMenu())
3675  Display();
3676  }
3677  return state;
3678 }
3679 
3680 // --- cMenuPluginItem -------------------------------------------------------
3681 
3682 class cMenuPluginItem : public cOsdItem {
3683 private:
3685 public:
3686  cMenuPluginItem(const char *Name, int Index);
3687  int PluginIndex(void) { return pluginIndex; }
3688  };
3689 
3690 cMenuPluginItem::cMenuPluginItem(const char *Name, int Index)
3691 :cOsdItem(Name, osPlugin)
3692 {
3693  pluginIndex = Index;
3694 }
3695 
3696 // --- cMenuMain -------------------------------------------------------------
3697 
3698 // TRANSLATORS: note the leading and trailing blanks!
3699 #define STOP_RECORDING trNOOP(" Stop recording ")
3700 
3702 
3704 :cOsdMenu("")
3705 {
3707  replaying = false;
3708  stopReplayItem = NULL;
3709  cancelEditingItem = NULL;
3710  cancelFileTransferItem = NULL;
3711  stopRecordingItem = NULL;
3712  recordControlsState = 0;
3713  Set();
3714 
3715  // Initial submenus:
3716 
3717  cOsdObject *menu = NULL;
3718  switch (State) {
3719  case osSchedule:
3720  if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu))
3721  menu = new cMenuSchedule;
3722  break;
3723  case osChannels:
3724  if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu))
3725  menu = new cMenuChannels;
3726  break;
3727  case osTimers:
3728  if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu))
3729  menu = new cMenuTimers;
3730  break;
3731  case osRecordings:
3732  if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu))
3733  menu = new cMenuRecordings(NULL, 0, true);
3734  break;
3735  case osSetup: menu = new cMenuSetup; break;
3736  case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break;
3737  default: break;
3738  }
3739  if (menu)
3740  if (menu->IsMenu())
3741  AddSubMenu((cOsdMenu *) menu);
3742 }
3743 
3745 {
3747  pluginOsdObject = NULL;
3748  return o;
3749 }
3750 
3751 void cMenuMain::Set(void)
3752 {
3753  Clear();
3754  SetTitle("VDR");
3755  SetHasHotkeys();
3756 
3757  // Basic menu items:
3758 
3759  Add(new cOsdItem(hk(tr("Schedule")), osSchedule));
3760  Add(new cOsdItem(hk(tr("Channels")), osChannels));
3761  Add(new cOsdItem(hk(tr("Timers")), osTimers));
3762  Add(new cOsdItem(hk(tr("Recordings")), osRecordings));
3763 
3764  // Plugins:
3765 
3766  for (int i = 0; ; i++) {
3768  if (p) {
3769  const char *item = p->MainMenuEntry();
3770  if (item)
3771  Add(new cMenuPluginItem(hk(item), i));
3772  }
3773  else
3774  break;
3775  }
3776 
3777  // More basic menu items:
3778 
3779  Add(new cOsdItem(hk(tr("Setup")), osSetup));
3780  if (Commands.Count())
3781  Add(new cOsdItem(hk(tr("Commands")), osCommands));
3782 
3783  Update(true);
3784 
3785  Display();
3786 }
3787 
3788 bool cMenuMain::Update(bool Force)
3789 {
3790  bool result = false;
3791 
3792  bool NewReplaying = cControl::Control() != NULL;
3793  if (Force || NewReplaying != replaying) {
3794  replaying = NewReplaying;
3795  // Replay control:
3796  if (replaying && !stopReplayItem)
3797  // TRANSLATORS: note the leading blank!
3798  Add(stopReplayItem = new cOsdItem(tr(" Stop replaying"), osStopReplay));
3799  else if (stopReplayItem && !replaying) {
3800  Del(stopReplayItem->Index());
3801  stopReplayItem = NULL;
3802  }
3803  // Color buttons:
3804  SetHelp(!replaying ? tr("Button$Record") : NULL, tr("Button$Audio"), replaying ? NULL : tr("Button$Pause"), replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Button$Resume") : NULL);
3805  result = true;
3806  }
3807 
3808  // Editing control:
3809  bool CutterActive = cCutter::Active();
3810  if (CutterActive && !cancelEditingItem) {
3811  // TRANSLATORS: note the leading blank!
3812  Add(cancelEditingItem = new cOsdItem(tr(" Cancel editing"), osCancelEdit));
3813  result = true;
3814  }
3815  else if (cancelEditingItem && !CutterActive) {
3817  cancelEditingItem = NULL;
3818  result = true;
3819  }
3820 
3821  // File transfer control:
3822  bool FileTransferActive = cFileTransfer::Active();
3823  if (FileTransferActive && !cancelFileTransferItem) {
3824  // TRANSLATORS: note the leading blank!
3825  Add(cancelFileTransferItem = new cOsdItem(tr(" Cancel file transfer"), osCancelTransfer));
3826  result = true;
3827  }
3828  else if (cancelFileTransferItem && !FileTransferActive) {
3830  cancelFileTransferItem = NULL;
3831  result = true;
3832  }
3833 
3834  // Record control:
3836  while (stopRecordingItem) {
3839  stopRecordingItem = it;
3840  }
3841  const char *s = NULL;
3842  while ((s = cRecordControls::GetInstantId(s)) != NULL) {
3843  cOsdItem *item = new cOsdItem(osStopRecord);
3844  item->SetText(cString::sprintf("%s%s", tr(STOP_RECORDING), s));
3845  Add(item);
3846  if (!stopRecordingItem)
3847  stopRecordingItem = item;
3848  }
3849  result = true;
3850  }
3851 
3852  return result;
3853 }
3854 
3856 {
3857  bool HadSubMenu = HasSubMenu();
3858  int osdLanguage = I18nCurrentLanguage();
3859  eOSState state = cOsdMenu::ProcessKey(Key);
3860  HadSubMenu |= HasSubMenu();
3861 
3862  cOsdObject *menu = NULL;
3863  switch (state) {
3864  case osSchedule:
3865  if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu))
3866  menu = new cMenuSchedule;
3867  else
3868  state = osContinue;
3869  break;
3870  case osChannels:
3871  if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu))
3872  menu = new cMenuChannels;
3873  else
3874  state = osContinue;
3875  break;
3876  case osTimers:
3877  if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu))
3878  menu = new cMenuTimers;
3879  else
3880  state = osContinue;
3881  break;
3882  case osRecordings:
3883  if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu))
3884  menu = new cMenuRecordings;
3885  else
3886  state = osContinue;
3887  break;
3888  case osSetup: menu = new cMenuSetup; break;
3889  case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break;
3890  case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) {
3891  cOsdItem *item = Get(Current());
3892  if (item) {
3893  cRecordControls::Stop(item->Text() + strlen(tr(STOP_RECORDING)));
3894  return osEnd;
3895  }
3896  }
3897  break;
3898  case osCancelEdit: if (Interface->Confirm(tr("Cancel editing?"))) {
3899  cCutter::Stop();
3900  return osEnd;
3901  }
3902  break;
3903  case osCancelTransfer:
3904  if (Interface->Confirm(tr("Cancel file transfer?"))) {
3906  return osEnd;
3907  }
3908  break;
3909  case osPlugin: {
3911  if (item) {
3913  if (p) {
3914  cOsdObject *menu = p->MainMenuAction();
3915  if (menu) {
3916  if (menu->IsMenu())
3917  return AddSubMenu((cOsdMenu *)menu);
3918  else {
3919  pluginOsdObject = menu;
3920  return osPlugin;
3921  }
3922  }
3923  }
3924  }
3925  state = osEnd;
3926  }
3927  break;
3928  default: switch (Key) {
3929  case kRecord:
3930  case kRed: if (!HadSubMenu)
3931  state = replaying ? osContinue : osRecord;
3932  break;
3933  case kGreen: if (!HadSubMenu) {
3934  cRemote::Put(kAudio, true);
3935  state = osEnd;
3936  }
3937  break;
3938  case kYellow: if (!HadSubMenu)
3939  state = replaying ? osContinue : osPause;
3940  break;
3941  case kBlue: if (!HadSubMenu)
3943  break;
3944  default: break;
3945  }
3946  }
3947  if (menu) {
3948  if (menu->IsMenu())
3949  return AddSubMenu((cOsdMenu *) menu);
3950  pluginOsdObject = menu;
3951  return osPlugin;
3952  }
3953  if (!HasSubMenu() && Update(HadSubMenu))
3954  Display();
3955  if (Key != kNone) {
3956  if (I18nCurrentLanguage() != osdLanguage) {
3957  Set();
3958  if (!HasSubMenu())
3959  Display();
3960  }
3961  }
3962  return state;
3963 }
3964 
3965 // --- SetTrackDescriptions --------------------------------------------------
3966 
3967 static void SetTrackDescriptions(int LiveChannel)
3968 {
3970  const cComponents *Components = NULL;
3971  cSchedulesLock SchedulesLock;
3972  if (LiveChannel) {
3973  cChannel *Channel = Channels.GetByNumber(LiveChannel);
3974  if (Channel) {
3975  const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
3976  if (Schedules) {
3977  const cSchedule *Schedule = Schedules->GetSchedule(Channel);
3978  if (Schedule) {
3979  const cEvent *Present = Schedule->GetPresentEvent();
3980  if (Present)
3981  Components = Present->Components();
3982  }
3983  }
3984  }
3985  }
3986  else if (cReplayControl::NowReplaying()) {
3987  cThreadLock RecordingsLock(&Recordings);
3989  if (Recording)
3990  Components = Recording->Info()->Components();
3991  }
3992  if (Components) {
3993  int indexAudio = 0;
3994  int indexDolby = 0;
3995  int indexSubtitle = 0;
3996  for (int i = 0; i < Components->NumComponents(); i++) {
3997  const tComponent *p = Components->Component(i);
3998  switch (p->stream) {
3999  case 2: if (p->type == 0x05)
4000  cDevice::PrimaryDevice()->SetAvailableTrack(ttDolby, indexDolby++, 0, LiveChannel ? NULL : p->language, p->description);
4001  else
4002  cDevice::PrimaryDevice()->SetAvailableTrack(ttAudio, indexAudio++, 0, LiveChannel ? NULL : p->language, p->description);
4003  break;
4004  case 3: cDevice::PrimaryDevice()->SetAvailableTrack(ttSubtitle, indexSubtitle++, 0, LiveChannel ? NULL : p->language, p->description);
4005  break;
4006  case 4: cDevice::PrimaryDevice()->SetAvailableTrack(ttDolby, indexDolby++, 0, LiveChannel ? NULL : p->language, p->description);
4007  break;
4008  default: ;
4009  }
4010  }
4011  }
4012 }
4013 
4014 // --- cDisplayChannel -------------------------------------------------------
4015 
4017 
4018 cDisplayChannel::cDisplayChannel(int Number, bool Switched)
4019 :cOsdObject(true)
4020 {
4021  currentDisplayChannel = this;
4022  group = -1;
4023  withInfo = !Switched || Setup.ShowInfoOnChSwitch;
4025  number = 0;
4026  timeout = Switched || Setup.TimeoutRequChInfo;
4027  channel = Channels.GetByNumber(Number);
4028  lastPresent = lastFollowing = NULL;
4029  if (channel) {
4030  DisplayChannel();
4031  DisplayInfo();
4032  displayChannel->Flush();
4033  }
4034  lastTime.Set();
4035 }
4036 
4038 :cOsdObject(true)
4039 {
4040  currentDisplayChannel = this;
4041  group = -1;
4042  number = 0;
4043  timeout = true;
4044  lastPresent = lastFollowing = NULL;
4045  lastTime.Set();
4049  ProcessKey(FirstKey);
4050 }
4051 
4053 {
4054  delete displayChannel;
4056  currentDisplayChannel = NULL;
4057 }
4058 
4060 {
4063  lastPresent = lastFollowing = NULL;
4064 }
4065 
4067 {
4068  if (withInfo && channel) {
4069  cSchedulesLock SchedulesLock;
4070  const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
4071  if (Schedules) {
4072  const cSchedule *Schedule = Schedules->GetSchedule(channel);
4073  if (Schedule) {
4074  const cEvent *Present = Schedule->GetPresentEvent();
4075  const cEvent *Following = Schedule->GetFollowingEvent();
4076  if (Present != lastPresent || Following != lastFollowing) {
4078  displayChannel->SetEvents(Present, Following);
4079  cStatus::MsgOsdProgramme(Present ? Present->StartTime() : 0, Present ? Present->Title() : NULL, Present ? Present->ShortText() : NULL, Following ? Following->StartTime() : 0, Following ? Following->Title() : NULL, Following ? Following->ShortText() : NULL);
4080  lastPresent = Present;
4081  lastFollowing = Following;
4082  }
4083  }
4084  }
4085  }
4086 }
4087 
4089 {
4090  DisplayChannel();
4091  displayChannel->SetEvents(NULL, NULL);
4092 }
4093 
4095 {
4096  if (Direction) {
4097  while (Channel) {
4098  Channel = Direction > 0 ? Channels.Next(Channel) : Channels.Prev(Channel);
4099  if (!Channel && Setup.ChannelsWrap)
4100  Channel = Direction > 0 ? Channels.First() : Channels.Last();
4101  if (Channel && !Channel->GroupSep() && cDevice::GetDevice(Channel, LIVEPRIORITY, true, true))
4102  return Channel;
4103  }
4104  }
4105  return NULL;
4106 }
4107 
4109 {
4110  cChannel *NewChannel = NULL;
4111  if (Key != kNone)
4112  lastTime.Set();
4113  switch (int(Key)) {
4114  case k0:
4115  if (number == 0) {
4116  // keep the "Toggle channels" function working
4117  cRemote::Put(Key);
4118  return osEnd;
4119  }
4120  case k1 ... k9:
4121  group = -1;
4122  if (number >= 0) {
4123  if (number > Channels.MaxNumber())
4124  number = Key - k0;
4125  else
4126  number = number * 10 + Key - k0;
4128  Refresh();
4129  withInfo = false;
4130  // Lets see if there can be any useful further input:
4131  int n = channel ? number * 10 : 0;
4132  int m = 10;
4133  cChannel *ch = channel;
4134  while (ch && (ch = Channels.Next(ch)) != NULL) {
4135  if (!ch->GroupSep()) {
4136  if (n <= ch->Number() && ch->Number() < n + m) {
4137  n = 0;
4138  break;
4139  }
4140  if (ch->Number() > n) {
4141  n *= 10;
4142  m *= 10;
4143  }
4144  }
4145  }
4146  if (n > 0) {
4147  // This channel is the only one that fits the input, so let's take it right away:
4148  NewChannel = channel;
4149  withInfo = true;
4150  number = 0;
4151  Refresh();
4152  }
4153  }
4154  break;
4155  case kLeft|k_Repeat:
4156  case kLeft:
4157  case kRight|k_Repeat:
4158  case kRight:
4159  case kNext|k_Repeat:
4160  case kNext:
4161  case kPrev|k_Repeat:
4162  case kPrev:
4163  withInfo = false;
4164  number = 0;
4165  if (group < 0) {
4167  if (channel)
4168  group = channel->Index();
4169  }
4170  if (group >= 0) {
4171  int SaveGroup = group;
4172  if (NORMALKEY(Key) == kRight || NORMALKEY(Key) == kNext)
4174  else
4175  group = Channels.GetPrevGroup(group < 1 ? 1 : group);
4176  if (group < 0)
4177  group = SaveGroup;
4179  if (channel) {
4180  Refresh();
4181  if (!channel->GroupSep())
4182  group = -1;
4183  }
4184  }
4185  break;
4186  case kUp|k_Repeat:
4187  case kUp:
4188  case kDown|k_Repeat:
4189  case kDown:
4190  case kChanUp|k_Repeat:
4191  case kChanUp:
4192  case kChanDn|k_Repeat:
4193  case kChanDn: {
4194  eKeys k = NORMALKEY(Key);
4195  cChannel *ch = NextAvailableChannel(channel, (k == kUp || k == kChanUp) ? 1 : -1);
4196  if (ch)
4197  channel = ch;
4198  else if (channel && channel->Number() != cDevice::CurrentChannel())
4199  Key = k; // immediately switches channel when hitting the beginning/end of the channel list with k_Repeat
4200  }
4201  // no break here
4202  case kUp|k_Release:
4203  case kDown|k_Release:
4204  case kChanUp|k_Release:
4205  case kChanDn|k_Release:
4206  case kNext|k_Release:
4207  case kPrev|k_Release:
4208  if (!(Key & k_Repeat) && channel && channel->Number() != cDevice::CurrentChannel())
4209  NewChannel = channel;
4210  withInfo = true;
4211  group = -1;
4212  number = 0;
4213  Refresh();
4214  break;
4215  case kNone:
4218  if (channel)
4219  NewChannel = channel;
4220  withInfo = true;
4221  number = 0;
4222  Refresh();
4223  lastTime.Set();
4224  }
4225  break;
4226  //TODO
4227  //XXX case kGreen: return osEventNow;
4228  //XXX case kYellow: return osEventNext;
4229  case kOk:
4230  if (group >= 0) {
4232  if (channel)
4233  NewChannel = channel;
4234  withInfo = true;
4235  group = -1;
4236  Refresh();
4237  }
4238  else if (number > 0) {
4240  if (channel)
4241  NewChannel = channel;
4242  withInfo = true;
4243  number = 0;
4244  Refresh();
4245  }
4246  else
4247  return osEnd;
4248  break;
4249  default:
4250  if ((Key & (k_Repeat | k_Release)) == 0) {
4251  cRemote::Put(Key);
4252  return osEnd;
4253  }
4254  };
4255  if (!timeout || lastTime.Elapsed() < (uint64_t)(Setup.ChannelInfoTime * 1000)) {
4256  if (Key == kNone && !number && group < 0 && !NewChannel && channel && channel->Number() != cDevice::CurrentChannel()) {
4257  // makes sure a channel switch through the SVDRP CHAN command is displayed
4259  Refresh();
4260  lastTime.Set();
4261  }
4262  DisplayInfo();
4263  displayChannel->Flush();
4264  if (NewChannel) {
4265  SetTrackDescriptions(NewChannel->Number()); // to make them immediately visible in the channel display
4266  Channels.SwitchTo(NewChannel->Number());
4267  SetTrackDescriptions(NewChannel->Number()); // switching the channel has cleared them
4268  channel = NewChannel;
4269  }
4270  return osContinue;
4271  }
4272  return osEnd;
4273 }
4274 
4275 // --- cDisplayVolume --------------------------------------------------------
4276 
4277 #define VOLUMETIMEOUT 1000 //ms
4278 #define MUTETIMEOUT 5000 //ms
4279 
4281 
4283 :cOsdObject(true)
4284 {
4285  currentDisplayVolume = this;
4288  Show();
4289 }
4290 
4292 {
4293  delete displayVolume;
4294  currentDisplayVolume = NULL;
4295 }
4296 
4298 {
4300 }
4301 
4303 {
4304  if (!currentDisplayVolume)
4305  new cDisplayVolume;
4306  return currentDisplayVolume;
4307 }
4308 
4310 {
4313 }
4314 
4316 {
4317  switch (int(Key)) {
4318  case kVolUp|k_Repeat:
4319  case kVolUp:
4320  case kVolDn|k_Repeat:
4321  case kVolDn:
4322  Show();
4324  break;
4325  case kMute:
4326  if (cDevice::PrimaryDevice()->IsMute()) {
4327  Show();
4329  }
4330  else
4331  timeout.Set();
4332  break;
4333  case kNone: break;
4334  default: if ((Key & k_Release) == 0) {
4335  cRemote::Put(Key);
4336  return osEnd;
4337  }
4338  }
4339  return timeout.TimedOut() ? osEnd : osContinue;
4340 }
4341 
4342 // --- cDisplayTracks --------------------------------------------------------
4343 
4344 #define TRACKTIMEOUT 5000 //ms
4345 
4347 
4349 :cOsdObject(true)
4350 {
4352  SetTrackDescriptions(!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring() ? cDevice::CurrentChannel() : 0);
4353  currentDisplayTracks = this;
4354  numTracks = track = 0;
4356  eTrackType CurrentAudioTrack = cDevice::PrimaryDevice()->GetCurrentAudioTrack();
4357  for (int i = ttAudioFirst; i <= ttDolbyLast; i++) {
4358  const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(i));
4359  if (TrackId && TrackId->id) {
4360  types[numTracks] = eTrackType(i);
4361  descriptions[numTracks] = strdup(*TrackId->description ? TrackId->description : *TrackId->language ? TrackId->language : *itoa(i));
4362  if (i == CurrentAudioTrack)
4363  track = numTracks;
4364  numTracks++;
4365  }
4366  }
4367  descriptions[numTracks] = NULL;
4370  Show();
4371 }
4372 
4374 {
4375  delete displayTracks;
4376  currentDisplayTracks = NULL;
4377  for (int i = 0; i < numTracks; i++)
4378  free(descriptions[i]);
4380 }
4381 
4383 {
4384  int ac = IS_AUDIO_TRACK(types[track]) ? audioChannel : -1;
4387  displayTracks->Flush();
4390 }
4391 
4393 {
4394  if (cDevice::PrimaryDevice()->NumAudioTracks() > 0) {
4395  if (!currentDisplayTracks)
4396  new cDisplayTracks;
4397  return currentDisplayTracks;
4398  }
4399  Skins.Message(mtWarning, tr("No audio available!"));
4400  return NULL;
4401 }
4402 
4404 {
4407 }
4408 
4410 {
4411  int oldTrack = track;
4412  int oldAudioChannel = audioChannel;
4413  switch (int(Key)) {
4414  case kUp|k_Repeat:
4415  case kUp:
4416  case kDown|k_Repeat:
4417  case kDown:
4418  if (NORMALKEY(Key) == kUp && track > 0)
4419  track--;
4420  else if (NORMALKEY(Key) == kDown && track < numTracks - 1)
4421  track++;
4423  break;
4424  case kLeft|k_Repeat:
4425  case kLeft:
4426  case kRight|k_Repeat:
4427  case kRight: if (IS_AUDIO_TRACK(types[track])) {
4428  static int ac[] = { 1, 0, 2 };
4430  if (NORMALKEY(Key) == kLeft && audioChannel > 0)
4431  audioChannel--;
4432  else if (NORMALKEY(Key) == kRight && audioChannel < 2)
4433  audioChannel++;
4434  audioChannel = ac[audioChannel];
4436  }
4437  break;
4438  case kAudio|k_Repeat:
4439  case kAudio:
4440  if (++track >= numTracks)
4441  track = 0;
4443  break;
4444  case kOk:
4445  if (types[track] != cDevice::PrimaryDevice()->GetCurrentAudioTrack())
4446  oldTrack = -1; // make sure we explicitly switch to that track
4447  timeout.Set();
4448  break;
4449  case kNone: break;
4450  default: if ((Key & k_Release) == 0)
4451  return osEnd;
4452  }
4453  if (track != oldTrack || audioChannel != oldAudioChannel)
4454  Show();
4455  if (track != oldTrack) {
4458  }
4459  if (audioChannel != oldAudioChannel)
4461  return timeout.TimedOut() ? osEnd : osContinue;
4462 }
4463 
4464 // --- cDisplaySubtitleTracks ------------------------------------------------
4465 
4467 
4469 :cOsdObject(true)
4470 {
4471  SetTrackDescriptions(!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring() ? cDevice::CurrentChannel() : 0);
4472  currentDisplayTracks = this;
4473  numTracks = track = 0;
4474  types[numTracks] = ttNone;
4475  descriptions[numTracks] = strdup(tr("No subtitles"));
4476  numTracks++;
4477  eTrackType CurrentSubtitleTrack = cDevice::PrimaryDevice()->GetCurrentSubtitleTrack();
4478  for (int i = ttSubtitleFirst; i <= ttSubtitleLast; i++) {
4479  const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(i));
4480  if (TrackId && TrackId->id) {
4481  types[numTracks] = eTrackType(i);
4482  descriptions[numTracks] = strdup(*TrackId->description ? TrackId->description : *TrackId->language ? TrackId->language : *itoa(i));
4483  if (i == CurrentSubtitleTrack)
4484  track = numTracks;
4485  numTracks++;
4486  }
4487  }
4488  descriptions[numTracks] = NULL;
4490  displayTracks = Skins.Current()->DisplayTracks(tr("Button$Subtitles"), numTracks, descriptions);
4491  Show();
4492 }
4493 
4495 {
4496  delete displayTracks;
4497  currentDisplayTracks = NULL;
4498  for (int i = 0; i < numTracks; i++)
4499  free(descriptions[i]);
4501 }
4502 
4504 {
4506  displayTracks->Flush();
4508 }
4509 
4511 {
4512  if (cDevice::PrimaryDevice()->NumSubtitleTracks() > 0) {
4513  if (!currentDisplayTracks)
4515  return currentDisplayTracks;
4516  }
4517  Skins.Message(mtWarning, tr("No subtitles available!"));
4518  return NULL;
4519 }
4520 
4522 {
4525 }
4526 
4528 {
4529  int oldTrack = track;
4530  switch (int(Key)) {
4531  case kUp|k_Repeat:
4532  case kUp:
4533  case kDown|k_Repeat:
4534  case kDown:
4535  if (NORMALKEY(Key) == kUp && track > 0)
4536  track--;
4537  else if (NORMALKEY(Key) == kDown && track < numTracks - 1)
4538  track++;
4540  break;
4541  case kSubtitles|k_Repeat:
4542  case kSubtitles:
4543  if (++track >= numTracks)
4544  track = 0;
4546  break;
4547  case kOk:
4548  if (types[track] != cDevice::PrimaryDevice()->GetCurrentSubtitleTrack())
4549  oldTrack = -1; // make sure we explicitly switch to that track
4550  timeout.Set();
4551  break;
4552  case kNone: break;
4553  default: if ((Key & k_Release) == 0)
4554  return osEnd;
4555  }
4556  if (track != oldTrack) {
4557  Show();
4559  }
4560  return timeout.TimedOut() ? osEnd : osContinue;
4561 }
4562 
4563 // --- cRecordControl --------------------------------------------------------
4564 
4565 cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause)
4566 {
4567  // Whatever happens here, the timers will be modified in some way...
4568  Timers.SetModified();
4569  // We're going to manipulate an event here, so we need to prevent
4570  // others from modifying any EPG data:
4571  cSchedulesLock SchedulesLock;
4572  cSchedules::Schedules(SchedulesLock);
4573 
4574  event = NULL;
4575  fileName = NULL;
4576  recorder = NULL;
4577  device = Device;
4578  if (!device) device = cDevice::PrimaryDevice();//XXX
4579  timer = Timer;
4580  if (!timer) {
4581  timer = new cTimer(true, Pause);
4582  Timers.Add(timer);
4583  instantId = cString::sprintf(cDevice::NumDevices() > 1 ? "%s - %d" : "%s", timer->Channel()->Name(), device->CardIndex() + 1);
4584  }
4585  timer->SetPending(true);
4586  timer->SetRecording(true);
4587  event = timer->Event();
4588 
4589  if (event || GetEvent())
4590  dsyslog("Title: '%s' Subtitle: '%s'", event->Title(), event->ShortText());
4591  cRecording Recording(timer, event);
4592  fileName = strdup(Recording.FileName());
4593 
4594  // crude attempt to avoid duplicate recordings:
4596  isyslog("already recording: '%s'", fileName);
4597  if (Timer) {
4598  timer->SetPending(false);
4599  timer->SetRecording(false);
4600  timer->OnOff();
4601  }
4602  else {
4603  Timers.Del(timer);
4604  if (!cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo()
4606  }
4607  timer = NULL;
4608  return;
4609  }
4610 
4612  isyslog("record %s", fileName);
4613  if (MakeDirs(fileName, true)) {
4614  const cChannel *ch = timer->Channel();
4615  recorder = new cRecorder(fileName, ch, timer->Priority());
4616  if (device->AttachReceiver(recorder)) {
4617  Recording.WriteInfo();
4618  cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true);
4619  if (!Timer && !cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo()
4622  if (Timer && !Timer->IsSingleEvent()) {
4623  char *Directory = strdup(fileName);
4624  // going up two directory levels to get the series folder
4625  if (char *p = strrchr(Directory, '/')) {
4626  while (p > Directory && *--p != '/')
4627  ;
4628  *p = 0;
4629  if (!HasRecordingsSortMode(Directory)) {
4630  dsyslog("setting %s to be sorted by time", Directory);
4631  SetRecordingsSortMode(Directory, rsmTime);
4632  }
4633  }
4634  free(Directory);
4635  }
4636  return;
4637  }
4638  else
4640  }
4641  else
4643  if (!Timer) {
4644  Timers.Del(timer);
4645  timer = NULL;
4646  }
4647 }
4648 
4650 {
4651  Stop();
4652  free(fileName);
4653 }
4654 
4655 #define INSTANT_REC_EPG_LOOKAHEAD 300 // seconds to look into the EPG data for an instant recording
4656 
4658 {
4659  const cChannel *channel = timer->Channel();
4661  for (int seconds = 0; seconds <= MAXWAIT4EPGINFO; seconds++) {
4662  {
4663  cSchedulesLock SchedulesLock;
4664  const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
4665  if (Schedules) {
4666  const cSchedule *Schedule = Schedules->GetSchedule(channel);
4667  if (Schedule) {
4668  event = Schedule->GetEventAround(Time);
4669  if (event) {
4670  if (seconds > 0)
4671  dsyslog("got EPG info after %d seconds", seconds);
4672  return true;
4673  }
4674  }
4675  }
4676  }
4677  if (seconds == 0)
4678  dsyslog("waiting for EPG info...");
4679  cCondWait::SleepMs(1000);
4680  }
4681  dsyslog("no EPG info available");
4682  return false;
4683 }
4684 
4685 void cRecordControl::Stop(bool ExecuteUserCommand)
4686 {
4687  if (timer) {
4689  timer->SetRecording(false);
4690  timer = NULL;
4691  cStatus::MsgRecording(device, NULL, fileName, false);
4692  if (ExecuteUserCommand)
4694  Timers.SetModified();
4695  }
4696 }
4697 
4699 {
4700  if (!recorder || !recorder->IsAttached() || !timer || !timer->Matches(t)) {
4701  if (timer)
4702  timer->SetPending(false);
4703  return false;
4704  }
4706  return true;
4707 }
4708 
4709 // --- cRecordControls -------------------------------------------------------
4710 
4712 int cRecordControls::state = 0;
4713 
4714 bool cRecordControls::Start(cTimer *Timer, bool Pause)
4715 {
4716  static time_t LastNoDiskSpaceMessage = 0;
4717  int FreeMB = 0;
4718  if (Timer) {
4719  AssertFreeDiskSpace(Timer->Priority(), !Timer->Pending());
4720  Timer->SetPending(true);
4721  }
4722  VideoDiskSpace(&FreeMB);
4723  if (FreeMB < MINFREEDISK) {
4724  if (!Timer || time(NULL) - LastNoDiskSpaceMessage > NODISKSPACEDELTA) {
4725  isyslog("not enough disk space to start recording%s%s", Timer ? " timer " : "", Timer ? *Timer->ToDescr() : "");
4726  Skins.Message(mtWarning, tr("Not enough disk space to start recording!"));
4727  LastNoDiskSpaceMessage = time(NULL);
4728  }
4729  return false;
4730  }
4731  LastNoDiskSpaceMessage = 0;
4732 
4733  ChangeState();
4734  int ch = Timer ? Timer->Channel()->Number() : cDevice::CurrentChannel();
4735  cChannel *channel = Channels.GetByNumber(ch);
4736 
4737  if (channel) {
4738  int Priority = Timer ? Timer->Priority() : Pause ? Setup.PausePriority : Setup.DefaultPriority;
4739  cDevice *device = cDevice::GetDevice(channel, Priority, false);
4740  if (device) {
4741  dsyslog("switching device %d to channel %d", device->DeviceNumber() + 1, channel->Number());
4742  if (!device->SwitchChannel(channel, false)) {
4744  return false;
4745  }
4746  if (!Timer || Timer->Matches()) {
4747  for (int i = 0; i < MAXRECORDCONTROLS; i++) {
4748  if (!RecordControls[i]) {
4749  RecordControls[i] = new cRecordControl(device, Timer, Pause);
4750  return RecordControls[i]->Process(time(NULL));
4751  }
4752  }
4753  }
4754  }
4755  else if (!Timer || !Timer->Pending()) {
4756  isyslog("no free DVB device to record channel %d!", ch);
4757  Skins.Message(mtError, tr("No free DVB device to record!"));
4758  }
4759  }
4760  else
4761  esyslog("ERROR: channel %d not defined!", ch);
4762  return false;
4763 }
4764 
4765 void cRecordControls::Stop(const char *InstantId)
4766 {
4767  ChangeState();
4768  for (int i = 0; i < MAXRECORDCONTROLS; i++) {
4769  if (RecordControls[i]) {
4770  const char *id = RecordControls[i]->InstantId();
4771  if (id && strcmp(id, InstantId) == 0) {
4772  cTimer *timer = RecordControls[i]->Timer();
4773  RecordControls[i]->Stop();
4774  if (timer) {
4775  isyslog("deleting timer %s", *timer->ToDescr());
4776  Timers.Del(timer);
4777  Timers.SetModified();
4778  }
4779  break;
4780  }
4781  }
4782  }
4783 }
4784 
4786 {
4787  Skins.Message(mtStatus, tr("Pausing live video..."));
4788  cReplayControl::SetRecording(NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed()
4789  if (Start(NULL, true)) {
4790  cReplayControl *rc = new cReplayControl(true);
4791  cControl::Launch(rc);
4792  cControl::Attach();
4793  Skins.Message(mtStatus, NULL);
4794  return true;
4795  }
4796  Skins.Message(mtStatus, NULL);
4797  return false;
4798 }
4799 
4800 const char *cRecordControls::GetInstantId(const char *LastInstantId)
4801 {
4802  for (int i = 0; i < MAXRECORDCONTROLS; i++) {
4803  if (RecordControls[i]) {
4804  if (!LastInstantId && RecordControls[i]->InstantId())
4805  return RecordControls[i]->InstantId();
4806  if (LastInstantId && LastInstantId == RecordControls[i]->InstantId())
4807  LastInstantId = NULL;
4808  }
4809  }
4810  return NULL;
4811 }
4812 
4814 {
4815  if (FileName) {
4816  for (int i = 0; i < MAXRECORDCONTROLS; i++) {
4817  if (RecordControls[i] && strcmp(RecordControls[i]->FileName(), FileName) == 0)
4818  return RecordControls[i];
4819  }
4820  }
4821  return NULL;
4822 }
4823 
4825 {
4826  for (int i = 0; i < MAXRECORDCONTROLS; i++) {
4827  if (RecordControls[i] && RecordControls[i]->Timer() == Timer)
4828  return RecordControls[i];
4829  }
4830  return NULL;
4831 }
4832 
4834 {
4835  for (int i = 0; i < MAXRECORDCONTROLS; i++) {
4836  if (RecordControls[i]) {
4837  if (!RecordControls[i]->Process(t)) {
4839  ChangeState();
4840  }
4841  }
4842  }
4843 }
4844 
4846 {
4847  for (int i = 0; i < MAXRECORDCONTROLS; i++) {
4848  if (RecordControls[i]) {
4849  if (RecordControls[i]->Timer() && RecordControls[i]->Timer()->Channel() == Channel) {
4850  if (RecordControls[i]->Device()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
4851  isyslog("stopping recording due to modification of channel %d", Channel->Number());
4852  RecordControls[i]->Stop();
4853  // This will restart the recording, maybe even from a different
4854  // device in case conditional access has changed.
4855  ChangeState();
4856  }
4857  }
4858  }
4859  }
4860 }
4861 
4863 {
4864  for (int i = 0; i < MAXRECORDCONTROLS; i++) {
4865  if (RecordControls[i])
4866  return true;
4867  }
4868  return false;
4869 }
4870 
4872 {
4873  for (int i = 0; i < MAXRECORDCONTROLS; i++)
4875  ChangeState();
4876 }
4877 
4879 {
4880  int NewState = state;
4881  bool Result = State != NewState;
4882  State = state;
4883  return Result;
4884 }
4885 
4886 // --- cReplayControl --------------------------------------------------------
4887 
4888 #define REPLAYCONTROLSKIPLIMIT 9 // s
4889 #define REPLAYCONTROLSKIPSECONDS 90 // s
4890 #define REPLAYCONTROLSKIPTIMEOUT 5000 // ms
4891 
4894 
4896 :cDvbPlayerControl(fileName, PauseLive)
4897 {
4898  currentReplayControl = this;
4899  displayReplay = NULL;
4900  marksModified = false;
4901  visible = modeOnly = shown = displayFrames = false;
4902  lastCurrent = lastTotal = -1;
4903  lastPlay = lastForward = false;
4904  lastSpeed = -2; // an invalid value
4905  lastSkipKey = kNone;
4907  lastSkipTimeout.Set(0);
4908  timeoutShow = 0;
4909  timeSearchActive = false;
4910  cRecording Recording(fileName);
4911  cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true);
4912  marks.Load(fileName, Recording.FramesPerSecond(), Recording.IsPesRecording());
4913  SetTrackDescriptions(false);
4914 }
4915 
4917 {
4918  Hide();
4919  cStatus::MsgReplaying(this, NULL, fileName, false);
4920  Stop();
4921  if (marksModified) {
4922  marks.Save();
4923  marksModified = false;
4924  }
4925  if (currentReplayControl == this)
4926  currentReplayControl = NULL;
4927 }
4928 
4930 {
4931  if (Setup.DelTimeshiftRec && *fileName) {
4933  if (rc && rc->InstantId()) {
4934  if (Active()) {
4935  if (Setup.DelTimeshiftRec == 2 || Interface->Confirm(tr("Delete timeshift recording?"))) {
4936  cTimer *timer = rc->Timer();
4937  rc->Stop(false); // don't execute user command
4938  if (timer) {
4939  isyslog("deleting timer %s", *timer->ToDescr());
4940  Timers.Del(timer);
4941  Timers.SetModified();
4942  }
4944  cRecording *recording = Recordings.GetByName(fileName);
4945  if (recording) {
4946  if (recording->Delete()) {
4949  }
4950  else
4951  Skins.Message(mtError, tr("Error while deleting recording!"));
4952  }
4953  return;
4954  }
4955  }
4956  }
4957  }
4959 }
4960 
4961 void cReplayControl::SetRecording(const char *FileName)
4962 {
4963  fileName = FileName;
4964 }
4965 
4967 {
4968  return currentReplayControl ? *fileName : NULL;
4969 }
4970 
4972 {
4973  return fileName;
4974 }
4975 
4976 void cReplayControl::ClearLastReplayed(const char *FileName)
4977 {
4978  if (*fileName && FileName && strcmp(fileName, FileName) == 0)
4979  fileName = NULL;
4980 }
4981 
4982 void cReplayControl::ShowTimed(int Seconds)
4983 {
4984  if (modeOnly)
4985  Hide();
4986  if (!visible) {
4987  shown = ShowProgress(true);
4988  timeoutShow = (shown && Seconds > 0) ? time(NULL) + Seconds : 0;
4989  }
4990  else if (timeoutShow && Seconds > 0)
4991  timeoutShow = time(NULL) + Seconds;
4992 }
4993 
4995 {
4996  ShowTimed();
4997 }
4998 
5000 {
5001  if (visible) {
5002  delete displayReplay;
5003  displayReplay = NULL;
5004  SetNeedsFastResponse(false);
5005  visible = false;
5006  modeOnly = false;
5007  lastPlay = lastForward = false;
5008  lastSpeed = -2; // an invalid value
5009  timeSearchActive = false;
5010  timeoutShow = 0;
5011  }
5012 }
5013 
5015 {
5016  if (visible || Setup.ShowReplayMode && !cOsd::IsOpen()) {
5017  bool Play, Forward;
5018  int Speed;
5019  if (GetReplayMode(Play, Forward, Speed) && (!visible || Play != lastPlay || Forward != lastForward || Speed != lastSpeed)) {
5020  bool NormalPlay = (Play && Speed == -1);
5021 
5022  if (!visible) {
5023  if (NormalPlay)
5024  return; // no need to do indicate ">" unless there was a different mode displayed before
5025  visible = modeOnly = true;
5027  }
5028 
5029  if (modeOnly && !timeoutShow && NormalPlay)
5030  timeoutShow = time(NULL) + MODETIMEOUT;
5031  displayReplay->SetMode(Play, Forward, Speed);
5032  lastPlay = Play;
5033  lastForward = Forward;
5034  lastSpeed = Speed;
5035  }
5036  }
5037 }
5038 
5040 {
5041  int Current, Total;
5042 
5043  if (GetIndex(Current, Total) && Total > 0) {
5044  if (!visible) {
5047  SetNeedsFastResponse(true);
5048  visible = true;
5049  }
5050  if (Initial) {
5051  if (*fileName) {
5052  if (cRecording *Recording = Recordings.GetByName(fileName))
5053  displayReplay->SetRecording(Recording);
5054  }
5055  lastCurrent = lastTotal = -1;
5056  }
5057  if (Current != lastCurrent || Total != lastTotal) {
5058  if (Setup.ShowRemainingTime || Total != lastTotal) {
5059  int Index = Total;
5061  Index = Current - Index;
5063  if (!Initial)
5064  displayReplay->Flush();
5065  }
5066  displayReplay->SetProgress(Current, Total);
5067  if (!Initial)
5068  displayReplay->Flush();
5070  displayReplay->Flush();
5071  lastCurrent = Current;
5072  }
5073  lastTotal = Total;
5074  ShowMode();
5075  return true;
5076  }
5077  return false;
5078 }
5079 
5081 {
5082  char buf[64];
5083  // TRANSLATORS: note the trailing blank!
5084  strcpy(buf, tr("Jump: "));
5085  int len = strlen(buf);
5086  char h10 = '0' + (timeSearchTime >> 24);
5087  char h1 = '0' + ((timeSearchTime & 0x00FF0000) >> 16);
5088  char m10 = '0' + ((timeSearchTime & 0x0000FF00) >> 8);
5089  char m1 = '0' + (timeSearchTime & 0x000000FF);
5090  char ch10 = timeSearchPos > 3 ? h10 : '-';
5091  char ch1 = timeSearchPos > 2 ? h1 : '-';
5092  char cm10 = timeSearchPos > 1 ? m10 : '-';
5093  char cm1 = timeSearchPos > 0 ? m1 : '-';
5094  sprintf(buf + len, "%c%c:%c%c", ch10, ch1, cm10, cm1);
5095  displayReplay->SetJump(buf);
5096 }
5097 
5099 {
5100 #define STAY_SECONDS_OFF_END 10
5101  int Seconds = (timeSearchTime >> 24) * 36000 + ((timeSearchTime & 0x00FF0000) >> 16) * 3600 + ((timeSearchTime & 0x0000FF00) >> 8) * 600 + (timeSearchTime & 0x000000FF) * 60;
5102  int Current = int(round(lastCurrent / FramesPerSecond()));
5103  int Total = int(round(lastTotal / FramesPerSecond()));
5104  switch (Key) {
5105  case k0 ... k9:
5106  if (timeSearchPos < 4) {
5107  timeSearchTime <<= 8;
5108  timeSearchTime |= Key - k0;
5109  timeSearchPos++;
5111  }
5112  break;
5113  case kFastRew:
5114  case kLeft:
5115  case kFastFwd:
5116  case kRight: {
5117  int dir = ((Key == kRight || Key == kFastFwd) ? 1 : -1);
5118  if (dir > 0)
5119  Seconds = min(Total - Current - STAY_SECONDS_OFF_END, Seconds);
5120  SkipSeconds(Seconds * dir);
5121  timeSearchActive = false;
5122  }
5123  break;
5124  case kPlay:
5125  case kUp:
5126  case kPause:
5127  case kDown:
5128  case kOk:
5129  Seconds = min(Total - STAY_SECONDS_OFF_END, Seconds);
5130  Goto(SecondsToFrames(Seconds, FramesPerSecond()), Key == kDown || Key == kPause || Key == kOk);
5131  timeSearchActive = false;
5132  break;
5133  default:
5134  if (!(Key & k_Flags)) // ignore repeat/release keys
5135  timeSearchActive = false;
5136  break;
5137  }
5138 
5139  if (!timeSearchActive) {
5140  if (timeSearchHide)
5141  Hide();
5142  else
5143  displayReplay->SetJump(NULL);
5144  ShowMode();
5145  }
5146 }
5147 
5149 {
5151  timeSearchHide = false;
5152  if (modeOnly)
5153  Hide();
5154  if (!visible) {
5155  Show();
5156  if (visible)
5157  timeSearchHide = true;
5158  else
5159  return;
5160  }
5161  timeoutShow = 0;
5163  timeSearchActive = true;
5164 }
5165 
5167 {
5168  int Current, Total;
5169  if (GetIndex(Current, Total, true)) {
5170  cMark *m = marks.Get(Current);
5171  lastCurrent = -1; // triggers redisplay
5172  if (m)
5173  marks.Del(m);
5174  else {
5175  marks.Add(Current);
5176  bool Play, Forward;
5177  int Speed;
5178  if (GetReplayMode(Play, Forward, Speed) && !Play)
5179  Goto(Current, true);
5180  }
5181  ShowTimed(2);
5182  marksModified = true;
5183  }
5184 }
5185 
5186 void cReplayControl::MarkJump(bool Forward)
5187 {
5188  if (marks.Count()) {
5189  int Current, Total;
5190  if (GetIndex(Current, Total)) {
5191  cMark *m = Forward ? marks.GetNext(Current) : marks.GetPrev(Current);
5192  if (m) {
5193  bool Play2, Forward2;
5194  int Speed;
5195  if (Setup.JumpPlay && GetReplayMode(Play2, Forward2, Speed) &&
5196  Play2 && Forward && m->Position() < Total - SecondsToFrames(3, FramesPerSecond())) {
5197  Goto(m->Position());
5198  Play();
5199  }
5200  else {
5201  Goto(m->Position(), true);
5202  displayFrames = true;
5203  }
5204  }
5205  }
5206  }
5207 }
5208 
5209 void cReplayControl::MarkMove(bool Forward)
5210 {
5211  int Current, Total;
5212  if (GetIndex(Current, Total)) {
5213  cMark *m = marks.Get(Current);
5214  if (m) {
5215  displayFrames = true;
5216  int p = SkipFrames(Forward ? 1 : -1);
5217  cMark *m2;
5218  if (Forward) {
5219  if ((m2 = marks.Next(m)) != NULL && m2->Position() <= p)
5220  return;
5221  }
5222  else {
5223  if ((m2 = marks.Prev(m)) != NULL && m2->Position() >= p)
5224  return;
5225  }
5226  m->SetPosition(p);
5227  Goto(m->Position(), true);
5228  marksModified = true;
5229  }
5230  }
5231 }
5232 
5234 {
5235  if (*fileName) {
5236  Hide();
5237  if (marksModified) {
5238  marks.Save();
5239  marksModified = false;
5240  }
5241  if (!cCutter::Active()) {
5242  if (!marks.Count())
5243  Skins.Message(mtError, tr("No editing marks defined!"));
5244  else if (!cCutter::Start(fileName, NULL, false))
5245  Skins.Message(mtError, tr("Can't start editing process!"));
5246  else
5247  Skins.Message(mtInfo, tr("Editing process started"));
5248  }
5249  else
5250  Skins.Message(mtError, tr("Editing process already active!"));
5251  ShowMode();
5252  }
5253 }
5254 
5256 {
5257  int Current, Total;
5258  if (GetIndex(Current, Total)) {
5259  cMark *m = marks.Get(Current);
5260  if (!m)
5261  m = marks.GetNext(Current);
5262  if (m) {
5263  if ((m->Index() & 0x01) != 0 && !Setup.PlayJump)
5264  m = marks.Next(m);
5265  if (m) {
5267  Play();
5268  }
5269  }
5270  }
5271 }
5272 
5274 {
5276  if (Recording)
5277  return new cMenuRecording(Recording, false);
5278  return NULL;
5279 }
5280 
5282 {
5283  if (const cRecording *Recording = Recordings.GetByName(LastReplayed()))
5284  return Recording;
5285  return NULL;
5286 }
5287 
5289 {
5290  if (!Active())
5291  return osEnd;
5292  if (Key == kNone)
5293  marks.Update();
5294  if (visible) {
5295  if (timeoutShow && time(NULL) > timeoutShow) {
5296  Hide();
5297  ShowMode();
5298  timeoutShow = 0;
5299  }
5300  else if (modeOnly)
5301  ShowMode();
5302  else
5303  shown = ShowProgress(!shown) || shown;
5304  }
5305  bool DisplayedFrames = displayFrames;
5306  displayFrames = false;
5307  if (timeSearchActive && Key != kNone) {
5308  TimeSearchProcess(Key);
5309  return osContinue;
5310  }
5311  bool DoShowMode = true;
5312  switch (int(Key)) {
5313  // Positioning:
5314  case kPlay:
5315  case kUp: Play(); break;
5316  case kPause:
5317  case kDown: Pause(); break;
5318  case kFastRew|k_Release:
5319  case kLeft|k_Release:
5320  if (Setup.MultiSpeedMode) break;
5321  case kFastRew:
5322  case kLeft: Backward(); break;
5323  case kFastFwd|k_Release:
5324  case kRight|k_Release:
5325  if (Setup.MultiSpeedMode) break;
5326  case kFastFwd:
5327  case kRight: Forward(); break;
5328  case kRed: TimeSearch(); break;
5329  case kGreen|k_Repeat:
5330  case kGreen: SkipSeconds(-60); break;
5331  case kYellow|k_Repeat:
5332  case kYellow: SkipSeconds( 60); break;
5333  case k1|k_Repeat:
5334  case k1: SkipSeconds(-20); break;
5335  case k3|k_Repeat:
5336  case k3: SkipSeconds( 20); break;
5337  case kPrev|k_Repeat:
5338  case kPrev: if (lastSkipTimeout.TimedOut()) {
5340  lastSkipKey = kPrev;
5341  }
5343  lastSkipSeconds /= 2;
5344  lastSkipKey = kNone;
5345  }
5347  SkipSeconds(-lastSkipSeconds); break;
5348  case kNext|k_Repeat:
5349  case kNext: if (lastSkipTimeout.TimedOut()) {
5351  lastSkipKey = kNext;
5352  }
5354  lastSkipSeconds /= 2;
5355  lastSkipKey = kNone;
5356  }
5358  SkipSeconds(lastSkipSeconds); break;
5359  case kStop:
5360  case kBlue: Hide();
5361  Stop();
5362  return osEnd;
5363  default: {
5364  DoShowMode = false;
5365  switch (int(Key)) {
5366  // Editing:
5367  case kMarkToggle: MarkToggle(); break;
5368  case kMarkJumpBack|k_Repeat:
5369  case kMarkJumpBack: MarkJump(false); break;
5371  case kMarkJumpForward: MarkJump(true); break;
5372  case kMarkMoveBack|k_Repeat:
5373  case kMarkMoveBack: MarkMove(false); break;
5375  case kMarkMoveForward: MarkMove(true); break;
5376  case kEditCut: EditCut(); break;
5377  case kEditTest: EditTest(); break;
5378  default: {
5379  displayFrames = DisplayedFrames;
5380  switch (Key) {
5381  // Menu control:
5382  case kOk: if (visible && !modeOnly) {
5383  Hide();
5384  DoShowMode = true;
5385  }
5386  else
5387  Show();
5388  break;
5389  case kBack: if (Setup.DelTimeshiftRec) {
5391  return rc && rc->InstantId() ? osEnd : osRecordings;
5392  }
5393  return osRecordings;
5394  default: return osUnknown;
5395  }
5396  }
5397  }
5398  }
5399  }
5400  if (DoShowMode)
5401  ShowMode();
5402  return osContinue;
5403 }