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