vdr  1.7.27
menu.c
Go to the documentation of this file.
00001 /*
00002  * menu.c: The actual menu implementations
00003  *
00004  * See the main source file 'vdr.c' for copyright information and
00005  * how to reach the author.
00006  *
00007  * $Id: menu.c 2.45 2012/03/13 13:14:38 kls Exp $
00008  */
00009 
00010 #include "menu.h"
00011 #include <ctype.h>
00012 #include <limits.h>
00013 #include <math.h>
00014 #include <stdio.h>
00015 #include <stdlib.h>
00016 #include <string.h>
00017 #include "channels.h"
00018 #include "config.h"
00019 #include "cutter.h"
00020 #include "eitscan.h"
00021 #include "filetransfer.h"
00022 #include "i18n.h"
00023 #include "interface.h"
00024 #include "plugin.h"
00025 #include "recording.h"
00026 #include "remote.h"
00027 #include "shutdown.h"
00028 #include "sourceparams.h"
00029 #include "sources.h"
00030 #include "status.h"
00031 #include "themes.h"
00032 #include "timers.h"
00033 #include "transfer.h"
00034 #include "videodir.h"
00035 
00036 #define MAXWAIT4EPGINFO   3 // seconds
00037 #define MODETIMEOUT       3 // seconds
00038 #define DISKSPACECHEK     5 // seconds between disk space checks
00039 #define NEWTIMERLIMIT   120 // seconds until the start time of a new timer created from the Schedule menu,
00040                             // within which it will go directly into the "Edit timer" menu to allow
00041                             // further parameter settings
00042 #define DEFERTIMER       60 // seconds by which a timer is deferred in case of problems
00043 
00044 #define MAXRECORDCONTROLS (MAXDEVICES * MAXRECEIVERS)
00045 #define MAXINSTANTRECTIME (24 * 60 - 1) // 23:59 hours
00046 #define MAXWAITFORCAMMENU  10 // seconds to wait for the CAM menu to open
00047 #define CAMMENURETYTIMEOUT  3 // seconds after which opening the CAM menu is retried
00048 #define CAMRESPONSETIMEOUT  5 // seconds to wait for a response from a CAM
00049 #define MINFREEDISK       300 // minimum free disk space (in MB) required to start recording
00050 #define NODISKSPACEDELTA  300 // seconds between "Not enough disk space to start recording!" messages
00051 
00052 #define CHNUMWIDTH  (numdigits(Channels.MaxNumber()) + 1)
00053 #define CHNAMWIDTH  (Channels.MaxShortChannelNameLength() + 1)
00054 
00055 // --- cFreeDiskSpace --------------------------------------------------------
00056 
00057 #define MB_PER_MINUTE 25.75 // this is just an estimate!
00058 
00059 class cFreeDiskSpace {
00060 private:
00061   static time_t lastDiskSpaceCheck;
00062   static int lastFreeMB;
00063   static cString freeDiskSpaceString;
00064 public:
00065   static bool HasChanged(bool ForceCheck = false);
00066   static const char *FreeDiskSpaceString(void) { HasChanged(); return freeDiskSpaceString; }
00067   };
00068 
00069 time_t cFreeDiskSpace::lastDiskSpaceCheck = 0;
00070 int cFreeDiskSpace::lastFreeMB = 0;
00071 cString cFreeDiskSpace::freeDiskSpaceString;
00072 
00073 cFreeDiskSpace FreeDiskSpace;
00074 
00075 bool cFreeDiskSpace::HasChanged(bool ForceCheck)
00076 {
00077   if (ForceCheck || time(NULL) - lastDiskSpaceCheck > DISKSPACECHEK) {
00078      int FreeMB;
00079      int Percent = VideoDiskSpace(&FreeMB);
00080      lastDiskSpaceCheck = time(NULL);
00081      if (ForceCheck || FreeMB != lastFreeMB) {
00082         int MBperMinute = Recordings.MBperMinute();
00083         if (MBperMinute <= 0)
00084            MBperMinute = MB_PER_MINUTE;
00085         int Minutes = int(double(FreeMB) / MBperMinute);
00086         int Hours = Minutes / 60;
00087         Minutes %= 60;
00088         freeDiskSpaceString = cString::sprintf("%s %d%%  -  %2d:%02d %s", tr("Disk"), Percent, Hours, Minutes, tr("free"));
00089         lastFreeMB = FreeMB;
00090         return true;
00091         }
00092      }
00093   return false;
00094 }
00095 
00096 // --- cMenuEditCaItem -------------------------------------------------------
00097 
00098 class cMenuEditCaItem : public cMenuEditIntItem {
00099 protected:
00100   virtual void Set(void);
00101 public:
00102   cMenuEditCaItem(const char *Name, int *Value);
00103   eOSState ProcessKey(eKeys Key);
00104   };
00105 
00106 cMenuEditCaItem::cMenuEditCaItem(const char *Name, int *Value)
00107 :cMenuEditIntItem(Name, Value, 0)
00108 {
00109   Set();
00110 }
00111 
00112 void cMenuEditCaItem::Set(void)
00113 {
00114   if (*value == CA_FTA)
00115      SetValue(tr("Free To Air"));
00116   else if (*value >= CA_ENCRYPTED_MIN)
00117      SetValue(tr("encrypted"));
00118   else
00119      cMenuEditIntItem::Set();
00120 }
00121 
00122 eOSState cMenuEditCaItem::ProcessKey(eKeys Key)
00123 {
00124   eOSState state = cMenuEditItem::ProcessKey(Key);
00125 
00126   if (state == osUnknown) {
00127      if (NORMALKEY(Key) == kLeft && *value >= CA_ENCRYPTED_MIN)
00128         *value = CA_FTA;
00129      else
00130         return cMenuEditIntItem::ProcessKey(Key);
00131      Set();
00132      state = osContinue;
00133      }
00134   return state;
00135 }
00136 
00137 // --- cMenuEditSrcItem ------------------------------------------------------
00138 
00139 class cMenuEditSrcItem : public cMenuEditIntItem {
00140 private:
00141   const cSource *source;
00142 protected:
00143   virtual void Set(void);
00144 public:
00145   cMenuEditSrcItem(const char *Name, int *Value);
00146   eOSState ProcessKey(eKeys Key);
00147   };
00148 
00149 cMenuEditSrcItem::cMenuEditSrcItem(const char *Name, int *Value)
00150 :cMenuEditIntItem(Name, Value, 0)
00151 {
00152   source = Sources.Get(*Value);
00153   Set();
00154 }
00155 
00156 void cMenuEditSrcItem::Set(void)
00157 {
00158   if (source)
00159      SetValue(cString::sprintf("%s - %s", *cSource::ToString(source->Code()), source->Description()));
00160   else
00161      cMenuEditIntItem::Set();
00162 }
00163 
00164 eOSState cMenuEditSrcItem::ProcessKey(eKeys Key)
00165 {
00166   eOSState state = cMenuEditItem::ProcessKey(Key);
00167 
00168   if (state == osUnknown) {
00169      bool IsRepeat = Key & k_Repeat;
00170      Key = NORMALKEY(Key);
00171      if (Key == kLeft) { // TODO might want to increase the delta if repeated quickly?
00172         if (source) {
00173            if (source->Prev())
00174               source = (cSource *)source->Prev();
00175            else if (!IsRepeat)
00176               source = Sources.Last();
00177            *value = source->Code();
00178            }
00179         }
00180      else if (Key == kRight) {
00181         if (source) {
00182            if (source->Next())
00183               source = (cSource *)source->Next();
00184            else if (!IsRepeat)
00185               source = Sources.First();
00186            }
00187         else
00188            source = Sources.First();
00189         if (source)
00190            *value = source->Code();
00191         }
00192      else
00193         return state; // we don't call cMenuEditIntItem::ProcessKey(Key) here since we don't accept numerical input
00194      Set();
00195      state = osContinue;
00196      }
00197   return state;
00198 }
00199 
00200 // --- cMenuEditChannel ------------------------------------------------------
00201 
00202 class cMenuEditChannel : public cOsdMenu {
00203 private:
00204   cChannel *channel;
00205   cChannel data;
00206   cSourceParam *sourceParam;
00207   char name[256];
00208   void Setup(void);
00209 public:
00210   cMenuEditChannel(cChannel *Channel, bool New = false);
00211   virtual eOSState ProcessKey(eKeys Key);
00212   };
00213 
00214 cMenuEditChannel::cMenuEditChannel(cChannel *Channel, bool New)
00215 :cOsdMenu(tr("Edit channel"), 16)
00216 {
00217   channel = Channel;
00218   sourceParam = NULL;
00219   *name = 0;
00220   if (channel) {
00221      data = *channel;
00222      strn0cpy(name, data.name, sizeof(name));
00223      if (New) {
00224         channel = NULL;
00225         data.nid = 0;
00226         data.tid = 0;
00227         data.rid = 0;
00228         }
00229      }
00230   Setup();
00231 }
00232 
00233 void cMenuEditChannel::Setup(void)
00234 {
00235   int current = Current();
00236 
00237   Clear();
00238 
00239   // Parameters for all types of sources:
00240   Add(new cMenuEditStrItem( tr("Name"),          name, sizeof(name)));
00241   Add(new cMenuEditSrcItem( tr("Source"),       &data.source));
00242   Add(new cMenuEditIntItem( tr("Frequency"),    &data.frequency));
00243   Add(new cMenuEditIntItem( tr("Vpid"),         &data.vpid,  0, 0x1FFF));
00244   Add(new cMenuEditIntItem( tr("Ppid"),         &data.ppid,  0, 0x1FFF));
00245   Add(new cMenuEditIntItem( tr("Apid1"),        &data.apids[0], 0, 0x1FFF));
00246   Add(new cMenuEditIntItem( tr("Apid2"),        &data.apids[1], 0, 0x1FFF));
00247   Add(new cMenuEditIntItem( tr("Dpid1"),        &data.dpids[0], 0, 0x1FFF));
00248   Add(new cMenuEditIntItem( tr("Dpid2"),        &data.dpids[1], 0, 0x1FFF));
00249   Add(new cMenuEditIntItem( tr("Spid1"),        &data.spids[0], 0, 0x1FFF));
00250   Add(new cMenuEditIntItem( tr("Spid2"),        &data.spids[1], 0, 0x1FFF));
00251   Add(new cMenuEditIntItem( tr("Tpid"),         &data.tpid,  0, 0x1FFF));
00252   Add(new cMenuEditCaItem(  tr("CA"),           &data.caids[0]));
00253   Add(new cMenuEditIntItem( tr("Sid"),          &data.sid, 1, 0xFFFF));
00254   /* XXX not yet used
00255   Add(new cMenuEditIntItem( tr("Nid"),          &data.nid, 0));
00256   Add(new cMenuEditIntItem( tr("Tid"),          &data.tid, 0));
00257   Add(new cMenuEditIntItem( tr("Rid"),          &data.rid, 0));
00258   XXX*/
00259   // Parameters for specific types of sources:
00260   sourceParam = SourceParams.Get(**cSource::ToString(data.source));
00261   if (sourceParam) {
00262      sourceParam->SetData(&data);
00263      cOsdItem *Item;
00264      while ((Item = sourceParam->GetOsdItem()) != NULL)
00265            Add(Item);
00266      }
00267 
00268   SetCurrent(Get(current));
00269   Display();
00270 }
00271 
00272 eOSState cMenuEditChannel::ProcessKey(eKeys Key)
00273 {
00274   int oldSource = data.source;
00275   eOSState state = cOsdMenu::ProcessKey(Key);
00276 
00277   if (state == osUnknown) {
00278      if (Key == kOk) {
00279         if (sourceParam)
00280            sourceParam->GetData(&data);
00281         if (Channels.HasUniqueChannelID(&data, channel)) {
00282            data.name = strcpyrealloc(data.name, name);
00283            if (channel) {
00284               *channel = data;
00285               isyslog("edited channel %d %s", channel->Number(), *data.ToText());
00286               state = osBack;
00287               }
00288            else {
00289               channel = new cChannel;
00290               *channel = data;
00291               Channels.Add(channel);
00292               Channels.ReNumber();
00293               isyslog("added channel %d %s", channel->Number(), *data.ToText());
00294               state = osUser1;
00295               }
00296            Channels.SetModified(true);
00297            }
00298         else {
00299            Skins.Message(mtError, tr("Channel settings are not unique!"));
00300            state = osContinue;
00301            }
00302         }
00303      }
00304   if (Key != kNone && (data.source & cSource::st_Mask) != (oldSource & cSource::st_Mask)) {
00305      if (sourceParam)
00306         sourceParam->GetData(&data);
00307      Setup();
00308      }
00309   return state;
00310 }
00311 
00312 // --- cMenuChannelItem ------------------------------------------------------
00313 
00314 class cMenuChannelItem : public cOsdItem {
00315 public:
00316   enum eChannelSortMode { csmNumber, csmName, csmProvider };
00317 private:
00318   static eChannelSortMode sortMode;
00319   cChannel *channel;
00320 public:
00321   cMenuChannelItem(cChannel *Channel);
00322   static void SetSortMode(eChannelSortMode SortMode) { sortMode = SortMode; }
00323   static void IncSortMode(void) { sortMode = eChannelSortMode((sortMode == csmProvider) ? csmNumber : sortMode + 1); }
00324   static eChannelSortMode SortMode(void) { return sortMode; }
00325   virtual int Compare(const cListObject &ListObject) const;
00326   virtual void Set(void);
00327   cChannel *Channel(void) { return channel; }
00328   };
00329 
00330 cMenuChannelItem::eChannelSortMode cMenuChannelItem::sortMode = csmNumber;
00331 
00332 cMenuChannelItem::cMenuChannelItem(cChannel *Channel)
00333 {
00334   channel = Channel;
00335   if (channel->GroupSep())
00336      SetSelectable(false);
00337   Set();
00338 }
00339 
00340 int cMenuChannelItem::Compare(const cListObject &ListObject) const
00341 {
00342   cMenuChannelItem *p = (cMenuChannelItem *)&ListObject;
00343   int r = -1;
00344   if (sortMode == csmProvider)
00345      r = strcoll(channel->Provider(), p->channel->Provider());
00346   if (sortMode == csmName || r == 0)
00347      r = strcoll(channel->Name(), p->channel->Name());
00348   if (sortMode == csmNumber || r == 0)
00349      r = channel->Number() - p->channel->Number();
00350   return r;
00351 }
00352 
00353 void cMenuChannelItem::Set(void)
00354 {
00355   cString buffer;
00356   const cEvent *Event = NULL;
00357   if (!channel->GroupSep()) {
00358      cSchedulesLock SchedulesLock;
00359      const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
00360      const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
00361      if (Schedule)
00362         Event = Schedule->GetPresentEvent();
00363 
00364      if (sortMode == csmProvider)
00365         buffer = cString::sprintf("%d\t%s - %s %c%s%c", channel->Number(), channel->Provider(), channel->Name(),
00366                                   Event ? '(' : ' ', Event ? Event->Title() : "", Event ? ')' : ' ');
00367      else
00368         buffer = cString::sprintf("%d\t%s %c%s%c", channel->Number(), channel->Name(),
00369                                   Event ? '(' : ' ', Event ? Event->Title() : "", Event ? ')' : ' ');
00370      }
00371   else
00372      buffer = cString::sprintf("---\t%s ----------------------------------------------------------------", channel->Name());
00373   SetText(buffer);
00374 }
00375 
00376 // --- cMenuChannels ---------------------------------------------------------
00377 
00378 #define CHANNELNUMBERTIMEOUT 1000 //ms
00379 
00380 class cMenuChannels : public cOsdMenu {
00381 private:
00382   int number;
00383   cTimeMs numberTimer;
00384   void Setup(void);
00385   cChannel *GetChannel(int Index);
00386   void Propagate(void);
00387 protected:
00388   eOSState Number(eKeys Key);
00389   eOSState Switch(void);
00390   eOSState Edit(void);
00391   eOSState New(void);
00392   eOSState Delete(void);
00393   virtual void Move(int From, int To);
00394 public:
00395   cMenuChannels(void);
00396   ~cMenuChannels();
00397   virtual eOSState ProcessKey(eKeys Key);
00398   };
00399 
00400 cMenuChannels::cMenuChannels(void)
00401 :cOsdMenu(tr("Channels"), CHNUMWIDTH)
00402 {
00403   number = 0;
00404   Setup();
00405   Channels.IncBeingEdited();
00406 }
00407 
00408 cMenuChannels::~cMenuChannels()
00409 {
00410   Channels.DecBeingEdited();
00411 }
00412 
00413 void cMenuChannels::Setup(void)
00414 {
00415   cChannel *currentChannel = GetChannel(Current());
00416   if (!currentChannel)
00417      currentChannel = Channels.GetByNumber(cDevice::CurrentChannel());
00418   cMenuChannelItem *currentItem = NULL;
00419   Clear();
00420   for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
00421       if (!channel->GroupSep() || cMenuChannelItem::SortMode() == cMenuChannelItem::csmNumber && *channel->Name()) {
00422          cMenuChannelItem *item = new cMenuChannelItem(channel);
00423          Add(item);
00424          if (channel == currentChannel)
00425             currentItem = item;
00426          }
00427       }
00428   if (cMenuChannelItem::SortMode() != cMenuChannelItem::csmNumber)
00429      Sort();
00430   SetCurrent(currentItem);
00431   SetHelp(tr("Button$Edit"), tr("Button$New"), tr("Button$Delete"), tr("Button$Mark"));
00432   Display();
00433 }
00434 
00435 cChannel *cMenuChannels::GetChannel(int Index)
00436 {
00437   cMenuChannelItem *p = (cMenuChannelItem *)Get(Index);
00438   return p ? (cChannel *)p->Channel() : NULL;
00439 }
00440 
00441 void cMenuChannels::Propagate(void)
00442 {
00443   Channels.ReNumber();
00444   for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next())
00445       ci->Set();
00446   Display();
00447   Channels.SetModified(true);
00448 }
00449 
00450 eOSState cMenuChannels::Number(eKeys Key)
00451 {
00452   if (HasSubMenu())
00453      return osContinue;
00454   if (numberTimer.TimedOut())
00455      number = 0;
00456   if (!number && Key == k0) {
00457      cMenuChannelItem::IncSortMode();
00458      Setup();
00459      }
00460   else {
00461      number = number * 10 + Key - k0;
00462      for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) {
00463          if (!ci->Channel()->GroupSep() && ci->Channel()->Number() == number) {
00464             SetCurrent(ci);
00465             Display();
00466             break;
00467             }
00468          }
00469      numberTimer.Set(CHANNELNUMBERTIMEOUT);
00470      }
00471   return osContinue;
00472 }
00473 
00474 eOSState cMenuChannels::Switch(void)
00475 {
00476   if (HasSubMenu())
00477      return osContinue;
00478   cChannel *ch = GetChannel(Current());
00479   if (ch)
00480      return cDevice::PrimaryDevice()->SwitchChannel(ch, true) ? osEnd : osContinue;
00481   return osEnd;
00482 }
00483 
00484 eOSState cMenuChannels::Edit(void)
00485 {
00486   if (HasSubMenu() || Count() == 0)
00487      return osContinue;
00488   cChannel *ch = GetChannel(Current());
00489   if (ch)
00490      return AddSubMenu(new cMenuEditChannel(ch));
00491   return osContinue;
00492 }
00493 
00494 eOSState cMenuChannels::New(void)
00495 {
00496   if (HasSubMenu())
00497      return osContinue;
00498   return AddSubMenu(new cMenuEditChannel(GetChannel(Current()), true));
00499 }
00500 
00501 eOSState cMenuChannels::Delete(void)
00502 {
00503   if (!HasSubMenu() && Count() > 0) {
00504      int CurrentChannelNr = cDevice::CurrentChannel();
00505      cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
00506      int Index = Current();
00507      cChannel *channel = GetChannel(Current());
00508      int DeletedChannel = channel->Number();
00509      // Check if there is a timer using this channel:
00510      if (channel->HasTimer()) {
00511         Skins.Message(mtError, tr("Channel is being used by a timer!"));
00512         return osContinue;
00513         }
00514      if (Interface->Confirm(tr("Delete channel?"))) {
00515         if (CurrentChannel && channel == CurrentChannel) {
00516            int n = Channels.GetNextNormal(CurrentChannel->Index());
00517            if (n < 0)
00518               n = Channels.GetPrevNormal(CurrentChannel->Index());
00519            CurrentChannel = Channels.Get(n);
00520            CurrentChannelNr = 0; // triggers channel switch below
00521            }
00522         Channels.Del(channel);
00523         cOsdMenu::Del(Index);
00524         Propagate();
00525         Channels.SetModified(true);
00526         isyslog("channel %d deleted", DeletedChannel);
00527         if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
00528            if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
00529               Channels.SwitchTo(CurrentChannel->Number());
00530            else
00531               cDevice::SetCurrentChannel(CurrentChannel);
00532            }
00533         }
00534      }
00535   return osContinue;
00536 }
00537 
00538 void cMenuChannels::Move(int From, int To)
00539 {
00540   int CurrentChannelNr = cDevice::CurrentChannel();
00541   cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
00542   cChannel *FromChannel = GetChannel(From);
00543   cChannel *ToChannel = GetChannel(To);
00544   if (FromChannel && ToChannel) {
00545      int FromNumber = FromChannel->Number();
00546      int ToNumber = ToChannel->Number();
00547      Channels.Move(FromChannel, ToChannel);
00548      cOsdMenu::Move(From, To);
00549      Propagate();
00550      Channels.SetModified(true);
00551      isyslog("channel %d moved to %d", FromNumber, ToNumber);
00552      if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
00553         if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
00554            Channels.SwitchTo(CurrentChannel->Number());
00555         else
00556            cDevice::SetCurrentChannel(CurrentChannel);
00557         }
00558      }
00559 }
00560 
00561 eOSState cMenuChannels::ProcessKey(eKeys Key)
00562 {
00563   eOSState state = cOsdMenu::ProcessKey(Key);
00564 
00565   switch (state) {
00566     case osUser1: {
00567          cChannel *channel = Channels.Last();
00568          if (channel) {
00569             Add(new cMenuChannelItem(channel), true);
00570             return CloseSubMenu();
00571             }
00572          }
00573          break;
00574     default:
00575          if (state == osUnknown) {
00576             switch (Key) {
00577               case k0 ... k9:
00578                             return Number(Key);
00579               case kOk:     return Switch();
00580               case kRed:    return Edit();
00581               case kGreen:  return New();
00582               case kYellow: return Delete();
00583               case kBlue:   if (!HasSubMenu())
00584                                Mark();
00585                             break;
00586               default: break;
00587               }
00588             }
00589     }
00590   return state;
00591 }
00592 
00593 // --- cMenuText -------------------------------------------------------------
00594 
00595 cMenuText::cMenuText(const char *Title, const char *Text, eDvbFont Font)
00596 :cOsdMenu(Title)
00597 {
00598   text = NULL;
00599   font = Font;
00600   SetText(Text);
00601 }
00602 
00603 cMenuText::~cMenuText()
00604 {
00605   free(text);
00606 }
00607 
00608 void cMenuText::SetText(const char *Text)
00609 {
00610   free(text);
00611   text = Text ? strdup(Text) : NULL;
00612 }
00613 
00614 void cMenuText::Display(void)
00615 {
00616   cOsdMenu::Display();
00617   DisplayMenu()->SetText(text, font == fontFix); //XXX define control character in text to choose the font???
00618   if (text)
00619      cStatus::MsgOsdTextItem(text);
00620 }
00621 
00622 eOSState cMenuText::ProcessKey(eKeys Key)
00623 {
00624   switch (int(Key)) {
00625     case kUp|k_Repeat:
00626     case kUp:
00627     case kDown|k_Repeat:
00628     case kDown:
00629     case kLeft|k_Repeat:
00630     case kLeft:
00631     case kRight|k_Repeat:
00632     case kRight:
00633                   DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight);
00634                   cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft);
00635                   return osContinue;
00636     default: break;
00637     }
00638 
00639   eOSState state = cOsdMenu::ProcessKey(Key);
00640 
00641   if (state == osUnknown) {
00642      switch (Key) {
00643        case kOk: return osBack;
00644        default:  state = osContinue;
00645        }
00646      }
00647   return state;
00648 }
00649 
00650 // --- cMenuFolderItem -------------------------------------------------------
00651 
00652 class cMenuFolderItem : public cOsdItem {
00653 private:
00654   cNestedItem *folder;
00655 public:
00656   cMenuFolderItem(cNestedItem *Folder);
00657   cNestedItem *Folder(void) { return folder; }
00658   };
00659 
00660 cMenuFolderItem::cMenuFolderItem(cNestedItem *Folder)
00661 :cOsdItem(Folder->Text())
00662 {
00663   folder = Folder;
00664   if (folder->SubItems())
00665      SetText(cString::sprintf("%s...", folder->Text()));
00666 }
00667 
00668 // --- cMenuEditFolder -------------------------------------------------------
00669 
00670 class cMenuEditFolder : public cOsdMenu {
00671 private:
00672   cList<cNestedItem> *list;
00673   cNestedItem *folder;
00674   char name[PATH_MAX];
00675   int subFolder;
00676   eOSState Confirm(void);
00677 public:
00678   cMenuEditFolder(const char *Dir, cList<cNestedItem> *List, cNestedItem *Folder = NULL);
00679   cString GetFolder(void);
00680   virtual eOSState ProcessKey(eKeys Key);
00681   };
00682 
00683 cMenuEditFolder::cMenuEditFolder(const char *Dir, cList<cNestedItem> *List, cNestedItem *Folder)
00684 :cOsdMenu(Folder ? tr("Edit folder") : tr("New folder"), 12)
00685 {
00686   list = List;
00687   folder = Folder;
00688   if (folder) {
00689      strn0cpy(name, folder->Text(), sizeof(name));
00690      subFolder = folder->SubItems() != NULL;
00691      }
00692   else {
00693      *name = 0;
00694      subFolder = 0;
00695      cRemote::Put(kRight, true); // go right into string editing mode
00696      }
00697   if (!isempty(Dir)) {
00698      cOsdItem *DirItem = new cOsdItem(Dir);
00699      DirItem->SetSelectable(false);
00700      Add(DirItem);
00701      }
00702   Add(new cMenuEditStrItem( tr("Name"), name, sizeof(name)));
00703   Add(new cMenuEditBoolItem(tr("Sub folder"), &subFolder));
00704 }
00705 
00706 cString cMenuEditFolder::GetFolder(void)
00707 {
00708   return folder ? folder->Text() : "";
00709 }
00710 
00711 eOSState cMenuEditFolder::Confirm(void)
00712 {
00713   if (!folder || strcmp(folder->Text(), name) != 0) {
00714      // each name may occur only once in a folder list
00715      for (cNestedItem *Folder = list->First(); Folder; Folder = list->Next(Folder)) {
00716          if (strcmp(Folder->Text(), name) == 0) {
00717             Skins.Message(mtError, tr("Folder name already exists!"));
00718             return osContinue;
00719             }
00720          }
00721      char *p = strpbrk(name, "\\{}#~"); // FOLDERDELIMCHAR
00722      if (p) {
00723         Skins.Message(mtError, cString::sprintf(tr("Folder name must not contain '%c'!"), *p));
00724         return osContinue;
00725         }
00726      }
00727   if (folder) {
00728      folder->SetText(name);
00729      folder->SetSubItems(subFolder);
00730      }
00731   else
00732      list->Add(folder = new cNestedItem(name, subFolder));
00733   return osEnd;
00734 }
00735 
00736 eOSState cMenuEditFolder::ProcessKey(eKeys Key)
00737 {
00738   eOSState state = cOsdMenu::ProcessKey(Key);
00739 
00740   if (state == osUnknown) {
00741      switch (Key) {
00742        case kOk:     return Confirm();
00743        case kRed:
00744        case kGreen:
00745        case kYellow:
00746        case kBlue:   return osContinue;
00747        default: break;
00748        }
00749      }
00750   return state;
00751 }
00752 
00753 // --- cMenuFolder -----------------------------------------------------------
00754 
00755 cMenuFolder::cMenuFolder(const char *Title, cNestedItemList *NestedItemList, const char *Path)
00756 :cOsdMenu(Title)
00757 {
00758   list = nestedItemList = NestedItemList;
00759   firstFolder = NULL;
00760   editing = false;
00761   Set();
00762   SetHelpKeys();
00763   DescendPath(Path);
00764 }
00765 
00766 cMenuFolder::cMenuFolder(const char *Title, cList<cNestedItem> *List, cNestedItemList *NestedItemList, const char *Dir, const char *Path)
00767 :cOsdMenu(Title)
00768 {
00769   list = List;
00770   nestedItemList = NestedItemList;
00771   dir = Dir;
00772   firstFolder = NULL;
00773   editing = false;
00774   Set();
00775   SetHelpKeys();
00776   DescendPath(Path);
00777 }
00778 
00779 void cMenuFolder::SetHelpKeys(void)
00780 {
00781   SetHelp(firstFolder ? tr("Button$Select") : NULL, tr("Button$New"), firstFolder ? tr("Button$Delete") : NULL, firstFolder ? tr("Button$Edit") : NULL);
00782 }
00783 
00784 void cMenuFolder::Set(const char *CurrentFolder)
00785 {
00786   firstFolder = NULL;
00787   Clear();
00788   if (!isempty(dir)) {
00789      cOsdItem *DirItem = new cOsdItem(dir);
00790      DirItem->SetSelectable(false);
00791      Add(DirItem);
00792      }
00793   list->Sort();
00794   for (cNestedItem *Folder = list->First(); Folder; Folder = list->Next(Folder)) {
00795       cOsdItem *FolderItem = new cMenuFolderItem(Folder);
00796       Add(FolderItem, CurrentFolder ? strcmp(Folder->Text(), CurrentFolder) == 0 : false);
00797       if (!firstFolder)
00798          firstFolder = FolderItem;
00799       }
00800 }
00801 
00802 void cMenuFolder::DescendPath(const char *Path)
00803 {
00804   if (Path) {
00805      const char *p = strchr(Path, FOLDERDELIMCHAR);
00806      if (p) {
00807         for (cMenuFolderItem *Folder = (cMenuFolderItem *)firstFolder; Folder; Folder = (cMenuFolderItem *)Next(Folder)) {
00808             if (strncmp(Folder->Folder()->Text(), Path, p - Path) == 0) {
00809                SetCurrent(Folder);
00810                if (Folder->Folder()->SubItems())
00811                   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));
00812                break;
00813                }
00814             }
00815         }
00816     }
00817 }
00818 
00819 eOSState cMenuFolder::Select(void)
00820 {
00821   if (firstFolder) {
00822      cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current());
00823      if (Folder) {
00824         if (Folder->Folder()->SubItems())
00825            return AddSubMenu(new cMenuFolder(Title(), Folder->Folder()->SubItems(), nestedItemList, !isempty(dir) ? *cString::sprintf("%s%c%s", *dir, FOLDERDELIMCHAR, Folder->Folder()->Text()) : Folder->Folder()->Text()));
00826         else
00827            return osEnd;
00828         }
00829      }
00830   return osContinue;
00831 }
00832 
00833 eOSState cMenuFolder::New(void)
00834 {
00835   editing = true;
00836   return AddSubMenu(new cMenuEditFolder(dir, list));
00837 }
00838 
00839 eOSState cMenuFolder::Delete(void)
00840 {
00841   if (!HasSubMenu() && firstFolder) {
00842      cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current());
00843      if (Folder && Interface->Confirm(Folder->Folder()->SubItems() ? tr("Delete folder and all sub folders?") : tr("Delete folder?"))) {
00844         list->Del(Folder->Folder());
00845         Del(Folder->Index());
00846         firstFolder = Get(isempty(dir) ? 0 : 1);
00847         Display();
00848         SetHelpKeys();
00849         nestedItemList->Save();
00850         }
00851      }
00852   return osContinue;
00853 }
00854 
00855 eOSState cMenuFolder::Edit(void)
00856 {
00857   if (!HasSubMenu() && firstFolder) {
00858      cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current());
00859      if (Folder) {
00860         editing = true;
00861         return AddSubMenu(new cMenuEditFolder(dir, list, Folder->Folder()));
00862         }
00863      }
00864   return osContinue;
00865 }
00866 
00867 eOSState cMenuFolder::SetFolder(void)
00868 {
00869   cMenuEditFolder *mef = (cMenuEditFolder *)SubMenu();
00870   if (mef) {
00871      Set(mef->GetFolder());
00872      SetHelpKeys();
00873      Display();
00874      nestedItemList->Save();
00875      }
00876   return CloseSubMenu();
00877 }
00878 
00879 cString cMenuFolder::GetFolder(void)
00880 {
00881   if (firstFolder) {
00882      cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current());
00883      if (Folder) {
00884         cMenuFolder *mf = (cMenuFolder *)SubMenu();
00885         if (mf)
00886            return cString::sprintf("%s%c%s", Folder->Folder()->Text(), FOLDERDELIMCHAR, *mf->GetFolder());
00887         return Folder->Folder()->Text();
00888         }
00889      }
00890   return "";
00891 }
00892 
00893 eOSState cMenuFolder::ProcessKey(eKeys Key)
00894 {
00895   if (!HasSubMenu())
00896      editing = false;
00897   eOSState state = cOsdMenu::ProcessKey(Key);
00898 
00899   if (state == osUnknown) {
00900      switch (Key) {
00901        case kOk:
00902        case kRed:    return Select();
00903        case kGreen:  return New();
00904        case kYellow: return Delete();
00905        case kBlue:   return Edit();
00906        default:      state = osContinue;
00907        }
00908      }
00909   else if (state == osEnd && HasSubMenu() && editing)
00910      state = SetFolder();
00911   return state;
00912 }
00913 
00914 // --- cMenuEditTimer --------------------------------------------------------
00915 
00916 cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New)
00917 :cOsdMenu(tr("Edit timer"), 12)
00918 {
00919   file = NULL;
00920   day = firstday = NULL;
00921   timer = Timer;
00922   addIfConfirmed = New;
00923   if (timer) {
00924      data = *timer;
00925      if (New)
00926         data.SetFlags(tfActive);
00927      channel = data.Channel()->Number();
00928      Add(new cMenuEditBitItem( tr("Active"),       &data.flags, tfActive));
00929      Add(new cMenuEditChanItem(tr("Channel"),      &channel));
00930      Add(day = new cMenuEditDateItem(tr("Day"),    &data.day, &data.weekdays));
00931      Add(new cMenuEditTimeItem(tr("Start"),        &data.start));
00932      Add(new cMenuEditTimeItem(tr("Stop"),         &data.stop));
00933      Add(new cMenuEditBitItem( tr("VPS"),          &data.flags, tfVps));
00934      Add(new cMenuEditIntItem( tr("Priority"),     &data.priority, 0, MAXPRIORITY));
00935      Add(new cMenuEditIntItem( tr("Lifetime"),     &data.lifetime, 0, MAXLIFETIME));
00936      Add(file = new cMenuEditStrItem( tr("File"),   data.file, sizeof(data.file)));
00937      SetFirstDayItem();
00938      }
00939   SetHelpKeys();
00940   Timers.IncBeingEdited();
00941 }
00942 
00943 cMenuEditTimer::~cMenuEditTimer()
00944 {
00945   if (timer && addIfConfirmed)
00946      delete timer; // apparently it wasn't confirmed
00947   Timers.DecBeingEdited();
00948 }
00949 
00950 void cMenuEditTimer::SetHelpKeys(void)
00951 {
00952   SetHelp(tr("Button$Folder"), data.weekdays ? tr("Button$Single") : tr("Button$Repeating"));
00953 }
00954 
00955 void cMenuEditTimer::SetFirstDayItem(void)
00956 {
00957   if (!firstday && !data.IsSingleEvent()) {
00958      Add(firstday = new cMenuEditDateItem(tr("First day"), &data.day));
00959      Display();
00960      }
00961   else if (firstday && data.IsSingleEvent()) {
00962      Del(firstday->Index());
00963      firstday = NULL;
00964      Display();
00965      }
00966 }
00967 
00968 eOSState cMenuEditTimer::SetFolder(void)
00969 {
00970   cMenuFolder *mf = (cMenuFolder *)SubMenu();
00971   if (mf) {
00972      cString Folder = mf->GetFolder();
00973      char *p = strrchr(data.file, FOLDERDELIMCHAR);
00974      if (p)
00975         p++;
00976      else
00977         p = data.file;
00978      if (!isempty(*Folder))
00979         strn0cpy(data.file, cString::sprintf("%s%c%s", *Folder, FOLDERDELIMCHAR, p), sizeof(data.file));
00980      else if (p != data.file)
00981         memmove(data.file, p, strlen(p) + 1);
00982      SetCurrent(file);
00983      Display();
00984      }
00985   return CloseSubMenu();
00986 }
00987 
00988 eOSState cMenuEditTimer::ProcessKey(eKeys Key)
00989 {
00990   eOSState state = cOsdMenu::ProcessKey(Key);
00991 
00992   if (state == osUnknown) {
00993      switch (Key) {
00994        case kOk:     {
00995                        cChannel *ch = Channels.GetByNumber(channel);
00996                        if (ch)
00997                           data.channel = ch;
00998                        else {
00999                           Skins.Message(mtError, tr("*** Invalid Channel ***"));
01000                           break;
01001                           }
01002                        if (!*data.file)
01003                           strcpy(data.file, data.Channel()->ShortName(true));
01004                        if (timer) {
01005                           if (memcmp(timer, &data, sizeof(data)) != 0)
01006                              *timer = data;
01007                           if (addIfConfirmed)
01008                              Timers.Add(timer);
01009                           timer->SetEventFromSchedule();
01010                           timer->Matches();
01011                           Timers.SetModified();
01012                           isyslog("timer %s %s (%s)", *timer->ToDescr(), addIfConfirmed ? "added" : "modified", timer->HasFlags(tfActive) ? "active" : "inactive");
01013                           addIfConfirmed = false;
01014                           }
01015                      }
01016                      return osBack;
01017        case kRed:    return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, data.file));
01018        case kGreen:  if (day) {
01019                         day->ToggleRepeating();
01020                         SetCurrent(day);
01021                         SetFirstDayItem();
01022                         SetHelpKeys();
01023                         Display();
01024                         }
01025                      return osContinue;
01026        case kYellow:
01027        case kBlue:   return osContinue;
01028        default: break;
01029        }
01030      }
01031   else if (state == osEnd && HasSubMenu())
01032      state = SetFolder();
01033   if (Key != kNone)
01034      SetFirstDayItem();
01035   return state;
01036 }
01037 
01038 // --- cMenuTimerItem --------------------------------------------------------
01039 
01040 class cMenuTimerItem : public cOsdItem {
01041 private:
01042   cTimer *timer;
01043   char diskStatus;
01044 public:
01045   cMenuTimerItem(cTimer *Timer);
01046   void SetDiskStatus(char DiskStatus);
01047   virtual int Compare(const cListObject &ListObject) const;
01048   virtual void Set(void);
01049   cTimer *Timer(void) { return timer; }
01050   };
01051 
01052 cMenuTimerItem::cMenuTimerItem(cTimer *Timer)
01053 {
01054   timer = Timer;
01055   diskStatus = ' ';
01056   Set();
01057 }
01058 
01059 int cMenuTimerItem::Compare(const cListObject &ListObject) const
01060 {
01061   return timer->Compare(*((cMenuTimerItem *)&ListObject)->timer);
01062 }
01063 
01064 void cMenuTimerItem::Set(void)
01065 {
01066   cString day, name("");
01067   if (timer->WeekDays())
01068      day = timer->PrintDay(0, timer->WeekDays(), false);
01069   else if (timer->Day() - time(NULL) < 28 * SECSINDAY) {
01070      day = itoa(timer->GetMDay(timer->Day()));
01071      name = WeekDayName(timer->Day());
01072      }
01073   else {
01074      struct tm tm_r;
01075      time_t Day = timer->Day();
01076      localtime_r(&Day, &tm_r);
01077      char buffer[16];
01078      strftime(buffer, sizeof(buffer), "%Y%m%d", &tm_r);
01079      day = buffer;
01080      }
01081   const char *File = Setup.FoldersInTimerMenu ? NULL : strrchr(timer->File(), FOLDERDELIMCHAR);
01082   if (File && strcmp(File + 1, TIMERMACRO_TITLE) && strcmp(File + 1, TIMERMACRO_EPISODE))
01083      File++;
01084   else
01085      File = timer->File();
01086   cCharSetConv csc("ISO-8859-1", cCharSetConv::SystemCharacterTable());
01087   char diskStatusString[2] = { diskStatus, 0 };
01088   SetText(cString::sprintf("%s%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s",
01089                     csc.Convert(diskStatusString),
01090                     !(timer->HasFlags(tfActive)) ? ' ' : timer->FirstDay() ? '!' : timer->Recording() ? '#' : '>',
01091                     timer->Channel()->Number(),
01092                     *name,
01093                     *name && **name ? " " : "",
01094                     *day,
01095                     timer->Start() / 100,
01096                     timer->Start() % 100,
01097                     timer->Stop() / 100,
01098                     timer->Stop() % 100,
01099                     File));
01100 }
01101 
01102 void cMenuTimerItem::SetDiskStatus(char DiskStatus)
01103 {
01104   diskStatus = DiskStatus;
01105   Set();
01106 }
01107 
01108 // --- cTimerEntry -----------------------------------------------------------
01109 
01110 class cTimerEntry : public cListObject {
01111 private:
01112   cMenuTimerItem *item;
01113   const cTimer *timer;
01114   time_t start;
01115 public:
01116   cTimerEntry(cMenuTimerItem *item) : item(item), timer(item->Timer()), start(timer->StartTime()) {}
01117   cTimerEntry(const cTimer *timer, time_t start) : item(NULL), timer(timer), start(start) {}
01118   virtual int Compare(const cListObject &ListObject) const;
01119   bool active(void) const { return timer->HasFlags(tfActive); }
01120   time_t startTime(void) const { return start; }
01121   int priority(void) const { return timer->Priority(); }
01122   int duration(void) const;
01123   bool repTimer(void) const { return !timer->IsSingleEvent(); }
01124   bool isDummy(void) const { return item == NULL; }
01125   const cTimer *Timer(void) const { return timer; }
01126   void SetDiskStatus(char DiskStatus);
01127   };
01128 
01129 int cTimerEntry::Compare(const cListObject &ListObject) const
01130 {
01131   cTimerEntry *entry = (cTimerEntry *)&ListObject;
01132   int r = startTime() - entry->startTime();
01133   if (r == 0)
01134      r = entry->priority() - priority();
01135   return r;
01136 }
01137 
01138 int cTimerEntry::duration(void) const
01139 {
01140   int dur = (timer->Stop()  / 100 * 60 + timer->Stop()  % 100) -
01141             (timer->Start() / 100 * 60 + timer->Start() % 100);
01142   if (dur < 0)
01143      dur += 24 * 60;
01144   return dur;
01145 }
01146 
01147 void cTimerEntry::SetDiskStatus(char DiskStatus)
01148 {
01149   if (item)
01150      item->SetDiskStatus(DiskStatus);
01151 }
01152 
01153 // --- cMenuTimers -----------------------------------------------------------
01154 
01155 class cMenuTimers : public cOsdMenu {
01156 private:
01157   eOSState Commands(eKeys Key = kNone);
01158   int helpKeys;
01159   eOSState Edit(void);
01160   eOSState New(void);
01161   eOSState Delete(void);
01162   eOSState OnOff(void);
01163   eOSState Info(void);
01164   cTimer *CurrentTimer(void);
01165   void SetHelpKeys(void);
01166   void ActualiseDiskStatus(void);
01167   bool actualiseDiskStatus;
01168 public:
01169   cMenuTimers(void);
01170   virtual ~cMenuTimers();
01171   virtual void Display(void);
01172   virtual eOSState ProcessKey(eKeys Key);
01173   };
01174 
01175 cMenuTimers::cMenuTimers(void)
01176 :cOsdMenu(tr("Timers"), 3, CHNUMWIDTH, 10, 6, 6)
01177 {
01178   helpKeys = -1;
01179   for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) {
01180       timer->SetEventFromSchedule(); // make sure the event is current
01181       Add(new cMenuTimerItem(timer));
01182       }
01183   Sort();
01184   SetCurrent(First());
01185   SetHelpKeys();
01186   Timers.IncBeingEdited();
01187   actualiseDiskStatus = true;
01188 }
01189 
01190 cMenuTimers::~cMenuTimers()
01191 {
01192   Timers.DecBeingEdited();
01193 }
01194 
01195 cTimer *cMenuTimers::CurrentTimer(void)
01196 {
01197   cMenuTimerItem *item = (cMenuTimerItem *)Get(Current());
01198   return item ? item->Timer() : NULL;
01199 }
01200 
01201 void cMenuTimers::SetHelpKeys(void)
01202 {
01203   int NewHelpKeys = 0;
01204   cTimer *timer = CurrentTimer();
01205   if (timer) {
01206      if (timer->Event())
01207         NewHelpKeys = 2;
01208      else
01209         NewHelpKeys = 1;
01210      }
01211   if (NewHelpKeys != helpKeys) {
01212      helpKeys = NewHelpKeys;
01213      SetHelp(helpKeys > 0 ? tr("Button$On/Off") : NULL, tr("Button$New"), helpKeys > 0 ? tr("Button$Delete") : NULL, helpKeys == 2 ? tr("Button$Info") : NULL);
01214      }
01215 }
01216 
01217 eOSState cMenuTimers::OnOff(void)
01218 {
01219   if (HasSubMenu())
01220      return osContinue;
01221   cTimer *timer = CurrentTimer();
01222   if (timer) {
01223      timer->OnOff();
01224      timer->SetEventFromSchedule();
01225      RefreshCurrent();
01226      Display();
01227      if (timer->FirstDay())
01228         isyslog("timer %s first day set to %s", *timer->ToDescr(), *timer->PrintFirstDay());
01229      else
01230         isyslog("timer %s %sactivated", *timer->ToDescr(), timer->HasFlags(tfActive) ? "" : "de");
01231      Timers.SetModified();
01232      }
01233   return osContinue;
01234 }
01235 
01236 eOSState cMenuTimers::Edit(void)
01237 {
01238   if (HasSubMenu() || Count() == 0)
01239      return osContinue;
01240   isyslog("editing timer %s", *CurrentTimer()->ToDescr());
01241   return AddSubMenu(new cMenuEditTimer(CurrentTimer()));
01242 }
01243 
01244 eOSState cMenuTimers::New(void)
01245 {
01246   if (HasSubMenu())
01247      return osContinue;
01248   return AddSubMenu(new cMenuEditTimer(new cTimer, true));
01249 }
01250 
01251 eOSState cMenuTimers::Delete(void)
01252 {
01253   // Check if this timer is active:
01254   cTimer *ti = CurrentTimer();
01255   if (ti) {
01256      if (Interface->Confirm(tr("Delete timer?"))) {
01257         if (ti->Recording()) {
01258            if (Interface->Confirm(tr("Timer still recording - really delete?"))) {
01259               ti->Skip();
01260               cRecordControls::Process(time(NULL));
01261               }
01262            else
01263               return osContinue;
01264            }
01265         isyslog("deleting timer %s", *ti->ToDescr());
01266         Timers.Del(ti);
01267         cOsdMenu::Del(Current());
01268         Timers.SetModified();
01269         Display();
01270         }
01271      }
01272   return osContinue;
01273 }
01274 
01275 #define CHECK_2PTR_NULL(x_,y_) ((x_)? ((y_)? y_:""):"")
01276 
01277 eOSState cMenuTimers::Commands(eKeys Key)
01278 {
01279   if (HasSubMenu() || Count() == 0)
01280      return osContinue;
01281   cTimer *ti = CurrentTimer();
01282   if (ti) {
01283      char *parameter = NULL;
01284      const cEvent *pEvent = ti->Event();
01285      int iRecNumber=0;
01286 
01287      if(!pEvent) {
01288         Timers.SetEvents();
01289         pEvent = ti->Event();
01290      }
01291      if(pEvent) {
01292 // create a dummy recording to get the real filename
01293         cRecording *rc_dummy = new cRecording(ti, pEvent);
01294         Recordings.Load();
01295         cRecording *rc = Recordings.GetByName(rc_dummy->FileName());
01296 
01297         delete rc_dummy;
01298         if(rc)
01299            iRecNumber=rc->Index() + 1;
01300      }
01301 //Parameter format TimerNumber 'ChannelId' Start Stop 'Titel' 'Subtitel' 'file' RecNumer
01302 //                 1           2           3     4    5       6          7      8
01303      asprintf(&parameter, "%d '%s' %d %d '%s' '%s' '%s' %d", ti->Index(),
01304                                                              *ti->Channel()->GetChannelID().ToString(),
01305                                                              (int)ti->StartTime(),
01306                                                              (int)ti->StopTime(),
01307                                                              CHECK_2PTR_NULL(pEvent, pEvent->Title()),
01308                                                              CHECK_2PTR_NULL(pEvent, pEvent->ShortText()),
01309                                                              ti->File(),
01310                                                              iRecNumber);
01311      isyslog("timercmd: %s", parameter);
01312      cMenuCommands *menu;
01313      eOSState state = AddSubMenu(menu = new cMenuCommands(tr("Timer commands"), &TimerCommands, parameter));
01314      free(parameter);
01315      if (Key != kNone)
01316         state = menu->ProcessKey(Key);
01317      return state;
01318      }
01319   return osContinue;
01320 }
01321 
01322 eOSState cMenuTimers::Info(void)
01323 {
01324   if (HasSubMenu() || Count() == 0)
01325      return osContinue;
01326   cTimer *ti = CurrentTimer();
01327   if (ti && ti->Event())
01328      return AddSubMenu(new cMenuEvent(ti->Event()));
01329   return osContinue;
01330 }
01331 
01332 void cMenuTimers::ActualiseDiskStatus(void)
01333 {
01334   if (!actualiseDiskStatus || !Count())
01335      return;
01336 
01337   // compute free disk space
01338   int freeMB, freeMinutes, runshortMinutes;
01339   VideoDiskSpace(&freeMB);
01340   freeMinutes = int(double(freeMB) * 1.1 / MB_PER_MINUTE); // overestimate by 10 percent
01341   runshortMinutes = freeMinutes / 5; // 20 Percent
01342 
01343   // fill entries list
01344   cTimerEntry *entry;
01345   cList<cTimerEntry> entries;
01346   for (cOsdItem *item = First(); item; item = Next(item))
01347      entries.Add(new cTimerEntry((cMenuTimerItem *)item));
01348 
01349   // search last start time
01350   time_t last = 0;
01351   for (entry = entries.First(); entry; entry = entries.Next(entry))
01352      last = max(entry->startTime(), last);
01353 
01354   // add entries for repeating timers
01355   for (entry = entries.First(); entry; entry = entries.Next(entry))
01356      if (entry->repTimer() && !entry->isDummy())
01357         for (time_t start = cTimer::IncDay(entry->startTime(), 1);
01358              start <= last;
01359              start = cTimer::IncDay(start, 1))
01360            if (entry->Timer()->DayMatches(start))
01361               entries.Add(new cTimerEntry(entry->Timer(), start));
01362 
01363   // set the disk-status
01364   entries.Sort();
01365   for (entry = entries.First(); entry; entry = entries.Next(entry)) {
01366      char status = ' ';
01367      if (entry->active()) {
01368         freeMinutes -= entry->duration();
01369         status = freeMinutes > runshortMinutes ? '+' : freeMinutes > 0 ? 177 /* +/- */ : '-';
01370         }
01371      entry->SetDiskStatus(status);
01372 #ifdef DEBUG_TIMER_INFO
01373      dsyslog("timer-info: %c | %d | %s | %s | %3d | %+5d -> %+5d",
01374              status,
01375              entry->startTime(),
01376              entry->active() ? "aktiv " : "n.akt.",
01377              entry->repTimer() ? entry->isDummy() ? "  dummy  " : "mehrmalig" : "einmalig ",
01378              entry->duration(),
01379              entry->active() ? freeMinutes + entry->duration() : freeMinutes,
01380              freeMinutes);
01381 #endif
01382      }
01383 
01384   actualiseDiskStatus = false;
01385 }
01386 
01387 void cMenuTimers::Display(void)
01388 {
01389   ActualiseDiskStatus();
01390   cOsdMenu::Display();
01391 }
01392 
01393 eOSState cMenuTimers::ProcessKey(eKeys Key)
01394 {
01395   int TimerNumber = HasSubMenu() ? Count() : -1;
01396   eOSState state = cOsdMenu::ProcessKey(Key);
01397 
01398   if (state == osUnknown) {
01399      switch (Key) {
01400        case kOk:     return Edit();
01401        case kRed:    actualiseDiskStatus = true;
01402                      state = OnOff(); break; // must go through SetHelpKeys()!
01403        case kGreen:  return New();
01404        case kYellow: actualiseDiskStatus = true;
01405                      state = Delete(); break;
01406        case kInfo:
01407        case kBlue:   return Info();
01408                      break;
01409        case k1...k9: return Commands(Key);
01410        case k0:      return (TimerCommands.Count()? Commands():osContinue);
01411        default: break;
01412        }
01413      }
01414   if (TimerNumber >= 0 && !HasSubMenu()) {
01415      if (Timers.Get(TimerNumber)) // a newly created timer was confirmed with Ok
01416         Add(new cMenuTimerItem(Timers.Get(TimerNumber)), true);
01417      Sort();
01418      actualiseDiskStatus = true;
01419      Display();
01420      }
01421   if (Key != kNone)
01422      SetHelpKeys();
01423   return state;
01424 }
01425 
01426 // --- cMenuEvent ------------------------------------------------------------
01427 
01428 cMenuEvent::cMenuEvent(const cEvent *Event, bool CanSwitch, bool Buttons)
01429 :cOsdMenu(tr("Event"))
01430 {
01431   event = Event;
01432   if (event) {
01433      cChannel *channel = Channels.GetByChannelID(event->ChannelID(), true);
01434      if (channel) {
01435         SetTitle(channel->Name());
01436         int TimerMatch = tmNone;
01437         Timers.GetMatch(event, &TimerMatch);
01438         if (Buttons)
01439            SetHelp(TimerMatch == tmFull ? tr("Button$Timer") : tr("Button$Record"), NULL, NULL, CanSwitch ? tr("Button$Switch") : NULL);
01440         }
01441      }
01442 }
01443 
01444 void cMenuEvent::Display(void)
01445 {
01446   cOsdMenu::Display();
01447   DisplayMenu()->SetEvent(event);
01448   if (event->Description())
01449      cStatus::MsgOsdTextItem(event->Description());
01450 }
01451 
01452 eOSState cMenuEvent::ProcessKey(eKeys Key)
01453 {
01454   switch (int(Key)) {
01455     case kUp|k_Repeat:
01456     case kUp:
01457     case kDown|k_Repeat:
01458     case kDown:
01459     case kLeft|k_Repeat:
01460     case kLeft:
01461     case kRight|k_Repeat:
01462     case kRight:
01463                   DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight);
01464                   cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft);
01465                   return osContinue;
01466     case kInfo:   return osBack;
01467     default: break;
01468     }
01469 
01470   eOSState state = cOsdMenu::ProcessKey(Key);
01471 
01472   if (state == osUnknown) {
01473      switch (Key) {
01474        case kGreen:
01475        case kYellow: return osContinue;
01476        case kOk:     return osBack;
01477        default: break;
01478        }
01479      }
01480   return state;
01481 }
01482 
01483 // --- cMenuScheduleItem -----------------------------------------------------
01484 
01485 class cMenuScheduleItem : public cOsdItem {
01486 public:
01487   enum eScheduleSortMode { ssmAllThis, ssmThisThis, ssmThisAll, ssmAllAll }; // "which event(s) on which channel(s)"
01488 private:
01489   static eScheduleSortMode sortMode;
01490 public:
01491   const cEvent *event;
01492   const cChannel *channel;
01493   bool withDate;
01494   int timerMatch;
01495   cMenuScheduleItem(const cEvent *Event, cChannel *Channel = NULL, bool WithDate = false);
01496   static void SetSortMode(eScheduleSortMode SortMode) { sortMode = SortMode; }
01497   static void IncSortMode(void) { sortMode = eScheduleSortMode((sortMode == ssmAllAll) ? ssmAllThis : sortMode + 1); }
01498   static eScheduleSortMode SortMode(void) { return sortMode; }
01499   virtual int Compare(const cListObject &ListObject) const;
01500   bool Update(bool Force = false);
01501   };
01502 
01503 cMenuScheduleItem::eScheduleSortMode cMenuScheduleItem::sortMode = ssmAllThis;
01504 
01505 cMenuScheduleItem::cMenuScheduleItem(const cEvent *Event, cChannel *Channel, bool WithDate)
01506 {
01507   event = Event;
01508   channel = Channel;
01509   withDate = WithDate;
01510   timerMatch = tmNone;
01511   Update(true);
01512 }
01513 
01514 int cMenuScheduleItem::Compare(const cListObject &ListObject) const
01515 {
01516   cMenuScheduleItem *p = (cMenuScheduleItem *)&ListObject;
01517   int r = -1;
01518   if (sortMode != ssmAllThis)
01519      r = strcoll(event->Title(), p->event->Title());
01520   if (sortMode == ssmAllThis || r == 0)
01521      r = event->StartTime() - p->event->StartTime();
01522   return r;
01523 }
01524 
01525 static const char *TimerMatchChars = " tT";
01526 
01527 bool cMenuScheduleItem::Update(bool Force)
01528 {
01529   bool result = false;
01530   int OldTimerMatch = timerMatch;
01531   Timers.GetMatch(event, &timerMatch);
01532   if (Force || timerMatch != OldTimerMatch) {
01533      cString buffer;
01534      char t = TimerMatchChars[timerMatch];
01535      char v = event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' ';
01536      char r = event->SeenWithin(30) && event->IsRunning() ? '*' : ' ';
01537      const char *csn = channel ? channel->ShortName(true) : NULL;
01538      cString eds = event->GetDateString();
01539      if (channel && withDate)
01540         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());
01541      else if (channel)
01542         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());
01543      else
01544         buffer = cString::sprintf("%.*s\t%s\t%c%c%c\t%s", Utf8SymChars(eds, 6), *eds, *event->GetTimeString(), t, v, r, event->Title());
01545      SetText(buffer);
01546      result = true;
01547      }
01548   return result;
01549 }
01550 
01551 // --- cMenuWhatsOn ----------------------------------------------------------
01552 
01553 class cMenuWhatsOn : public cOsdMenu {
01554 private:
01555   bool now;
01556   int helpKeys;
01557   int timerState;
01558   eOSState Record(void);
01559   eOSState Switch(void);
01560   static int currentChannel;
01561   static const cEvent *scheduleEvent;
01562   bool Update(void);
01563   void SetHelpKeys(void);
01564 public:
01565   cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr);
01566   static int CurrentChannel(void) { return currentChannel; }
01567   static void SetCurrentChannel(int ChannelNr) { currentChannel = ChannelNr; }
01568   static const cEvent *ScheduleEvent(void);
01569   virtual eOSState ProcessKey(eKeys Key);
01570   };
01571 
01572 int cMenuWhatsOn::currentChannel = 0;
01573 const cEvent *cMenuWhatsOn::scheduleEvent = NULL;
01574 
01575 cMenuWhatsOn::cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr)
01576 :cOsdMenu(Now ? tr("What's on now?") : tr("What's on next?"), CHNUMWIDTH, CHNAMWIDTH, 6, 4)
01577 {
01578   now = Now;
01579   helpKeys = -1;
01580   timerState = 0;
01581   Timers.Modified(timerState);
01582   for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
01583       if (!Channel->GroupSep()) {
01584          const cSchedule *Schedule = Schedules->GetSchedule(Channel);
01585          if (Schedule) {
01586             const cEvent *Event = Now ? Schedule->GetPresentEvent() : Schedule->GetFollowingEvent();
01587             if (Event)
01588                Add(new cMenuScheduleItem(Event, Channel), Channel->Number() == CurrentChannelNr);
01589             }
01590          }
01591       }
01592   currentChannel = CurrentChannelNr;
01593   Display();
01594   SetHelpKeys();
01595 }
01596 
01597 bool cMenuWhatsOn::Update(void)
01598 {
01599   bool result = false;
01600   if (Timers.Modified(timerState)) {
01601      for (cOsdItem *item = First(); item; item = Next(item)) {
01602          if (((cMenuScheduleItem *)item)->Update())
01603             result = true;
01604          }
01605      }
01606   return result;
01607 }
01608 
01609 void cMenuWhatsOn::SetHelpKeys(void)
01610 {
01611   cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
01612   int NewHelpKeys = 0;
01613   if (item) {
01614      if (item->timerMatch == tmFull)
01615         NewHelpKeys = 2;
01616      else
01617         NewHelpKeys = 1;
01618      }
01619   if (NewHelpKeys != helpKeys) {
01620      const char *Red[] = { NULL, tr("Button$Record"), tr("Button$Timer") };
01621      SetHelp(Red[NewHelpKeys], now ? tr("Button$Next") : tr("Button$Now"), tr("Button$Schedule"), tr("Button$Switch"));
01622      helpKeys = NewHelpKeys;
01623      }
01624 }
01625 
01626 const cEvent *cMenuWhatsOn::ScheduleEvent(void)
01627 {
01628   const cEvent *ei = scheduleEvent;
01629   scheduleEvent = NULL;
01630   return ei;
01631 }
01632 
01633 eOSState cMenuWhatsOn::Switch(void)
01634 {
01635   cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
01636   if (item) {
01637      cChannel *channel = Channels.GetByChannelID(item->event->ChannelID(), true);
01638      if (channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true))
01639         return osEnd;
01640      }
01641   Skins.Message(mtError, tr("Can't switch channel!"));
01642   return osContinue;
01643 }
01644 
01645 eOSState cMenuWhatsOn::Record(void)
01646 {
01647   cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
01648   if (item) {
01649      if (item->timerMatch == tmFull) {
01650         int tm = tmNone;
01651         cTimer *timer = Timers.GetMatch(item->event, &tm);
01652         if (timer)
01653            return AddSubMenu(new cMenuEditTimer(timer));
01654         }
01655      cTimer *timer = new cTimer(item->event);
01656      cTimer *t = Timers.GetTimer(timer);
01657      if (t) {
01658         delete timer;
01659         timer = t;
01660         return AddSubMenu(new cMenuEditTimer(timer));
01661         }
01662      else {
01663         Timers.Add(timer);
01664         Timers.SetModified();
01665         isyslog("timer %s added (active)", *timer->ToDescr());
01666         if (timer->Matches(0, false, NEWTIMERLIMIT))
01667            return AddSubMenu(new cMenuEditTimer(timer));
01668         if (HasSubMenu())
01669            CloseSubMenu();
01670         if (Update())
01671            Display();
01672         SetHelpKeys();
01673         }
01674      }
01675   return osContinue;
01676 }
01677 
01678 eOSState cMenuWhatsOn::ProcessKey(eKeys Key)
01679 {
01680   bool HadSubMenu = HasSubMenu();
01681   eOSState state = cOsdMenu::ProcessKey(Key);
01682 
01683   if (state == osUnknown) {
01684      switch (Key) {
01685        case kRecord:
01686        case kRed:    return Record();
01687        case kYellow: state = osBack;
01688                      // continue with kGreen
01689        case kGreen:  {
01690                        cMenuScheduleItem *mi = (cMenuScheduleItem *)Get(Current());
01691                        if (mi) {
01692                           scheduleEvent = mi->event;
01693                           currentChannel = mi->channel->Number();
01694                           }
01695                      }
01696                      break;
01697        case kBlue:   return Switch();
01698        case kInfo:
01699        case kOk:     if (Count())
01700                         return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->event, true, true));
01701                      break;
01702        default:      break;
01703        }
01704      }
01705   else if (!HasSubMenu()) {
01706      if (HadSubMenu && Update())
01707         Display();
01708      if (Key != kNone)
01709         SetHelpKeys();
01710      }
01711   return state;
01712 }
01713 
01714 // --- cMenuSchedule ---------------------------------------------------------
01715 
01716 class cMenuSchedule : public cOsdMenu {
01717 private:
01718   cSchedulesLock schedulesLock;
01719   const cSchedules *schedules;
01720   bool now, next;
01721   int otherChannel;
01722   int helpKeys;
01723   int timerState;
01724   eOSState Number(void);
01725   eOSState Record(void);
01726   eOSState Switch(void);
01727   void PrepareScheduleAllThis(const cEvent *Event, const cChannel *Channel);
01728   void PrepareScheduleThisThis(const cEvent *Event, const cChannel *Channel);
01729   void PrepareScheduleThisAll(const cEvent *Event, const cChannel *Channel);
01730   void PrepareScheduleAllAll(const cEvent *Event, const cChannel *Channel);
01731   bool Update(void);
01732   void SetHelpKeys(void);
01733 public:
01734   cMenuSchedule(void);
01735   virtual ~cMenuSchedule();
01736   virtual eOSState ProcessKey(eKeys Key);
01737   };
01738 
01739 cMenuSchedule::cMenuSchedule(void)
01740 :cOsdMenu("")
01741 {
01742   now = next = false;
01743   otherChannel = 0;
01744   helpKeys = -1;
01745   timerState = 0;
01746   Timers.Modified(timerState);
01747   cMenuScheduleItem::SetSortMode(cMenuScheduleItem::ssmAllThis);
01748   cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
01749   if (channel) {
01750      cMenuWhatsOn::SetCurrentChannel(channel->Number());
01751      schedules = cSchedules::Schedules(schedulesLock);
01752      PrepareScheduleAllThis(NULL, channel);
01753      SetHelpKeys();
01754      }
01755 }
01756 
01757 cMenuSchedule::~cMenuSchedule()
01758 {
01759   cMenuWhatsOn::ScheduleEvent(); // makes sure any posted data is cleared
01760 }
01761 
01762 void cMenuSchedule::PrepareScheduleAllThis(const cEvent *Event, const cChannel *Channel)
01763 {
01764   Clear();
01765   SetCols(7, 6, 4);
01766   SetTitle(cString::sprintf(tr("Schedule - %s"), Channel->Name()));
01767   if (schedules && Channel) {
01768      const cSchedule *Schedule = schedules->GetSchedule(Channel);
01769      if (Schedule) {
01770         const cEvent *PresentEvent = Event ? Event : Schedule->GetPresentEvent();
01771         time_t now = time(NULL) - Setup.EPGLinger * 60;
01772         for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
01773             if (ev->EndTime() > now || ev == PresentEvent)
01774                Add(new cMenuScheduleItem(ev), ev == PresentEvent);
01775             }
01776         }
01777      }
01778 }
01779 
01780 void cMenuSchedule::PrepareScheduleThisThis(const cEvent *Event, const cChannel *Channel)
01781 {
01782   Clear();
01783   SetCols(7, 6, 4);
01784   SetTitle(cString::sprintf(tr("This event - %s"), Channel->Name()));
01785   if (schedules && Channel && Event) {
01786      const cSchedule *Schedule = schedules->GetSchedule(Channel);
01787      if (Schedule) {
01788         time_t now = time(NULL) - Setup.EPGLinger * 60;
01789         for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
01790             if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title()))
01791                Add(new cMenuScheduleItem(ev), ev == Event);
01792             }
01793         }
01794      }
01795 }
01796 
01797 void cMenuSchedule::PrepareScheduleThisAll(const cEvent *Event, const cChannel *Channel)
01798 {
01799   Clear();
01800   SetCols(CHNUMWIDTH, CHNAMWIDTH, 7, 6, 4);
01801   SetTitle(tr("This event - all channels"));
01802   if (schedules && Event) {
01803      for (cChannel *ch = Channels.First(); ch; ch = Channels.Next(ch)) {
01804          const cSchedule *Schedule = schedules->GetSchedule(ch);
01805          if (Schedule) {
01806             time_t now = time(NULL) - Setup.EPGLinger * 60;
01807             for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
01808                 if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title()))
01809                    Add(new cMenuScheduleItem(ev, ch, true), ev == Event && ch == Channel);
01810                 }
01811             }
01812          }
01813      }
01814 }
01815 
01816 void cMenuSchedule::PrepareScheduleAllAll(const cEvent *Event, const cChannel *Channel)
01817 {
01818   Clear();
01819   SetCols(CHNUMWIDTH, CHNAMWIDTH, 7, 6, 4);
01820   SetTitle(tr("All events - all channels"));
01821   if (schedules) {
01822      for (cChannel *ch = Channels.First(); ch; ch = Channels.Next(ch)) {
01823          const cSchedule *Schedule = schedules->GetSchedule(ch);
01824          if (Schedule) {
01825             time_t now = time(NULL) - Setup.EPGLinger * 60;
01826             for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) {
01827                 if (ev->EndTime() > now || ev == Event)
01828                    Add(new cMenuScheduleItem(ev, ch, true), ev == Event && ch == Channel);
01829                 }
01830             }
01831          }
01832      }
01833 }
01834 
01835 bool cMenuSchedule::Update(void)
01836 {
01837   bool result = false;
01838   if (Timers.Modified(timerState)) {
01839      for (cOsdItem *item = First(); item; item = Next(item)) {
01840          if (((cMenuScheduleItem *)item)->Update())
01841             result = true;
01842          }
01843      }
01844   return result;
01845 }
01846 
01847 void cMenuSchedule::SetHelpKeys(void)
01848 {
01849   cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
01850   int NewHelpKeys = 0;
01851   if (item) {
01852      if (item->timerMatch == tmFull)
01853         NewHelpKeys = 2;
01854      else
01855         NewHelpKeys = 1;
01856      }
01857   if (NewHelpKeys != helpKeys) {
01858      const char *Red[] = { NULL, tr("Button$Record"), tr("Button$Timer") };
01859      SetHelp(Red[NewHelpKeys], tr("Button$Now"), tr("Button$Next"));
01860      helpKeys = NewHelpKeys;
01861      }
01862 }
01863 
01864 eOSState cMenuSchedule::Number(void)
01865 {
01866   cMenuScheduleItem::IncSortMode();
01867   cMenuScheduleItem *CurrentItem = (cMenuScheduleItem *)Get(Current());
01868   const cChannel *Channel = NULL;
01869   const cEvent *Event = NULL;
01870   if (CurrentItem) {
01871      Event = CurrentItem->event;
01872      Channel = Channels.GetByChannelID(Event->ChannelID(), true);
01873      }
01874   else
01875      Channel = Channels.GetByNumber(cDevice::CurrentChannel());
01876   switch (cMenuScheduleItem::SortMode()) {
01877     case cMenuScheduleItem::ssmAllThis:  PrepareScheduleAllThis(Event, Channel); break;
01878     case cMenuScheduleItem::ssmThisThis: PrepareScheduleThisThis(Event, Channel); break;
01879     case cMenuScheduleItem::ssmThisAll:  PrepareScheduleThisAll(Event, Channel); break;
01880     case cMenuScheduleItem::ssmAllAll:   PrepareScheduleAllAll(Event, Channel); break;
01881     default: esyslog("ERROR: unknown SortMode %d (%s %d)", cMenuScheduleItem::SortMode(), __FUNCTION__, __LINE__);
01882     }
01883   CurrentItem = (cMenuScheduleItem *)Get(Current());
01884   Sort();
01885   SetCurrent(CurrentItem);
01886   Display();
01887   return osContinue;
01888 }
01889 
01890 eOSState cMenuSchedule::Record(void)
01891 {
01892   cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
01893   if (item) {
01894      if (item->timerMatch == tmFull) {
01895         int tm = tmNone;
01896         cTimer *timer = Timers.GetMatch(item->event, &tm);
01897         if (timer)
01898            return AddSubMenu(new cMenuEditTimer(timer));
01899         }
01900      cTimer *timer = new cTimer(item->event);
01901      cTimer *t = Timers.GetTimer(timer);
01902      if (t) {
01903         delete timer;
01904         timer = t;
01905         return AddSubMenu(new cMenuEditTimer(timer));
01906         }
01907      else {
01908         Timers.Add(timer);
01909         Timers.SetModified();
01910         isyslog("timer %s added (active)", *timer->ToDescr());
01911         if (timer->Matches(0, false, NEWTIMERLIMIT))
01912            return AddSubMenu(new cMenuEditTimer(timer));
01913         if (HasSubMenu())
01914            CloseSubMenu();
01915         if (Update())
01916            Display();
01917         SetHelpKeys();
01918         }
01919      }
01920   return osContinue;
01921 }
01922 
01923 eOSState cMenuSchedule::Switch(void)
01924 {
01925   if (otherChannel) {
01926      if (Channels.SwitchTo(otherChannel))
01927         return osEnd;
01928      }
01929   Skins.Message(mtError, tr("Can't switch channel!"));
01930   return osContinue;
01931 }
01932 
01933 eOSState cMenuSchedule::ProcessKey(eKeys Key)
01934 {
01935   bool HadSubMenu = HasSubMenu();
01936   eOSState state = cOsdMenu::ProcessKey(Key);
01937 
01938   if (state == osUnknown) {
01939      switch (Key) {
01940        case k0:      return Number();
01941        case kRecord:
01942        case kRed:    return Record();
01943        case kGreen:  if (schedules) {
01944                         if (!now && !next) {
01945                            int ChannelNr = 0;
01946                            if (Count()) {
01947                               cChannel *channel = Channels.GetByChannelID(((cMenuScheduleItem *)Get(Current()))->event->ChannelID(), true);
01948                               if (channel)
01949                                  ChannelNr = channel->Number();
01950                               }
01951                            now = true;
01952                            return AddSubMenu(new cMenuWhatsOn(schedules, true, ChannelNr));
01953                            }
01954                         now = !now;
01955                         next = !next;
01956                         return AddSubMenu(new cMenuWhatsOn(schedules, now, cMenuWhatsOn::CurrentChannel()));
01957                         }
01958        case kYellow: if (schedules)
01959                         return AddSubMenu(new cMenuWhatsOn(schedules, false, cMenuWhatsOn::CurrentChannel()));
01960                      break;
01961        case kBlue:   if (Count() && otherChannel)
01962                         return Switch();
01963                      break;
01964        case kInfo:
01965        case kOk:     if (Count())
01966                         return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->event, otherChannel, true));
01967                      break;
01968        default:      break;
01969        }
01970      }
01971   else if (!HasSubMenu()) {
01972      now = next = false;
01973      const cEvent *ei = cMenuWhatsOn::ScheduleEvent();
01974      if (ei) {
01975         cChannel *channel = Channels.GetByChannelID(ei->ChannelID(), true);
01976         if (channel) {
01977            cMenuScheduleItem::SetSortMode(cMenuScheduleItem::ssmAllThis);
01978            PrepareScheduleAllThis(NULL, channel);
01979            if (channel->Number() != cDevice::CurrentChannel()) {
01980               otherChannel = channel->Number();
01981               SetHelp(Count() ? tr("Button$Record") : NULL, tr("Button$Now"), tr("Button$Next"), tr("Button$Switch"));
01982               }
01983            Display();
01984            }
01985         }
01986      else if (HadSubMenu && Update())
01987         Display();
01988      if (Key != kNone)
01989         SetHelpKeys();
01990      }
01991   return state;
01992 }
01993 
01994 // --- cMenuCommands ---------------------------------------------------------
01995 
01996 cMenuCommands::cMenuCommands(const char *Title, cList<cNestedItem> *Commands, const char *Parameters)
01997 :cOsdMenu(Title)
01998 {
01999   result = NULL;
02000   SetHasHotkeys();
02001   commands = Commands;
02002   parameters = Parameters;
02003   for (cNestedItem *Command = commands->First(); Command; Command = commands->Next(Command)) {
02004       const char *s = Command->Text();
02005       if (Command->SubItems())
02006          Add(new cOsdItem(hk(cString::sprintf("%s...", s))));
02007       else if (Parse(s))
02008          Add(new cOsdItem(hk(title)));
02009       }
02010 }
02011 
02012 cMenuCommands::~cMenuCommands()
02013 {
02014   free(result);
02015 }
02016 
02017 bool cMenuCommands::Parse(const char *s)
02018 {
02019   const char *p = strchr(s, ':');
02020   if (p) {
02021      int l = p - s;
02022      if (l > 0) {
02023         char t[l + 1];
02024         stripspace(strn0cpy(t, s, l + 1));
02025         l = strlen(t);
02026         if (l > 1 && t[l - 1] == '?') {
02027            t[l - 1] = 0;
02028            confirm = true;
02029            }
02030         else
02031            confirm = false;
02032         title = t;
02033         command = skipspace(p + 1);
02034         return true;
02035         }
02036      }
02037   return false;
02038 }
02039 
02040 eOSState cMenuCommands::Execute(void)
02041 {
02042   cNestedItem *Command = commands->Get(Current());
02043   if (Command) {
02044      if (Command->SubItems())
02045         return AddSubMenu(new cMenuCommands(Title(), Command->SubItems(), parameters));
02046      if (Parse(Command->Text())) {
02047         if (!confirm || Interface->Confirm(cString::sprintf("%s?", *title))) {
02048            Skins.Message(mtStatus, cString::sprintf("%s...", *title));
02049            free(result);
02050            result = NULL;
02051            cString cmdbuf;
02052            if (!isempty(parameters))
02053               cmdbuf = cString::sprintf("%s %s", *command, *parameters);
02054            const char *cmd = *cmdbuf ? *cmdbuf : *command;
02055            dsyslog("executing command '%s'", cmd);
02056            cPipe p;
02057            if (p.Open(cmd, "r")) {
02058               int l = 0;
02059               int c;
02060               while ((c = fgetc(p)) != EOF) {
02061                     if (l % 20 == 0) {
02062                        if (char *NewBuffer = (char *)realloc(result, l + 21))
02063                           result = NewBuffer;
02064                        else {
02065                           esyslog("ERROR: out of memory");
02066                           break;
02067                           }
02068                        }
02069                     result[l++] = char(c);
02070                     }
02071               if (result)
02072                  result[l] = 0;
02073               p.Close();
02074               }
02075            else
02076               esyslog("ERROR: can't open pipe for command '%s'", cmd);
02077            Skins.Message(mtStatus, NULL);
02078            if (result)
02079               return AddSubMenu(new cMenuText(title, result, fontFix));
02080            return osEnd;
02081            }
02082         }
02083      }
02084   return osContinue;
02085 }
02086 
02087 eOSState cMenuCommands::ProcessKey(eKeys Key)
02088 {
02089   eOSState state = cOsdMenu::ProcessKey(Key);
02090 
02091   if (state == osUnknown) {
02092      switch (Key) {
02093        case kRed:
02094        case kGreen:
02095        case kYellow:
02096        case kBlue:   return osContinue;
02097        case kOk:     return Execute();
02098        default:      break;
02099        }
02100      }
02101   return state;
02102 }
02103 
02104 // --- cMenuCam --------------------------------------------------------------
02105 
02106 class cMenuCam : public cOsdMenu {
02107 private:
02108   cCamSlot *camSlot;
02109   cCiMenu *ciMenu;
02110   cCiEnquiry *ciEnquiry;
02111   char *input;
02112   int offset;
02113   time_t lastCamExchange;
02114   void GenerateTitle(const char *s = NULL);
02115   void QueryCam(void);
02116   void AddMultiLineItem(const char *s);
02117   void Set(void);
02118   eOSState Select(void);
02119 public:
02120   cMenuCam(cCamSlot *CamSlot);
02121   virtual ~cMenuCam();
02122   virtual eOSState ProcessKey(eKeys Key);
02123   };
02124 
02125 cMenuCam::cMenuCam(cCamSlot *CamSlot)
02126 :cOsdMenu("", 1) // tab necessary for enquiry!
02127 {
02128   camSlot = CamSlot;
02129   ciMenu = NULL;
02130   ciEnquiry = NULL;
02131   input = NULL;
02132   offset = 0;
02133   lastCamExchange = time(NULL);
02134   SetNeedsFastResponse(true);
02135   QueryCam();
02136 }
02137 
02138 cMenuCam::~cMenuCam()
02139 {
02140   if (ciMenu)
02141      ciMenu->Abort();
02142   delete ciMenu;
02143   if (ciEnquiry)
02144      ciEnquiry->Abort();
02145   delete ciEnquiry;
02146   free(input);
02147 }
02148 
02149 void cMenuCam::GenerateTitle(const char *s)
02150 {
02151   SetTitle(cString::sprintf("CAM %d - %s", camSlot->SlotNumber(), (s && *s) ? s : camSlot->GetCamName()));
02152 }
02153 
02154 void cMenuCam::QueryCam(void)
02155 {
02156   delete ciMenu;
02157   ciMenu = NULL;
02158   delete ciEnquiry;
02159   ciEnquiry = NULL;
02160   if (camSlot->HasUserIO()) {
02161      ciMenu = camSlot->GetMenu();
02162      ciEnquiry = camSlot->GetEnquiry();
02163      }
02164   Set();
02165 }
02166 
02167 void cMenuCam::Set(void)
02168 {
02169   if (ciMenu) {
02170      Clear();
02171      free(input);
02172      input = NULL;
02173      dsyslog("CAM %d: Menu ------------------", camSlot->SlotNumber());
02174      offset = 0;
02175      SetHasHotkeys(ciMenu->Selectable());
02176      GenerateTitle(ciMenu->TitleText());
02177      dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->TitleText());
02178      if (*ciMenu->SubTitleText()) {
02179         dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->SubTitleText());
02180         AddMultiLineItem(ciMenu->SubTitleText());
02181         offset = Count();
02182         }
02183      for (int i = 0; i < ciMenu->NumEntries(); i++) {
02184          Add(new cOsdItem(hk(ciMenu->Entry(i)), osUnknown, ciMenu->Selectable()));
02185          dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->Entry(i));
02186          }
02187      if (*ciMenu->BottomText()) {
02188         AddMultiLineItem(ciMenu->BottomText());
02189         dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->BottomText());
02190         }
02191      cRemote::TriggerLastActivity();
02192      }
02193   else if (ciEnquiry) {
02194      Clear();
02195      int Length = ciEnquiry->ExpectedLength();
02196      free(input);
02197      input = MALLOC(char, Length + 1);
02198      *input = 0;
02199      GenerateTitle();
02200      Add(new cOsdItem(ciEnquiry->Text(), osUnknown, false));
02201      Add(new cOsdItem("", osUnknown, false));
02202      Add(new cMenuEditNumItem("", input, Length, ciEnquiry->Blind()));
02203      }
02204   Display();
02205 }
02206 
02207 void cMenuCam::AddMultiLineItem(const char *s)
02208 {
02209   while (s && *s) {
02210         const char *p = strchr(s, '\n');
02211         int l = p ? p - s : strlen(s);
02212         cOsdItem *item = new cOsdItem;
02213         item->SetSelectable(false);
02214         item->SetText(strndup(s, l), false);
02215         Add(item);
02216         s = p ? p + 1 : p;
02217         }
02218 }
02219 
02220 eOSState cMenuCam::Select(void)
02221 {
02222   if (ciMenu) {
02223      if (ciMenu->Selectable()) {
02224         ciMenu->Select(Current() - offset);
02225         dsyslog("CAM %d: select %d", camSlot->SlotNumber(), Current() - offset);
02226         }
02227      else
02228         ciMenu->Cancel();
02229      }
02230   else if (ciEnquiry) {
02231      if (ciEnquiry->ExpectedLength() < 0xFF && int(strlen(input)) != ciEnquiry->ExpectedLength()) {
02232         char buffer[64];
02233         snprintf(buffer, sizeof(buffer), tr("Please enter %d digits!"), ciEnquiry->ExpectedLength());
02234         Skins.Message(mtError, buffer);
02235         return osContinue;
02236         }
02237      ciEnquiry->Reply(input);
02238      dsyslog("CAM %d: entered '%s'", camSlot->SlotNumber(), ciEnquiry->Blind() ? "****" : input);
02239      }
02240   QueryCam();
02241   return osContinue;
02242 }
02243 
02244 eOSState cMenuCam::ProcessKey(eKeys Key)
02245 {
02246   if (!camSlot->HasMMI())
02247      return osBack;
02248 
02249   eOSState state = cOsdMenu::ProcessKey(Key);
02250 
02251   if (ciMenu || ciEnquiry) {
02252      lastCamExchange = time(NULL);
02253      if (state == osUnknown) {
02254         switch (Key) {
02255           case kOk: return Select();
02256           default: break;
02257           }
02258         }
02259      else if (state == osBack) {
02260         if (ciMenu)
02261            ciMenu->Cancel();
02262         if (ciEnquiry)
02263            ciEnquiry->Cancel();
02264         QueryCam();
02265         return osContinue;
02266         }
02267      if (ciMenu && ciMenu->HasUpdate()) {
02268         QueryCam();
02269         return osContinue;
02270         }
02271      }
02272   else if (time(NULL) - lastCamExchange < CAMRESPONSETIMEOUT)
02273      QueryCam();
02274   else {
02275      Skins.Message(mtError, tr("CAM not responding!"));
02276      return osBack;
02277      }
02278   return state;
02279 }
02280 
02281 // --- CamControl ------------------------------------------------------------
02282 
02283 cOsdObject *CamControl(void)
02284 {
02285   for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
02286       if (CamSlot->HasUserIO())
02287          return new cMenuCam(CamSlot);
02288       }
02289   return NULL;
02290 }
02291 
02292 // --- cMenuRecording --------------------------------------------------------
02293 
02294 class cMenuRecording : public cOsdMenu {
02295 private:
02296   const cRecording *recording;
02297   bool withButtons;
02298 public:
02299   cMenuRecording(const cRecording *Recording, bool WithButtons = false);
02300   virtual void Display(void);
02301   virtual eOSState ProcessKey(eKeys Key);
02302 };
02303 
02304 cMenuRecording::cMenuRecording(const cRecording *Recording, bool WithButtons)
02305 :cOsdMenu(tr("Recording info"))
02306 {
02307   recording = Recording;
02308   withButtons = WithButtons;
02309   if (withButtons)
02310      SetHelp(tr("Button$Play"), tr("Button$Rewind"));
02311 }
02312 
02313 void cMenuRecording::Display(void)
02314 {
02315   cOsdMenu::Display();
02316   DisplayMenu()->SetRecording(recording);
02317   if (recording->Info()->Description())
02318      cStatus::MsgOsdTextItem(recording->Info()->Description());
02319 }
02320 
02321 eOSState cMenuRecording::ProcessKey(eKeys Key)
02322 {
02323   switch (int(Key)) {
02324     case kUp|k_Repeat:
02325     case kUp:
02326     case kDown|k_Repeat:
02327     case kDown:
02328     case kLeft|k_Repeat:
02329     case kLeft:
02330     case kRight|k_Repeat:
02331     case kRight:
02332                   DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight);
02333                   cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft);
02334                   return osContinue;
02335     case kInfo:   return osBack;
02336     default: break;
02337     }
02338 
02339   eOSState state = cOsdMenu::ProcessKey(Key);
02340 
02341   if (state == osUnknown) {
02342      switch (Key) {
02343        case kRed:    if (withButtons)
02344                         Key = kOk; // will play the recording, even if recording commands are defined
02345        case kGreen:  if (!withButtons)
02346                         break;
02347                      cRemote::Put(Key, true);
02348                      // continue with osBack to close the info menu and process the key
02349        case kOk:     return osBack;
02350        default: break;
02351        }
02352      }
02353   return state;
02354 }
02355 
02356 // --- cMenuRecordingItem ----------------------------------------------------
02357 
02358 class cMenuRecordingItem : public cOsdItem {
02359 private:
02360   char *fileName;
02361   char *name;
02362   int totalEntries, newEntries;
02363 public:
02364   cMenuRecordingItem(cRecording *Recording, int Level);
02365   ~cMenuRecordingItem();
02366   void IncrementCounter(bool New);
02367   const char *Name(void) { return name; }
02368   const char *FileName(void) { return fileName; }
02369   bool IsDirectory(void) { return name != NULL; }
02370   };
02371 
02372 cMenuRecordingItem::cMenuRecordingItem(cRecording *Recording, int Level)
02373 {
02374   fileName = strdup(Recording->FileName());
02375   name = NULL;
02376   totalEntries = newEntries = 0;
02377   SetText(Recording->Title('\t', true, Level));
02378   if (*Text() == '\t')
02379      name = strdup(Text() + 2); // 'Text() + 2' to skip the two '\t'
02380 }
02381 
02382 cMenuRecordingItem::~cMenuRecordingItem()
02383 {
02384   free(fileName);
02385   free(name);
02386 }
02387 
02388 void cMenuRecordingItem::IncrementCounter(bool New)
02389 {
02390   totalEntries++;
02391   if (New)
02392      newEntries++;
02393   SetText(cString::sprintf("%d\t\t%d\t%s", totalEntries, newEntries, name));
02394 }
02395 
02396 // --- cMenuEditRecording ----------------------------------------------------
02397 
02398 class cMenuEditRecording : public cOsdMenu {
02399 private:
02400   char name[MaxFileName];
02401   cMenuEditStrItem *file;
02402   cOsdItem *marksItem, *resumeItem;
02403   bool isResume, isMarks;
02404   cRecording *recording;
02405   void SetHelpKeys(void);
02406   eOSState SetFolder(void);
02407 public:
02408   cMenuEditRecording(cRecording *Recording);
02409   virtual eOSState ProcessKey(eKeys Key);
02410 };
02411 
02412 cMenuEditRecording::cMenuEditRecording(cRecording *Recording)
02413 :cOsdMenu(tr("Edit recording"), 14)
02414 {
02415   cMarks marks;
02416 
02417   file = NULL;
02418   recording = Recording;
02419 
02420   if (recording) {
02421      Utf8Strn0Cpy(name, recording->Name(), sizeof(name));
02422      Add(file = new cMenuEditStrItem(tr("File"), name, sizeof(name)));
02423 
02424      Add(new cOsdItem("", osUnknown, false));
02425 
02426      Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Date"), *DayDateTime(recording->Start())), osUnknown, false));
02427 
02428      cChannel *channel = Channels.GetByChannelID(((cRecordingInfo *)recording->Info())->ChannelID());
02429      if (channel)
02430         Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Channel"), *ChannelString(channel, 0)), osUnknown, false));
02431 
02432      int recLen = recording->LengthInSeconds();
02433      if (recLen >= 0)
02434         Add(new cOsdItem(cString::sprintf("%s:\t%d:%02d:%02d", tr("Length"), recLen / 3600, recLen / 60 % 60, recLen % 60), osUnknown, false));
02435      else
02436         recLen = 0;
02437 
02438      int dirSize = DirSizeMB(recording->FileName());
02439      cString bitRate = recLen ? cString::sprintf(" (%.2f MBit/s)", 8.0 * dirSize / recLen) : cString("");
02440      Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Format"), recording->IsPesRecording() ? tr("PES") : tr("TS")), osUnknown, false));
02441      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));
02442 
02443      Add(new cOsdItem("", osUnknown, false));
02444 
02445      isMarks = marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && marks.Count();
02446      marksItem = new cOsdItem(tr("Delete marks information?"), osUser1, isMarks);
02447      Add(marksItem);
02448 
02449      cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
02450      isResume = (ResumeFile.Read() != -1);
02451      resumeItem = new cOsdItem(tr("Delete resume information?"), osUser2, isResume);
02452      Add(resumeItem);
02453      }
02454 
02455   SetHelpKeys();
02456 }
02457 
02458 void cMenuEditRecording::SetHelpKeys(void)
02459 {
02460   SetHelp(tr("Button$Folder"), tr("Button$Cut"), tr("Button$Copy"), tr("Button$Rename/Move"));
02461 }
02462 
02463 eOSState cMenuEditRecording::SetFolder(void)
02464 {
02465   cMenuFolder *mf = (cMenuFolder *)SubMenu();
02466   if (mf) {
02467      cString Folder = mf->GetFolder();
02468      char *p = strrchr(name, FOLDERDELIMCHAR);
02469      if (p)
02470         p++;
02471      else
02472         p = name;
02473      if (!isempty(*Folder))
02474         strn0cpy(name, cString::sprintf("%s%c%s", *Folder, FOLDERDELIMCHAR, p), sizeof(name));
02475      else if (p != name)
02476         memmove(name, p, strlen(p) + 1);
02477      SetCurrent(file);
02478      Display();
02479      }
02480   return CloseSubMenu();
02481 }
02482 
02483 eOSState cMenuEditRecording::ProcessKey(eKeys Key)
02484 {
02485   eOSState state = cOsdMenu::ProcessKey(Key);
02486 
02487   if (state == osUnknown) {
02488      switch (Key) {
02489        case kRed:
02490             return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, name));
02491             break;
02492        case kGreen:
02493             if (!cCutter::Active()) {
02494                if (!isMarks)
02495                   Skins.Message(mtError, tr("No editing marks defined!"));
02496                else if (!cCutter::Start(recording->FileName(), strcmp(recording->Name(), name) ? *NewVideoFileName(recording->FileName(), name) : NULL, false))
02497                   Skins.Message(mtError, tr("Can't start editing process!"));
02498                else
02499                   Skins.Message(mtInfo, tr("Editing process started"));
02500                }
02501             else
02502                Skins.Message(mtError, tr("Editing process already active!"));
02503             return osContinue;
02504        case kYellow:
02505        case kBlue:
02506             if (strcmp(recording->Name(), name)) {
02507                if (!cFileTransfer::Active()) {
02508                   if (cFileTransfer::Start(recording, name, (Key == kYellow)))
02509                      Skins.Message(mtInfo, tr("File transfer started"));
02510                   else
02511                      Skins.Message(mtError, tr("Can't start file transfer!"));
02512                   }
02513                else
02514                   Skins.Message(mtError, tr("File transfer already active!"));
02515                }
02516             return osRecordings;
02517        default:
02518             break;
02519        }
02520      return osContinue;
02521      }
02522   else if (state == osEnd && HasSubMenu())
02523      state = SetFolder();
02524   else if (state == osUser1) {
02525      if (isMarks && Interface->Confirm(tr("Delete marks information?"))) {
02526         cMarks marks;
02527         marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording());
02528         cMark *mark = marks.First();
02529         while (mark) {
02530               cMark *nextmark = marks.Next(mark);
02531               marks.Del(mark);
02532               mark = nextmark;
02533               }
02534         marks.Save();
02535         isMarks = false;
02536         marksItem->SetSelectable(isMarks);
02537         SetCurrent(First());
02538         Display();
02539         }
02540      return osContinue;
02541      }
02542   else if (state == osUser2) {
02543      if (isResume && Interface->Confirm(tr("Delete resume information?"))) {
02544         cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
02545         ResumeFile.Delete();
02546         isResume = false;
02547         resumeItem->SetSelectable(isResume);
02548         SetCurrent(First());
02549         Display();
02550         }
02551      return osContinue;
02552      }
02553 
02554   return state;
02555 }
02556 
02557 // --- cMenuRecordings -------------------------------------------------------
02558 
02559 cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus)
02560 :cOsdMenu(Base ? Base : tr("Recordings"), 9, 6, 6)
02561 {
02562   base = Base ? strdup(Base) : NULL;
02563   level = Setup.RecordingDirs ? Level : -1;
02564   Recordings.StateChanged(recordingsState); // just to get the current state
02565   helpKeys = -1;
02566   Display(); // this keeps the higher level menus from showing up briefly when pressing 'Back' during replay
02567   Set();
02568   SetFreeDiskDisplay(true);
02569   if (Current() < 0)
02570      SetCurrent(First());
02571   else if (OpenSubMenus && cReplayControl::LastReplayed() && Open(true))
02572      return;
02573   Display();
02574   SetHelpKeys();
02575 }
02576 
02577 cMenuRecordings::~cMenuRecordings()
02578 {
02579   helpKeys = -1;
02580   free(base);
02581 }
02582 
02583 bool cMenuRecordings::SetFreeDiskDisplay(bool Force)
02584 {
02585   if (FreeDiskSpace.HasChanged(Force)) {
02586      //XXX -> skin function!!!
02587      SetTitle(cString::sprintf("%s  -  %s", base ? base : tr("Recordings"), FreeDiskSpace.FreeDiskSpaceString()));
02588      return true;
02589      }
02590   return false;
02591 }
02592 
02593 void cMenuRecordings::SetHelpKeys(void)
02594 {
02595   cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
02596   int NewHelpKeys = 0;
02597   if (ri) {
02598      if (ri->IsDirectory())
02599         NewHelpKeys = 1;
02600      else {
02601         NewHelpKeys = 2;
02602         cRecording *recording = GetRecording(ri);
02603         if (recording && recording->Info()->Title())
02604            NewHelpKeys = 3;
02605         }
02606      }
02607   if (NewHelpKeys != helpKeys) {
02608      switch (NewHelpKeys) {
02609        case 0: SetHelp(NULL); break;
02610        case 1: SetHelp(tr("Button$Open")); break;
02611        case 2:
02612        case 3: SetHelp(RecordingCommands.Count() ? tr("Commands") : tr("Button$Play"), tr("Button$Rewind"), tr("Button$Delete"), NewHelpKeys == 3 ? tr("Button$Info") : NULL);
02613        default: ;
02614        }
02615      helpKeys = NewHelpKeys;
02616      }
02617 }
02618 
02619 void cMenuRecordings::Set(bool Refresh)
02620 {
02621   const char *CurrentRecording = cReplayControl::LastReplayed();
02622   cMenuRecordingItem *LastItem = NULL;
02623   char *LastItemText = NULL;
02624   cThreadLock RecordingsLock(&Recordings);
02625   if (Refresh) {
02626      cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
02627      if (ri) {
02628         cRecording *Recording = Recordings.GetByName(ri->FileName());
02629         if (Recording)
02630            CurrentRecording = Recording->FileName();
02631         }
02632      }
02633   Clear();
02634   Recordings.Sort();
02635   for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) {
02636       if (!base || (strstr(recording->Name(), base) == recording->Name() && recording->Name()[strlen(base)] == FOLDERDELIMCHAR)) {
02637          cMenuRecordingItem *Item = new cMenuRecordingItem(recording, level);
02638          if (*Item->Text() && (!Item->IsDirectory() || (!LastItem || !LastItem->IsDirectory() || strcmp(Item->Text(), LastItemText) != 0))) {
02639             Add(Item);
02640             LastItem = Item;
02641             free(LastItemText);
02642             LastItemText = strdup(LastItem->Text()); // must use a copy because of the counters!
02643             }
02644          else
02645             delete Item;
02646          if (LastItem) {
02647             if (CurrentRecording && strcmp(CurrentRecording, recording->FileName()) == 0)
02648                SetCurrent(LastItem);
02649             if (LastItem->IsDirectory())
02650                LastItem->IncrementCounter(recording->IsNew());
02651             }
02652          }
02653       }
02654   free(LastItemText);
02655   Refresh |= SetFreeDiskDisplay(Refresh);
02656   if (Refresh)
02657      Display();
02658 }
02659 
02660 cRecording *cMenuRecordings::GetRecording(cMenuRecordingItem *Item)
02661 {
02662   cRecording *recording = Recordings.GetByName(Item->FileName());
02663   if (!recording)
02664      Skins.Message(mtError, tr("Error while accessing recording!"));
02665   return recording;
02666 }
02667 
02668 bool cMenuRecordings::Open(bool OpenSubMenus)
02669 {
02670   cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
02671   if (ri && ri->IsDirectory()) {
02672      const char *t = ri->Name();
02673      cString buffer;
02674      if (base) {
02675         buffer = cString::sprintf("%s~%s", base, t);
02676         t = buffer;
02677         }
02678      AddSubMenu(new cMenuRecordings(t, level + 1, OpenSubMenus));
02679      return true;
02680      }
02681   return false;
02682 }
02683 
02684 eOSState cMenuRecordings::Play(void)
02685 {
02686   cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
02687   if (ri) {
02688      if (ri->IsDirectory())
02689         Open();
02690      else {
02691         cRecording *recording = GetRecording(ri);
02692         if (recording) {
02693            cReplayControl::SetRecording(recording->FileName(), recording->Title());
02694            return osReplay;
02695            }
02696         }
02697      }
02698   return osContinue;
02699 }
02700 
02701 eOSState cMenuRecordings::Rewind(void)
02702 {
02703   if (HasSubMenu() || Count() == 0)
02704      return osContinue;
02705   cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
02706   if (ri && !ri->IsDirectory()) {
02707      cRecording *recording = GetRecording(ri);
02708      if (recording) {
02709         cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording
02710         cResumeFile ResumeFile(ri->FileName(), recording->IsPesRecording());
02711         ResumeFile.Delete();
02712         return Play();
02713         }
02714      }
02715   return osContinue;
02716 }
02717 
02718 eOSState cMenuRecordings::Delete(void)
02719 {
02720   if (HasSubMenu() || Count() == 0)
02721      return osContinue;
02722   cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
02723   if (ri && !ri->IsDirectory()) {
02724      if (Interface->Confirm(tr("Delete recording?"))) {
02725         cRecordControl *rc = cRecordControls::GetRecordControl(ri->FileName());
02726         if (rc) {
02727            if (Interface->Confirm(tr("Timer still recording - really delete?"))) {
02728               cTimer *timer = rc->Timer();
02729               if (timer) {
02730                  timer->Skip();
02731                  cRecordControls::Process(time(NULL));
02732                  if (timer->IsSingleEvent()) {
02733                     isyslog("deleting timer %s", *timer->ToDescr());
02734                     Timers.Del(timer);
02735                     }
02736                  Timers.SetModified();
02737                  }
02738               }
02739            else
02740               return osContinue;
02741            }
02742         cRecording *recording = GetRecording(ri);
02743         if (recording) {
02744            if (cCutter::Active(ri->FileName())) {
02745               if (Interface->Confirm(tr("Recording is being edited - really delete?"))) {
02746                  cCutter::Stop();
02747                  recording = Recordings.GetByName(ri->FileName()); // cCutter::Stop() might have deleted it if it was the edited version
02748                  // we continue with the code below even if recording is NULL,
02749                  // in order to have the menu updated etc.
02750                  }
02751               else
02752                  return osContinue;
02753               }
02754            if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), ri->FileName()) == 0)
02755               cControl::Shutdown();
02756            if (!recording || recording->Delete()) {
02757               cReplayControl::ClearLastReplayed(ri->FileName());
02758               Recordings.DelByName(ri->FileName());
02759               cOsdMenu::Del(Current());
02760               SetHelpKeys();
02761               SetFreeDiskDisplay(true);
02762               Display();
02763               if (!Count())
02764                  return osBack;
02765               }
02766            else
02767               Skins.Message(mtError, tr("Error while deleting recording!"));
02768            }
02769         }
02770      }
02771   return osContinue;
02772 }
02773 
02774 eOSState cMenuRecordings::Info(void)
02775 {
02776   if (HasSubMenu() || Count() == 0)
02777      return osContinue;
02778   cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
02779   if (ri && !ri->IsDirectory()) {
02780      cRecording *recording = GetRecording(ri);
02781      if (recording && recording->Info()->Title())
02782         return AddSubMenu(new cMenuRecording(recording, true));
02783      }
02784   return osContinue;
02785 }
02786 
02787 eOSState cMenuRecordings::Commands(eKeys Key)
02788 {
02789   if (HasSubMenu() || Count() == 0)
02790      return osContinue;
02791   cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
02792   if (ri && !ri->IsDirectory()) {
02793      cRecording *recording = GetRecording(ri);
02794      if (recording) {
02795         cMenuCommands *menu;
02796         eOSState state = AddSubMenu(menu = new cMenuCommands(tr("Recording commands"), &RecordingCommands, cString::sprintf("\"%s\"", *strescape(recording->FileName(), "\\\"$"))));
02797         if (Key != kNone)
02798            state = menu->ProcessKey(Key);
02799         return state;
02800         }
02801      }
02802   return osContinue;
02803 }
02804 
02805 eOSState cMenuRecordings::Edit(void)
02806 {
02807   if (HasSubMenu() || Count() == 0)
02808      return osContinue;
02809   cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
02810   if (ri && !ri->IsDirectory()) {
02811      cRecording *recording = GetRecording(ri);
02812      if (recording)
02813         return AddSubMenu(new cMenuEditRecording(recording));
02814      }
02815   return osContinue;
02816 }
02817 
02818 eOSState cMenuRecordings::ProcessKey(eKeys Key)
02819 {
02820   bool HadSubMenu = HasSubMenu();
02821   eOSState state = cOsdMenu::ProcessKey(Key);
02822 
02823   if (state == osUnknown) {
02824      switch (Key) {
02825        case kPlay:
02826        case kOk:     return Play();
02827        case kRed:    return (helpKeys > 1 && RecordingCommands.Count()) ? Commands() : Play();
02828        case kGreen:  return Rewind();
02829        case kYellow: return Delete();
02830        case kInfo:
02831        case kBlue:   return Info();
02832        case k0:      return Edit();
02833        case k1...k9: return Commands(Key);
02834        case kNone:   if (Recordings.StateChanged(recordingsState))
02835                         Set(true);
02836                      break;
02837        default: break;
02838        }
02839      }
02840   if (Key == kYellow && HadSubMenu && !HasSubMenu()) {
02841      // the last recording in a subdirectory was deleted, so let's go back up
02842      cOsdMenu::Del(Current());
02843      if (!Count())
02844         return osBack;
02845      Display();
02846      }
02847   if (!HasSubMenu()) {
02848      if (HadSubMenu)
02849         SetFreeDiskDisplay();
02850      if (Key != kNone)
02851         SetHelpKeys();
02852      }
02853   return state;
02854 }
02855 
02856 // --- cMenuSetupBase --------------------------------------------------------
02857 
02858 class cMenuSetupBase : public cMenuSetupPage {
02859 protected:
02860   cSetup data;
02861   virtual void Store(void);
02862 public:
02863   cMenuSetupBase(void);
02864   };
02865 
02866 cMenuSetupBase::cMenuSetupBase(void)
02867 {
02868   data = Setup;
02869 }
02870 
02871 void cMenuSetupBase::Store(void)
02872 {
02873   Setup = data;
02874   cOsdProvider::UpdateOsdSize(true);
02875   Setup.Save();
02876 }
02877 
02878 // --- cMenuSetupOSD ---------------------------------------------------------
02879 
02880 class cMenuSetupOSD : public cMenuSetupBase {
02881 private:
02882   const char *useSmallFontTexts[3];
02883   int osdLanguageIndex;
02884   int numSkins;
02885   int originalSkinIndex;
02886   int skinIndex;
02887   const char **skinDescriptions;
02888   cThemes themes;
02889   int originalThemeIndex;
02890   int themeIndex;
02891   cStringList fontOsdNames, fontSmlNames, fontFixNames;
02892   int fontOsdIndex, fontSmlIndex, fontFixIndex;
02893   virtual void Set(void);
02894 public:
02895   cMenuSetupOSD(void);
02896   virtual ~cMenuSetupOSD();
02897   virtual eOSState ProcessKey(eKeys Key);
02898   };
02899 
02900 cMenuSetupOSD::cMenuSetupOSD(void)
02901 {
02902   osdLanguageIndex = I18nCurrentLanguage();
02903   numSkins = Skins.Count();
02904   skinIndex = originalSkinIndex = Skins.Current()->Index();
02905   skinDescriptions = new const char*[numSkins];
02906   themes.Load(Skins.Current()->Name());
02907   themeIndex = originalThemeIndex = Skins.Current()->Theme() ? themes.GetThemeIndex(Skins.Current()->Theme()->Description()) : 0;
02908   cFont::GetAvailableFontNames(&fontOsdNames);
02909   cFont::GetAvailableFontNames(&fontSmlNames);
02910   cFont::GetAvailableFontNames(&fontFixNames, true);
02911   fontOsdNames.Insert(strdup(DefaultFontOsd));
02912   fontSmlNames.Insert(strdup(DefaultFontSml));
02913   fontFixNames.Insert(strdup(DefaultFontFix));
02914   fontOsdIndex = max(0, fontOsdNames.Find(Setup.FontOsd));
02915   fontSmlIndex = max(0, fontSmlNames.Find(Setup.FontSml));
02916   fontFixIndex = max(0, fontFixNames.Find(Setup.FontFix));
02917   Set();
02918 }
02919 
02920 cMenuSetupOSD::~cMenuSetupOSD()
02921 {
02922   delete[] skinDescriptions;
02923 }
02924 
02925 void cMenuSetupOSD::Set(void)
02926 {
02927   int current = Current();
02928   for (cSkin *Skin = Skins.First(); Skin; Skin = Skins.Next(Skin))
02929       skinDescriptions[Skin->Index()] = Skin->Description();
02930   useSmallFontTexts[0] = tr("never");
02931   useSmallFontTexts[1] = tr("skin dependent");
02932   useSmallFontTexts[2] = tr("always");
02933   Clear();
02934   SetSection(tr("OSD"));
02935   Add(new cMenuEditStraItem(tr("Setup.OSD$Language"),               &osdLanguageIndex, I18nNumLanguagesWithLocale(), &I18nLanguages()->At(0)));
02936   Add(new cMenuEditStraItem(tr("Setup.OSD$Skin"),                   &skinIndex, numSkins, skinDescriptions));
02937   if (themes.NumThemes())
02938   Add(new cMenuEditStraItem(tr("Setup.OSD$Theme"),                  &themeIndex, themes.NumThemes(), themes.Descriptions()));
02939   Add(new cMenuEditPrcItem( tr("Setup.OSD$Left (%)"),               &data.OSDLeftP, 0.0, 0.5));
02940   Add(new cMenuEditPrcItem( tr("Setup.OSD$Top (%)"),                &data.OSDTopP, 0.0, 0.5));
02941   Add(new cMenuEditPrcItem( tr("Setup.OSD$Width (%)"),              &data.OSDWidthP, 0.5, 1.0));
02942   Add(new cMenuEditPrcItem( tr("Setup.OSD$Height (%)"),             &data.OSDHeightP, 0.5, 1.0));
02943   Add(new cMenuEditIntItem( tr("Setup.OSD$Message time (s)"),       &data.OSDMessageTime, 1, 60));
02944   Add(new cMenuEditStraItem(tr("Setup.OSD$Use small font"),         &data.UseSmallFont, 3, useSmallFontTexts));
02945   Add(new cMenuEditBoolItem(tr("Setup.OSD$Anti-alias"),             &data.AntiAlias));
02946   Add(new cMenuEditStraItem(tr("Setup.OSD$Default font"),           &fontOsdIndex, fontOsdNames.Size(), &fontOsdNames[0]));
02947   Add(new cMenuEditStraItem(tr("Setup.OSD$Small font"),             &fontSmlIndex, fontSmlNames.Size(), &fontSmlNames[0]));
02948   Add(new cMenuEditStraItem(tr("Setup.OSD$Fixed font"),             &fontFixIndex, fontFixNames.Size(), &fontFixNames[0]));
02949   Add(new cMenuEditPrcItem( tr("Setup.OSD$Default font size (%)"),  &data.FontOsdSizeP, 0.01, 0.1, 1));
02950   Add(new cMenuEditPrcItem( tr("Setup.OSD$Small font size (%)"),    &data.FontSmlSizeP, 0.01, 0.1, 1));
02951   Add(new cMenuEditPrcItem( tr("Setup.OSD$Fixed font size (%)"),    &data.FontFixSizeP, 0.01, 0.1, 1));
02952   Add(new cMenuEditBoolItem(tr("Setup.OSD$Channel info position"),  &data.ChannelInfoPos, tr("bottom"), tr("top")));
02953   Add(new cMenuEditIntItem( tr("Setup.OSD$Channel info time (s)"),  &data.ChannelInfoTime, 1, 60));
02954   Add(new cMenuEditBoolItem(tr("Setup.OSD$Info on channel switch"), &data.ShowInfoOnChSwitch));
02955   Add(new cMenuEditBoolItem(tr("Setup.OSD$Timeout requested channel info"), &data.TimeoutRequChInfo));
02956   Add(new cMenuEditBoolItem(tr("Setup.OSD$Scroll pages"),           &data.MenuScrollPage));
02957   Add(new cMenuEditBoolItem(tr("Setup.OSD$Scroll wraps"),           &data.MenuScrollWrap));
02958   Add(new cMenuEditBoolItem(tr("Setup.OSD$Menu key closes"),        &data.MenuKeyCloses));
02959   Add(new cMenuEditBoolItem(tr("Setup.OSD$Recording directories"),  &data.RecordingDirs));
02960   Add(new cMenuEditBoolItem(tr("Setup.OSD$Folders in timer menu"),  &data.FoldersInTimerMenu));
02961   Add(new cMenuEditBoolItem(tr("Setup.OSD$Number keys for characters"), &data.NumberKeysForChars));
02962   SetCurrent(Get(current));
02963   Display();
02964 }
02965 
02966 eOSState cMenuSetupOSD::ProcessKey(eKeys Key)
02967 {
02968   bool ModifiedAppearance = false;
02969 
02970   if (Key == kOk) {
02971      I18nSetLocale(data.OSDLanguage);
02972      if (skinIndex != originalSkinIndex) {
02973         cSkin *Skin = Skins.Get(skinIndex);
02974         if (Skin) {
02975            Utf8Strn0Cpy(data.OSDSkin, Skin->Name(), sizeof(data.OSDSkin));
02976            Skins.SetCurrent(Skin->Name());
02977            ModifiedAppearance = true;
02978            }
02979         }
02980      if (themes.NumThemes() && Skins.Current()->Theme()) {
02981         Skins.Current()->Theme()->Load(themes.FileName(themeIndex));
02982         Utf8Strn0Cpy(data.OSDTheme, themes.Name(themeIndex), sizeof(data.OSDTheme));
02983         ModifiedAppearance |= themeIndex != originalThemeIndex;
02984         }
02985      if (!(DoubleEqual(data.OSDLeftP, Setup.OSDLeftP) && DoubleEqual(data.OSDTopP, Setup.OSDTopP) && DoubleEqual(data.OSDWidthP, Setup.OSDWidthP) && DoubleEqual(data.OSDHeightP, Setup.OSDHeightP)))
02986         ModifiedAppearance = true;
02987      if (data.UseSmallFont != Setup.UseSmallFont || data.AntiAlias != Setup.AntiAlias)
02988         ModifiedAppearance = true;
02989      Utf8Strn0Cpy(data.FontOsd, fontOsdNames[fontOsdIndex], sizeof(data.FontOsd));
02990      Utf8Strn0Cpy(data.FontSml, fontSmlNames[fontSmlIndex], sizeof(data.FontSml));
02991      Utf8Strn0Cpy(data.FontFix, fontFixNames[fontFixIndex], sizeof(data.FontFix));
02992      if (strcmp(data.FontOsd, Setup.FontOsd) || !DoubleEqual(data.FontOsdSizeP, Setup.FontOsdSizeP))
02993         ModifiedAppearance = true;
02994      if (strcmp(data.FontSml, Setup.FontSml) || !DoubleEqual(data.FontSmlSizeP, Setup.FontSmlSizeP))
02995         ModifiedAppearance = true;
02996      if (strcmp(data.FontFix, Setup.FontFix) || !DoubleEqual(data.FontFixSizeP, Setup.FontFixSizeP))
02997         ModifiedAppearance = true;
02998      }
02999 
03000   int oldSkinIndex = skinIndex;
03001   int oldOsdLanguageIndex = osdLanguageIndex;
03002   eOSState state = cMenuSetupBase::ProcessKey(Key);
03003 
03004   if (ModifiedAppearance) {
03005      cOsdProvider::UpdateOsdSize(true);
03006      SetDisplayMenu();
03007      }
03008 
03009   if (osdLanguageIndex != oldOsdLanguageIndex || skinIndex != oldSkinIndex) {
03010      strn0cpy(data.OSDLanguage, I18nLocale(osdLanguageIndex), sizeof(data.OSDLanguage));
03011      int OriginalOSDLanguage = I18nCurrentLanguage();
03012      I18nSetLanguage(osdLanguageIndex);
03013 
03014      cSkin *Skin = Skins.Get(skinIndex);
03015      if (Skin) {
03016         char *d = themes.NumThemes() ? strdup(themes.Descriptions()[themeIndex]) : NULL;
03017         themes.Load(Skin->Name());
03018         if (skinIndex != oldSkinIndex)
03019            themeIndex = d ? themes.GetThemeIndex(d) : 0;
03020         free(d);
03021         }
03022 
03023      Set();
03024      I18nSetLanguage(OriginalOSDLanguage);
03025      }
03026   return state;
03027 }
03028 
03029 // --- cMenuSetupEPG ---------------------------------------------------------
03030 
03031 class cMenuSetupEPG : public cMenuSetupBase {
03032 private:
03033   int originalNumLanguages;
03034   int numLanguages;
03035   void Setup(void);
03036 public:
03037   cMenuSetupEPG(void);
03038   virtual eOSState ProcessKey(eKeys Key);
03039   };
03040 
03041 cMenuSetupEPG::cMenuSetupEPG(void)
03042 {
03043   for (numLanguages = 0; numLanguages < I18nLanguages()->Size() && data.EPGLanguages[numLanguages] >= 0; numLanguages++)
03044       ;
03045   originalNumLanguages = numLanguages;
03046   SetSection(tr("EPG"));
03047   SetHelp(tr("Button$Scan"));
03048   Setup();
03049 }
03050 
03051 void cMenuSetupEPG::Setup(void)
03052 {
03053   int current = Current();
03054 
03055   Clear();
03056 
03057   Add(new cMenuEditIntItem( tr("Setup.EPG$EPG scan timeout (h)"),      &data.EPGScanTimeout));
03058   Add(new cMenuEditIntItem( tr("Setup.EPG$EPG bugfix level"),          &data.EPGBugfixLevel, 0, MAXEPGBUGFIXLEVEL));
03059   Add(new cMenuEditIntItem( tr("Setup.EPG$EPG linger time (min)"),     &data.EPGLinger, 0));
03060   Add(new cMenuEditBoolItem(tr("Setup.EPG$Set system time"),           &data.SetSystemTime));
03061   if (data.SetSystemTime)
03062      Add(new cMenuEditTranItem(tr("Setup.EPG$Use time from transponder"), &data.TimeTransponder, &data.TimeSource));
03063   // TRANSLATORS: note the plural!
03064   Add(new cMenuEditIntItem( tr("Setup.EPG$Preferred languages"),       &numLanguages, 0, I18nLanguages()->Size()));
03065   for (int i = 0; i < numLanguages; i++)
03066       // TRANSLATORS: note the singular!
03067       Add(new cMenuEditStraItem(tr("Setup.EPG$Preferred language"),    &data.EPGLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0)));
03068 
03069   SetCurrent(Get(current));
03070   Display();
03071 }
03072 
03073 eOSState cMenuSetupEPG::ProcessKey(eKeys Key)
03074 {
03075   if (Key == kOk) {
03076      bool Modified = numLanguages != originalNumLanguages;
03077      if (!Modified) {
03078         for (int i = 0; i < numLanguages; i++) {
03079             if (data.EPGLanguages[i] != ::Setup.EPGLanguages[i]) {
03080                Modified = true;
03081                break;
03082                }
03083             }
03084         }
03085      if (Modified)
03086         cSchedules::ResetVersions();
03087      }
03088 
03089   int oldnumLanguages = numLanguages;
03090   int oldSetSystemTime = data.SetSystemTime;
03091 
03092   eOSState state = cMenuSetupBase::ProcessKey(Key);
03093   if (Key != kNone) {
03094      if (numLanguages != oldnumLanguages || data.SetSystemTime != oldSetSystemTime) {
03095         for (int i = oldnumLanguages; i < numLanguages; i++) {
03096             data.EPGLanguages[i] = 0;
03097             for (int l = 0; l < I18nLanguages()->Size(); l++) {
03098                 int k;
03099                 for (k = 0; k < oldnumLanguages; k++) {
03100                     if (data.EPGLanguages[k] == l)
03101                        break;
03102                     }
03103                 if (k >= oldnumLanguages) {
03104                    data.EPGLanguages[i] = l;
03105                    break;
03106                    }
03107                 }
03108             }
03109         data.EPGLanguages[numLanguages] = -1;
03110         Setup();
03111         }
03112      if (Key == kRed) {
03113         EITScanner.ForceScan();
03114         return osEnd;
03115         }
03116      }
03117   return state;
03118 }
03119 
03120 // --- cMenuSetupDVB ---------------------------------------------------------
03121 
03122 class cMenuSetupDVB : public cMenuSetupBase {
03123 private:
03124   int originalNumAudioLanguages;
03125   int numAudioLanguages;
03126   int originalNumSubtitleLanguages;
03127   int numSubtitleLanguages;
03128   void Setup(void);
03129   const char *videoDisplayFormatTexts[3];
03130   const char *updateChannelsTexts[6];
03131 public:
03132   cMenuSetupDVB(void);
03133   virtual eOSState ProcessKey(eKeys Key);
03134   };
03135 
03136 cMenuSetupDVB::cMenuSetupDVB(void)
03137 {
03138   for (numAudioLanguages = 0; numAudioLanguages < I18nLanguages()->Size() && data.AudioLanguages[numAudioLanguages] >= 0; numAudioLanguages++)
03139       ;
03140   for (numSubtitleLanguages = 0; numSubtitleLanguages < I18nLanguages()->Size() && data.SubtitleLanguages[numSubtitleLanguages] >= 0; numSubtitleLanguages++)
03141       ;
03142   originalNumAudioLanguages = numAudioLanguages;
03143   originalNumSubtitleLanguages = numSubtitleLanguages;
03144   videoDisplayFormatTexts[0] = tr("pan&scan");
03145   videoDisplayFormatTexts[1] = tr("letterbox");
03146   videoDisplayFormatTexts[2] = tr("center cut out");
03147   updateChannelsTexts[0] = tr("no");
03148   updateChannelsTexts[1] = tr("names only");
03149   updateChannelsTexts[2] = tr("PIDs only");
03150   updateChannelsTexts[3] = tr("names and PIDs");
03151   updateChannelsTexts[4] = tr("add new channels");
03152   updateChannelsTexts[5] = tr("add new transponders");
03153 
03154   SetSection(tr("DVB"));
03155   SetHelp(NULL, tr("Button$Audio"), tr("Button$Subtitles"), NULL); 
03156   Setup();
03157 }
03158 
03159 void cMenuSetupDVB::Setup(void)
03160 {
03161   int current = Current();
03162 
03163   Clear();
03164 
03165   Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDevice::NumDevices()));
03166   Add(new cMenuEditBoolItem(tr("Setup.DVB$Video format"),          &data.VideoFormat, "4:3", "16:9"));
03167   if (data.VideoFormat == 0)
03168      Add(new cMenuEditStraItem(tr("Setup.DVB$Video display format"), &data.VideoDisplayFormat, 3, videoDisplayFormatTexts));
03169   Add(new cMenuEditBoolItem(tr("Setup.DVB$Use Dolby Digital"),     &data.UseDolbyDigital));
03170   Add(new cMenuEditStraItem(tr("Setup.DVB$Update channels"),       &data.UpdateChannels, 6, updateChannelsTexts));
03171   Add(new cMenuEditIntItem( tr("Setup.DVB$Audio languages"),       &numAudioLanguages, 0, I18nLanguages()->Size()));
03172   for (int i = 0; i < numAudioLanguages; i++)
03173       Add(new cMenuEditStraItem(tr("Setup.DVB$Audio language"),    &data.AudioLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0)));
03174   Add(new cMenuEditBoolItem(tr("Setup.DVB$Display subtitles"),     &data.DisplaySubtitles));
03175   if (data.DisplaySubtitles) {
03176      Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle languages"),    &numSubtitleLanguages, 0, I18nLanguages()->Size()));
03177      for (int i = 0; i < numSubtitleLanguages; i++)
03178          Add(new cMenuEditStraItem(tr("Setup.DVB$Subtitle language"), &data.SubtitleLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0)));
03179      Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle offset"),                  &data.SubtitleOffset,      -100, 100));
03180      Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle foreground transparency"), &data.SubtitleFgTransparency, 0, 9));
03181      Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle background transparency"), &data.SubtitleBgTransparency, 0, 10));
03182      }
03183   Add(new cMenuEditBoolItem(tr("Setup.DVB$Enable teletext support"), &data.SupportTeletext));
03184 
03185   SetCurrent(Get(current));
03186   Display();
03187 }
03188 
03189 eOSState cMenuSetupDVB::ProcessKey(eKeys Key)
03190 {
03191   int oldPrimaryDVB = ::Setup.PrimaryDVB;
03192   int oldVideoDisplayFormat = ::Setup.VideoDisplayFormat;
03193   bool oldVideoFormat = ::Setup.VideoFormat;
03194   bool newVideoFormat = data.VideoFormat;
03195   bool oldDisplaySubtitles = ::Setup.DisplaySubtitles;
03196   bool newDisplaySubtitles = data.DisplaySubtitles;
03197   int oldnumAudioLanguages = numAudioLanguages;
03198   int oldnumSubtitleLanguages = numSubtitleLanguages;
03199   eOSState state = cMenuSetupBase::ProcessKey(Key);
03200 
03201   if (Key != kNone) {
03202      switch (Key) {
03203        case kGreen:  cRemote::Put(kAudio, true);
03204                      state = osEnd;
03205                      break;
03206        case kYellow: cRemote::Put(kSubtitles, true);
03207                      state = osEnd;
03208                      break;
03209        default: { 
03210             bool DoSetup = data.VideoFormat != newVideoFormat;
03211             DoSetup |= data.DisplaySubtitles != newDisplaySubtitles;
03212             if (numAudioLanguages != oldnumAudioLanguages) {
03213                for (int i = oldnumAudioLanguages; i < numAudioLanguages; i++) {
03214                    data.AudioLanguages[i] = 0;
03215                    for (int l = 0; l < I18nLanguages()->Size(); l++) {
03216                        int k;
03217                        for (k = 0; k < oldnumAudioLanguages; k++) {
03218                            if (data.AudioLanguages[k] == l)
03219                               break;
03220                            }
03221                        if (k >= oldnumAudioLanguages) {
03222                           data.AudioLanguages[i] = l;
03223                           break;
03224                           }
03225                        }
03226                    }
03227                data.AudioLanguages[numAudioLanguages] = -1;
03228                DoSetup = true;
03229                }
03230             if (numSubtitleLanguages != oldnumSubtitleLanguages) {
03231                for (int i = oldnumSubtitleLanguages; i < numSubtitleLanguages; i++) {
03232                    data.SubtitleLanguages[i] = 0;
03233                    for (int l = 0; l < I18nLanguages()->Size(); l++) {
03234                        int k;
03235                        for (k = 0; k < oldnumSubtitleLanguages; k++) {
03236                            if (data.SubtitleLanguages[k] == l)
03237                               break;
03238                            }
03239                        if (k >= oldnumSubtitleLanguages) {
03240                           data.SubtitleLanguages[i] = l;
03241                           break;
03242                           }
03243                        }
03244                    }
03245                data.SubtitleLanguages[numSubtitleLanguages] = -1;
03246                DoSetup = true;
03247                }
03248             if (DoSetup)
03249                Setup();
03250             }
03251        }
03252      }
03253   if (state == osBack && Key == kOk) {
03254      if (::Setup.PrimaryDVB != oldPrimaryDVB)
03255         state = osSwitchDvb;
03256      if (::Setup.VideoDisplayFormat != oldVideoDisplayFormat)
03257         cDevice::PrimaryDevice()->SetVideoDisplayFormat(eVideoDisplayFormat(::Setup.VideoDisplayFormat));
03258      if (::Setup.VideoFormat != oldVideoFormat)
03259         cDevice::PrimaryDevice()->SetVideoFormat(::Setup.VideoFormat);
03260      if (::Setup.DisplaySubtitles != oldDisplaySubtitles)
03261         cDevice::PrimaryDevice()->EnsureSubtitleTrack();
03262      cDvbSubtitleConverter::SetupChanged();
03263      }
03264   return state;
03265 }
03266 
03267 // --- cMenuSetupLNB ---------------------------------------------------------
03268 
03269 class cMenuSetupLNB : public cMenuSetupBase {
03270 private:
03271   cSatCableNumbers satCableNumbers;
03272   void Setup(void);
03273 public:
03274   cMenuSetupLNB(void);
03275   virtual eOSState ProcessKey(eKeys Key);
03276   };
03277 
03278 cMenuSetupLNB::cMenuSetupLNB(void)
03279 :satCableNumbers(MAXDEVICES)
03280 {
03281   satCableNumbers.FromString(data.DeviceBondings);
03282   SetSection(tr("LNB"));
03283   Setup();
03284 }
03285 
03286 void cMenuSetupLNB::Setup(void)
03287 {
03288   int current = Current();
03289 
03290   Clear();
03291 
03292   Add(new cMenuEditBoolItem(tr("Setup.LNB$Use DiSEqC"),               &data.DiSEqC));
03293   if (!data.DiSEqC) {
03294      Add(new cMenuEditIntItem( tr("Setup.LNB$SLOF (MHz)"),               &data.LnbSLOF));
03295      Add(new cMenuEditIntItem( tr("Setup.LNB$Low LNB frequency (MHz)"),  &data.LnbFrequLo));
03296      Add(new cMenuEditIntItem( tr("Setup.LNB$High LNB frequency (MHz)"), &data.LnbFrequHi));
03297      }
03298 
03299   int NumSatDevices = 0;
03300   for (int i = 0; i < cDevice::NumDevices(); i++) {
03301       if (cDevice::GetDevice(i)->ProvidesSource(cSource::stSat))
03302          NumSatDevices++;
03303       }
03304   if (NumSatDevices > 1) {
03305      for (int i = 0; i < cDevice::NumDevices(); i++) {
03306          if (cDevice::GetDevice(i)->ProvidesSource(cSource::stSat))
03307             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")));
03308          }
03309      }
03310 
03311   SetCurrent(Get(current));
03312   Display();
03313 }
03314 
03315 eOSState cMenuSetupLNB::ProcessKey(eKeys Key)
03316 {
03317   int oldDiSEqC = data.DiSEqC;
03318   bool DeviceBondingsChanged = false;
03319   if (Key == kOk) {
03320      cString NewDeviceBondings = satCableNumbers.ToString();
03321      DeviceBondingsChanged = strcmp(data.DeviceBondings, NewDeviceBondings) != 0;
03322      data.DeviceBondings = NewDeviceBondings;
03323      }
03324   eOSState state = cMenuSetupBase::ProcessKey(Key);
03325 
03326   if (Key != kNone && data.DiSEqC != oldDiSEqC)
03327      Setup();
03328   else if (DeviceBondingsChanged)
03329      cDvbDevice::BondDevices(data.DeviceBondings);
03330   return state;
03331 }
03332 
03333 // --- cMenuSetupCAM ---------------------------------------------------------
03334 
03335 class cMenuSetupCAMItem : public cOsdItem {
03336 private:
03337   cCamSlot *camSlot;
03338 public:
03339   cMenuSetupCAMItem(cCamSlot *CamSlot);
03340   cCamSlot *CamSlot(void) { return camSlot; }
03341   bool Changed(void);
03342   };
03343 
03344 cMenuSetupCAMItem::cMenuSetupCAMItem(cCamSlot *CamSlot)
03345 {
03346   camSlot = CamSlot;
03347   SetText("");
03348   Changed();
03349 }
03350 
03351 bool cMenuSetupCAMItem::Changed(void)
03352 {
03353   char buffer[32];
03354   const char *CamName = camSlot->GetCamName();
03355   if (!CamName) {
03356      switch (camSlot->ModuleStatus()) {
03357        case msReset:   CamName = tr("CAM reset"); break;
03358        case msPresent: CamName = tr("CAM present"); break;
03359        case msReady:   CamName = tr("CAM ready"); break;
03360        default:        CamName = "-"; break;
03361        }
03362      }
03363   snprintf(buffer, sizeof(buffer), " %d %s", camSlot->SlotNumber(), CamName);
03364   if (strcmp(buffer, Text()) != 0) {
03365      SetText(buffer);
03366      return true;
03367      }
03368   return false;
03369 }
03370 
03371 class cMenuSetupCAM : public cMenuSetupBase {
03372 private:
03373   eOSState Menu(void);
03374   eOSState Reset(void);
03375 public:
03376   cMenuSetupCAM(void);
03377   virtual eOSState ProcessKey(eKeys Key);
03378   };
03379 
03380 cMenuSetupCAM::cMenuSetupCAM(void)
03381 {
03382   SetSection(tr("CAM"));
03383   SetCols(15);
03384   SetHasHotkeys();
03385   for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot))
03386       Add(new cMenuSetupCAMItem(CamSlot));
03387   SetHelp(tr("Button$Menu"), tr("Button$Reset"));
03388 }
03389 
03390 eOSState cMenuSetupCAM::Menu(void)
03391 {
03392   cMenuSetupCAMItem *item = (cMenuSetupCAMItem *)Get(Current());
03393   if (item) {
03394      if (item->CamSlot()->EnterMenu()) {
03395         Skins.Message(mtStatus, tr("Opening CAM menu..."));
03396         time_t t0 = time(NULL);
03397         time_t t1 = t0;
03398         while (time(NULL) - t0 <= MAXWAITFORCAMMENU) {
03399               if (item->CamSlot()->HasUserIO())
03400                  break;
03401               if (time(NULL) - t1 >= CAMMENURETYTIMEOUT) {
03402                  dsyslog("CAM %d: retrying to enter CAM menu...", item->CamSlot()->SlotNumber());
03403                  item->CamSlot()->EnterMenu();
03404                  t1 = time(NULL);
03405                  }
03406               cCondWait::SleepMs(100);
03407               }
03408         Skins.Message(mtStatus, NULL);
03409         if (item->CamSlot()->HasUserIO())
03410            return AddSubMenu(new cMenuCam(item->CamSlot()));
03411         }
03412      Skins.Message(mtError, tr("Can't open CAM menu!"));
03413      }
03414   return osContinue;
03415 }
03416 
03417 eOSState cMenuSetupCAM::Reset(void)
03418 {
03419   cMenuSetupCAMItem *item = (cMenuSetupCAMItem *)Get(Current());
03420   if (item) {
03421      if (!item->CamSlot()->Device() || Interface->Confirm(tr("CAM is in use - really reset?"))) {
03422         if (!item->CamSlot()->Reset())
03423            Skins.Message(mtError, tr("Can't reset CAM!"));
03424         }
03425      }
03426   return osContinue;
03427 }
03428 
03429 eOSState cMenuSetupCAM::ProcessKey(eKeys Key)
03430 {
03431   eOSState state = HasSubMenu() ? cMenuSetupBase::ProcessKey(Key) : cOsdMenu::ProcessKey(Key);
03432 
03433   if (!HasSubMenu()) {
03434      switch (Key) {
03435        case kOk:
03436        case kRed:    return Menu();
03437        case kGreen:  state = Reset(); break;
03438        default: break;
03439        }
03440      for (cMenuSetupCAMItem *ci = (cMenuSetupCAMItem *)First(); ci; ci = (cMenuSetupCAMItem *)ci->Next()) {
03441          if (ci->Changed())
03442             DisplayItem(ci);
03443          }
03444      }
03445   return state;
03446 }
03447 
03448 // --- cMenuSetupRecord ------------------------------------------------------
03449 
03450 class cMenuSetupRecord : public cMenuSetupBase {
03451 private:
03452   const char *pauseKeyHandlingTexts[3];
03453   const char *delTimeshiftRecTexts[3];
03454 public:
03455   cMenuSetupRecord(void);
03456   };
03457 
03458 cMenuSetupRecord::cMenuSetupRecord(void)
03459 {
03460   pauseKeyHandlingTexts[0] = tr("do not pause live video");
03461   pauseKeyHandlingTexts[1] = tr("confirm pause live video");
03462   pauseKeyHandlingTexts[2] = tr("pause live video");
03463   delTimeshiftRecTexts[0] = tr("no");
03464   delTimeshiftRecTexts[1] = tr("confirm");
03465   delTimeshiftRecTexts[2] = tr("yes");
03466   SetSection(tr("Recording"));
03467   Add(new cMenuEditIntItem( tr("Setup.Recording$Margin at start (min)"),     &data.MarginStart));
03468   Add(new cMenuEditIntItem( tr("Setup.Recording$Margin at stop (min)"),      &data.MarginStop));
03469   Add(new cMenuEditIntItem( tr("Setup.Recording$Default priority"),          &data.DefaultPriority, 0, MAXPRIORITY));
03470   Add(new cMenuEditIntItem( tr("Setup.Recording$Default lifetime (d)"),      &data.DefaultLifetime, 0, MAXLIFETIME));
03471   Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"),        &data.PauseKeyHandling, 3, pauseKeyHandlingTexts));
03472   Add(new cMenuEditIntItem( tr("Setup.Recording$Pause priority"),            &data.PausePriority, 0, MAXPRIORITY));
03473   Add(new cMenuEditIntItem( tr("Setup.Recording$Pause lifetime (d)"),        &data.PauseLifetime, 0, MAXLIFETIME));
03474   Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"),          &data.UseSubtitle));
03475   Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"),                   &data.UseVps));
03476   Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"),            &data.VpsMargin, 0));
03477   Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"),    &data.MarkInstantRecord));
03478   Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"),     data.NameInstantRecord, sizeof(data.NameInstantRecord)));
03479   Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"),   &data.InstantRecordTime, 1, MAXINSTANTRECTIME));
03480   Add(new cMenuEditIntItem( tr("Setup.Recording$Max. video file size (MB)"), &data.MaxVideoFileSize, MINVIDEOFILESIZE, MAXVIDEOFILESIZETS));
03481   Add(new cMenuEditIntItem( tr("Setup.Recording$Max. recording size (GB)"),  &data.MaxRecordingSize, MINRECORDINGSIZE, MAXRECORDINGSIZE));
03482   Add(new cMenuEditBoolItem(tr("Setup.Recording$Split edited files"),        &data.SplitEditedFiles));
03483   Add(new cMenuEditStraItem(tr("Setup.Recording$Delete timeshift recording"),&data.DelTimeshiftRec, 3, delTimeshiftRecTexts));
03484   Add(new cMenuEditBoolItem(tr("Setup.Recording$Hard Link Cutter"),          &data.HardLinkCutter));
03485 }
03486 
03487 // --- cMenuSetupReplay ------------------------------------------------------
03488 
03489 class cMenuSetupReplay : public cMenuSetupBase {
03490 protected:
03491   virtual void Store(void);
03492 public:
03493   cMenuSetupReplay(void);
03494   };
03495 
03496 cMenuSetupReplay::cMenuSetupReplay(void)
03497 {
03498   SetSection(tr("Replay"));
03499   Add(new cMenuEditBoolItem(tr("Setup.Replay$Multi speed mode"), &data.MultiSpeedMode));
03500   Add(new cMenuEditBoolItem(tr("Setup.Replay$Show replay mode"), &data.ShowReplayMode));
03501   Add(new cMenuEditBoolItem(tr("Setup.Replay$Show remaining time"), &data.ShowRemainingTime));
03502   Add(new cMenuEditIntItem(tr("Setup.Replay$Resume ID"), &data.ResumeID, 0, 99));
03503   Add(new cMenuEditBoolItem(tr("Setup.Replay$Jump&Play"), &data.JumpPlay));
03504   Add(new cMenuEditBoolItem(tr("Setup.Replay$Play&Jump"), &data.PlayJump));
03505   Add(new cMenuEditBoolItem(tr("Setup.Replay$Pause at last mark"), &data.PauseLastMark));
03506 }
03507 
03508 void cMenuSetupReplay::Store(void)
03509 {
03510   if (Setup.ResumeID != data.ResumeID)
03511      Recordings.ResetResume();
03512   cMenuSetupBase::Store();
03513 }
03514 
03515 // --- cMenuSetupMisc --------------------------------------------------------
03516 
03517 class cMenuSetupMisc : public cMenuSetupBase {
03518 public:
03519   cMenuSetupMisc(void);
03520   };
03521 
03522 cMenuSetupMisc::cMenuSetupMisc(void)
03523 {
03524   SetSection(tr("Miscellaneous"));
03525   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. event timeout (min)"),   &data.MinEventTimeout));
03526   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. user inactivity (min)"), &data.MinUserInactivity));
03527   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$SVDRP timeout (s)"),          &data.SVDRPTimeout));
03528   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Zap timeout (s)"),            &data.ZapTimeout));
03529   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Channel entry timeout (ms)"), &data.ChannelEntryTimeout, 0));
03530   Add(new cMenuEditChanItem(tr("Setup.Miscellaneous$Initial channel"),            &data.InitialChannel, tr("Setup.Miscellaneous$as before")));
03531   Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Initial volume"),             &data.InitialVolume, -1, 255, tr("Setup.Miscellaneous$as before")));
03532   Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Channels wrap"),              &data.ChannelsWrap));
03533   Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Emergency exit"),             &data.EmergencyExit));
03534 }
03535 
03536 // --- cMenuSetupPluginItem --------------------------------------------------
03537 
03538 class cMenuSetupPluginItem : public cOsdItem {
03539 private:
03540   int pluginIndex;
03541 public:
03542   cMenuSetupPluginItem(const char *Name, int Index);
03543   int PluginIndex(void) { return pluginIndex; }
03544   };
03545 
03546 cMenuSetupPluginItem::cMenuSetupPluginItem(const char *Name, int Index)
03547 :cOsdItem(Name)
03548 {
03549   pluginIndex = Index;
03550 }
03551 
03552 // --- cMenuSetupPlugins -----------------------------------------------------
03553 
03554 class cMenuSetupPlugins : public cMenuSetupBase {
03555 public:
03556   cMenuSetupPlugins(void);
03557   virtual eOSState ProcessKey(eKeys Key);
03558   };
03559 
03560 cMenuSetupPlugins::cMenuSetupPlugins(void)
03561 {
03562   SetSection(tr("Plugins"));
03563   SetHasHotkeys();
03564   for (int i = 0; ; i++) {
03565       cPlugin *p = cPluginManager::GetPlugin(i);
03566       if (p)
03567          Add(new cMenuSetupPluginItem(hk(cString::sprintf("%s (%s) - %s", p->Name(), p->Version(), p->Description())), i));
03568       else
03569          break;
03570       }
03571 }
03572 
03573 eOSState cMenuSetupPlugins::ProcessKey(eKeys Key)
03574 {
03575   eOSState state = HasSubMenu() ? cMenuSetupBase::ProcessKey(Key) : cOsdMenu::ProcessKey(Key);
03576 
03577   if (Key == kOk) {
03578      if (state == osUnknown) {
03579         cMenuSetupPluginItem *item = (cMenuSetupPluginItem *)Get(Current());
03580         if (item) {
03581            cPlugin *p = cPluginManager::GetPlugin(item->PluginIndex());
03582            if (p) {
03583               cMenuSetupPage *menu = p->SetupMenu();
03584               if (menu) {
03585                  menu->SetPlugin(p);
03586                  return AddSubMenu(menu);
03587                  }
03588               Skins.Message(mtInfo, tr("This plugin has no setup parameters!"));
03589               }
03590            }
03591         }
03592      else if (state == osContinue)
03593         Store();
03594      }
03595   return state;
03596 }
03597 
03598 // --- cMenuSetup ------------------------------------------------------------
03599 
03600 class cMenuSetup : public cOsdMenu {
03601 private:
03602   virtual void Set(void);
03603   eOSState Restart(void);
03604 public:
03605   cMenuSetup(void);
03606   virtual eOSState ProcessKey(eKeys Key);
03607   };
03608 
03609 cMenuSetup::cMenuSetup(void)
03610 :cOsdMenu("")
03611 {
03612   Set();
03613 }
03614 
03615 void cMenuSetup::Set(void)
03616 {
03617   Clear();
03618   char buffer[64];
03619   snprintf(buffer, sizeof(buffer), "%s - VDR %s", tr("Setup"), VDRVERSION);
03620   SetTitle(buffer);
03621   SetHasHotkeys();
03622   Add(new cOsdItem(hk(tr("OSD")),           osUser1));
03623   Add(new cOsdItem(hk(tr("EPG")),           osUser2));
03624   Add(new cOsdItem(hk(tr("DVB")),           osUser3));
03625   Add(new cOsdItem(hk(tr("LNB")),           osUser4));
03626   Add(new cOsdItem(hk(tr("CAM")),           osUser5));
03627   Add(new cOsdItem(hk(tr("Recording")),     osUser6));
03628   Add(new cOsdItem(hk(tr("Replay")),        osUser7));
03629   Add(new cOsdItem(hk(tr("Miscellaneous")), osUser8));
03630   if (cPluginManager::HasPlugins())
03631   Add(new cOsdItem(hk(tr("Plugins")),       osUser9));
03632   Add(new cOsdItem(hk(tr("Restart")),       osUser10));
03633 }
03634 
03635 eOSState cMenuSetup::Restart(void)
03636 {
03637   if (Interface->Confirm(tr("Really restart?")) && ShutdownHandler.ConfirmRestart(true)) {
03638      ShutdownHandler.Exit(1);
03639      return osEnd;
03640      }
03641   return osContinue;
03642 }
03643 
03644 eOSState cMenuSetup::ProcessKey(eKeys Key)
03645 {
03646   int osdLanguage = I18nCurrentLanguage();
03647   eOSState state = cOsdMenu::ProcessKey(Key);
03648 
03649   switch (state) {
03650     case osUser1: return AddSubMenu(new cMenuSetupOSD);
03651     case osUser2: return AddSubMenu(new cMenuSetupEPG);
03652     case osUser3: return AddSubMenu(new cMenuSetupDVB);
03653     case osUser4: return AddSubMenu(new cMenuSetupLNB);
03654     case osUser5: return AddSubMenu(new cMenuSetupCAM);
03655     case osUser6: return AddSubMenu(new cMenuSetupRecord);
03656     case osUser7: return AddSubMenu(new cMenuSetupReplay);
03657     case osUser8: return AddSubMenu(new cMenuSetupMisc);
03658     case osUser9: return AddSubMenu(new cMenuSetupPlugins);
03659     case osUser10: return Restart();
03660     default: ;
03661     }
03662   if (I18nCurrentLanguage() != osdLanguage) {
03663      Set();
03664      if (!HasSubMenu())
03665         Display();
03666      }
03667   return state;
03668 }
03669 
03670 // --- cMenuPluginItem -------------------------------------------------------
03671 
03672 class cMenuPluginItem : public cOsdItem {
03673 private:
03674   int pluginIndex;
03675 public:
03676   cMenuPluginItem(const char *Name, int Index);
03677   int PluginIndex(void) { return pluginIndex; }
03678   };
03679 
03680 cMenuPluginItem::cMenuPluginItem(const char *Name, int Index)
03681 :cOsdItem(Name, osPlugin)
03682 {
03683   pluginIndex = Index;
03684 }
03685 
03686 // --- cMenuMain -------------------------------------------------------------
03687 
03688 // TRANSLATORS: note the leading and trailing blanks!
03689 #define STOP_RECORDING trNOOP(" Stop recording ")
03690 
03691 cOsdObject *cMenuMain::pluginOsdObject = NULL;
03692 
03693 cMenuMain::cMenuMain(eOSState State)
03694 :cOsdMenu("")
03695 {
03696   replaying = false;
03697   stopReplayItem = NULL;
03698   cancelEditingItem = NULL;
03699   cancelFileTransferItem = NULL;
03700   stopRecordingItem = NULL;
03701   recordControlsState = 0;
03702   Set();
03703 
03704   // Initial submenus:
03705 
03706   cOsdObject *menu = NULL;
03707   switch (State) {
03708     case osSchedule:
03709         if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu))
03710             menu = new cMenuSchedule;
03711         break;
03712     case osChannels:
03713         if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu))
03714             menu = new cMenuChannels;
03715         break;
03716     case osTimers:
03717         if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu))
03718             menu = new cMenuTimers;
03719         break;
03720     case osRecordings:
03721         if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu))
03722             menu = new cMenuRecordings(NULL, 0, true);
03723         break;
03724     case osSetup:      menu = new cMenuSetup; break;
03725     case osCommands:   menu = new cMenuCommands(tr("Commands"), &Commands); break;
03726     default: break;
03727     }
03728   if (menu)
03729      if (menu->IsMenu())
03730         AddSubMenu((cOsdMenu *) menu);
03731 }
03732 
03733 cOsdObject *cMenuMain::PluginOsdObject(void)
03734 {
03735   cOsdObject *o = pluginOsdObject;
03736   pluginOsdObject = NULL;
03737   return o;
03738 }
03739 
03740 void cMenuMain::Set(void)
03741 {
03742   Clear();
03743   SetTitle("VDR");
03744   SetHasHotkeys();
03745 
03746   // Basic menu items:
03747 
03748   Add(new cOsdItem(hk(tr("Schedule")),   osSchedule));
03749   Add(new cOsdItem(hk(tr("Channels")),   osChannels));
03750   Add(new cOsdItem(hk(tr("Timers")),     osTimers));
03751   Add(new cOsdItem(hk(tr("Recordings")), osRecordings));
03752 
03753   // Plugins:
03754 
03755   for (int i = 0; ; i++) {
03756       cPlugin *p = cPluginManager::GetPlugin(i);
03757       if (p) {
03758          const char *item = p->MainMenuEntry();
03759          if (item)
03760             Add(new cMenuPluginItem(hk(item), i));
03761          }
03762       else
03763          break;
03764       }
03765 
03766   // More basic menu items:
03767 
03768   Add(new cOsdItem(hk(tr("Setup")),      osSetup));
03769   if (Commands.Count())
03770      Add(new cOsdItem(hk(tr("Commands")),  osCommands));
03771 
03772   Update(true);
03773 
03774   Display();
03775 }
03776 
03777 bool cMenuMain::Update(bool Force)
03778 {
03779   bool result = false;
03780 
03781   // Title with disk usage:
03782   if (FreeDiskSpace.HasChanged(Force)) {
03783      //XXX -> skin function!!!
03784      SetTitle(cString::sprintf("%s  -  %s", tr("VDR"), FreeDiskSpace.FreeDiskSpaceString()));
03785      result = true;
03786      }
03787 
03788   bool NewReplaying = cControl::Control() != NULL;
03789   if (Force || NewReplaying != replaying) {
03790      replaying = NewReplaying;
03791      // Replay control:
03792      if (replaying && !stopReplayItem)
03793         // TRANSLATORS: note the leading blank!
03794         Add(stopReplayItem = new cOsdItem(tr(" Stop replaying"), osStopReplay));
03795      else if (stopReplayItem && !replaying) {
03796         Del(stopReplayItem->Index());
03797         stopReplayItem = NULL;
03798         }
03799      // Color buttons:
03800      SetHelp(!replaying ? tr("Button$Record") : NULL, tr("Button$Audio"), replaying ? NULL : tr("Button$Pause"), replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Button$Resume") : NULL);
03801      result = true;
03802      }
03803 
03804   // Editing control:
03805   bool CutterActive = cCutter::Active();
03806   if (CutterActive && !cancelEditingItem) {
03807      // TRANSLATORS: note the leading blank!
03808      Add(cancelEditingItem = new cOsdItem(tr(" Cancel editing"), osCancelEdit));
03809      result = true;
03810      }
03811   else if (cancelEditingItem && !CutterActive) {
03812      Del(cancelEditingItem->Index());
03813      cancelEditingItem = NULL;
03814      result = true;
03815      }
03816 
03817   // File transfer control:
03818   bool FileTransferActive = cFileTransfer::Active();
03819   if (FileTransferActive && !cancelFileTransferItem) {
03820      // TRANSLATORS: note the leading blank!
03821      Add(cancelFileTransferItem = new cOsdItem(tr(" Cancel file transfer"), osCancelTransfer));
03822      result = true;
03823      }
03824   else if (cancelFileTransferItem && !FileTransferActive) {
03825      Del(cancelFileTransferItem->Index());
03826      cancelFileTransferItem = NULL;
03827      result = true;
03828      }
03829 
03830   // Record control:
03831   if (cRecordControls::StateChanged(recordControlsState)) {
03832      while (stopRecordingItem) {
03833            cOsdItem *it = Next(stopRecordingItem);
03834            Del(stopRecordingItem->Index());
03835            stopRecordingItem = it;
03836            }
03837      const char *s = NULL;
03838      while ((s = cRecordControls::GetInstantId(s)) != NULL) {
03839            cOsdItem *item = new cOsdItem(osStopRecord);
03840            item->SetText(cString::sprintf("%s%s", tr(STOP_RECORDING), s));
03841            Add(item);
03842            if (!stopRecordingItem)
03843               stopRecordingItem = item;
03844            }
03845      result = true;
03846      }
03847 
03848   return result;
03849 }
03850 
03851 eOSState cMenuMain::ProcessKey(eKeys Key)
03852 {
03853   bool HadSubMenu = HasSubMenu();
03854   int osdLanguage = I18nCurrentLanguage();
03855   eOSState state = cOsdMenu::ProcessKey(Key);
03856   HadSubMenu |= HasSubMenu();
03857 
03858   cOsdObject *menu = NULL;
03859   switch (state) {
03860     case osSchedule:
03861         if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu))
03862             menu = new cMenuSchedule;
03863         else
03864             state = osContinue;
03865         break;
03866     case osChannels:
03867         if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu))
03868             menu = new cMenuChannels;
03869         else
03870             state = osContinue;
03871         break;
03872     case osTimers:
03873         if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu))
03874             menu = new cMenuTimers;
03875         else
03876             state = osContinue;
03877         break;
03878     case osRecordings:
03879         if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu))
03880             menu = new cMenuRecordings;
03881         else
03882             state = osContinue;
03883         break;
03884     case osSetup:      menu = new cMenuSetup; break;
03885     case osCommands:   menu = new cMenuCommands(tr("Commands"), &Commands); break;
03886     case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) {
03887                           cOsdItem *item = Get(Current());
03888                           if (item) {
03889                              cRecordControls::Stop(item->Text() + strlen(tr(STOP_RECORDING)));
03890                              return osEnd;
03891                              }
03892                           }
03893                        break;
03894     case osCancelEdit: if (Interface->Confirm(tr("Cancel editing?"))) {
03895                           cCutter::Stop();
03896                           return osEnd;
03897                           }
03898                        break;
03899     case osCancelTransfer:
03900                        if (Interface->Confirm(tr("Cancel file transfer?"))) {
03901                           cFileTransfer::Stop();
03902                           return osEnd;
03903                           }
03904                        break;
03905     case osPlugin:     {
03906                          cMenuPluginItem *item = (cMenuPluginItem *)Get(Current());
03907                          if (item) {
03908                             cPlugin *p = cPluginManager::GetPlugin(item->PluginIndex());
03909                             if (p) {
03910                                cOsdObject *menu = p->MainMenuAction();
03911                                if (menu) {
03912                                   if (menu->IsMenu())
03913                                      return AddSubMenu((cOsdMenu *)menu);
03914                                   else {
03915                                      pluginOsdObject = menu;
03916                                      return osPlugin;
03917                                      }
03918                                   }
03919                                }
03920                             }
03921                          state = osEnd;
03922                        }
03923                        break;
03924     default: switch (Key) {
03925                case kRecord:
03926                case kRed:    if (!HadSubMenu)
03927                                 state = replaying ? osContinue : osRecord;
03928                              break;
03929                case kGreen:  if (!HadSubMenu) {
03930                                 cRemote::Put(kAudio, true);
03931                                 state = osEnd;
03932                                 }
03933                              break;
03934                case kYellow: if (!HadSubMenu)
03935                                 state = replaying ? osContinue : osPause;
03936                              break;
03937                case kBlue:   if (!HadSubMenu)
03938                                 state = replaying ? osStopReplay : cReplayControl::LastReplayed() ? osReplay : osContinue;
03939                              break;
03940                default:      break;
03941                }
03942     }
03943   if (menu) {
03944      if (menu->IsMenu())
03945         return AddSubMenu((cOsdMenu *) menu);
03946      pluginOsdObject = menu;
03947      return osPlugin;
03948   } 
03949   if (!HasSubMenu() && Update(HadSubMenu))
03950      Display();
03951   if (Key != kNone) {
03952      if (I18nCurrentLanguage() != osdLanguage) {
03953         Set();
03954         if (!HasSubMenu())
03955            Display();
03956         }
03957      }
03958   return state;
03959 }
03960 
03961 // --- SetTrackDescriptions --------------------------------------------------
03962 
03963 static void SetTrackDescriptions(int LiveChannel)
03964 {
03965   cDevice::PrimaryDevice()->ClrAvailableTracks(true);
03966   const cComponents *Components = NULL;
03967   cSchedulesLock SchedulesLock;
03968   if (LiveChannel) {
03969      cChannel *Channel = Channels.GetByNumber(LiveChannel);
03970      if (Channel) {
03971         const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
03972         if (Schedules) {
03973            const cSchedule *Schedule = Schedules->GetSchedule(Channel);
03974            if (Schedule) {
03975               const cEvent *Present = Schedule->GetPresentEvent();
03976               if (Present)
03977                  Components = Present->Components();
03978               }
03979            }
03980         }
03981      }
03982   else if (cReplayControl::NowReplaying()) {
03983      cThreadLock RecordingsLock(&Recordings);
03984      cRecording *Recording = Recordings.GetByName(cReplayControl::NowReplaying());
03985      if (Recording)
03986         Components = Recording->Info()->Components();
03987      }
03988   if (Components) {
03989      int indexAudio = 0;
03990      int indexDolby = 0;
03991      int indexSubtitle = 0;
03992      for (int i = 0; i < Components->NumComponents(); i++) {
03993          const tComponent *p = Components->Component(i);
03994          switch (p->stream) {
03995            case 2: if (p->type == 0x05)
03996                       cDevice::PrimaryDevice()->SetAvailableTrack(ttDolby, indexDolby++, 0, LiveChannel ? NULL : p->language, p->description);
03997                    else
03998                       cDevice::PrimaryDevice()->SetAvailableTrack(ttAudio, indexAudio++, 0, LiveChannel ? NULL : p->language, p->description);
03999                    break;
04000            case 3: cDevice::PrimaryDevice()->SetAvailableTrack(ttSubtitle, indexSubtitle++, 0, LiveChannel ? NULL : p->language, p->description);
04001                    break;
04002            case 4: cDevice::PrimaryDevice()->SetAvailableTrack(ttDolby, indexDolby++, 0, LiveChannel ? NULL : p->language, p->description);
04003                    break;
04004            default: ;
04005            }
04006          }
04007      }
04008 }
04009 
04010 // --- cDisplayChannel -------------------------------------------------------
04011 
04012 cDisplayChannel *cDisplayChannel::currentDisplayChannel = NULL;
04013 
04014 cDisplayChannel::cDisplayChannel(int Number, bool Switched)
04015 :cOsdObject(true)
04016 {
04017   currentDisplayChannel = this;
04018   group = -1;
04019   withInfo = !Switched || Setup.ShowInfoOnChSwitch;
04020   displayChannel = Skins.Current()->DisplayChannel(withInfo);
04021   number = 0;
04022   timeout = Switched || Setup.TimeoutRequChInfo;
04023   channel = Channels.GetByNumber(Number);
04024   lastPresent = lastFollowing = NULL;
04025   if (channel) {
04026      DisplayChannel();
04027      DisplayInfo();
04028      displayChannel->Flush();
04029      }
04030   lastTime.Set();
04031 }
04032 
04033 cDisplayChannel::cDisplayChannel(eKeys FirstKey)
04034 :cOsdObject(true)
04035 {
04036   currentDisplayChannel = this;
04037   group = -1;
04038   number = 0;
04039   timeout = true;
04040   lastPresent = lastFollowing = NULL;
04041   lastTime.Set();
04042   withInfo = Setup.ShowInfoOnChSwitch;
04043   displayChannel = Skins.Current()->DisplayChannel(withInfo);
04044   channel = Channels.GetByNumber(cDevice::CurrentChannel());
04045   ProcessKey(FirstKey);
04046 }
04047 
04048 cDisplayChannel::~cDisplayChannel()
04049 {
04050   delete displayChannel;
04051   cStatus::MsgOsdClear();
04052   currentDisplayChannel = NULL;
04053 }
04054 
04055 void cDisplayChannel::DisplayChannel(void)
04056 {
04057   displayChannel->SetChannel(channel, number);
04058   cStatus::MsgOsdChannel(ChannelString(channel, number));
04059   lastPresent = lastFollowing = NULL;
04060 }
04061 
04062 void cDisplayChannel::DisplayInfo(void)
04063 {
04064   if (withInfo && channel) {
04065      cSchedulesLock SchedulesLock;
04066      const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
04067      if (Schedules) {
04068         const cSchedule *Schedule = Schedules->GetSchedule(channel);
04069         if (Schedule) {
04070            const cEvent *Present = Schedule->GetPresentEvent();
04071            const cEvent *Following = Schedule->GetFollowingEvent();
04072            if (Present != lastPresent || Following != lastFollowing) {
04073               SetTrackDescriptions(channel->Number());
04074               displayChannel->SetEvents(Present, Following);
04075               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);
04076               lastPresent = Present;
04077               lastFollowing = Following;
04078               }
04079            }
04080         }
04081      }
04082 }
04083 
04084 void cDisplayChannel::Refresh(void)
04085 {
04086   DisplayChannel();
04087   displayChannel->SetEvents(NULL, NULL);
04088 }
04089 
04090 cChannel *cDisplayChannel::NextAvailableChannel(cChannel *Channel, int Direction)
04091 {
04092   if (Direction) {
04093      while (Channel) {
04094            Channel = Direction > 0 ? Channels.Next(Channel) : Channels.Prev(Channel);
04095            if (!Channel && Setup.ChannelsWrap)
04096               Channel = Direction > 0 ? Channels.First() : Channels.Last();
04097            if (Channel && !Channel->GroupSep() && cDevice::GetDevice(Channel, LIVEPRIORITY, true, true))
04098               return Channel;
04099            }
04100      }
04101   return NULL;
04102 }
04103 
04104 eOSState cDisplayChannel::ProcessKey(eKeys Key)
04105 {
04106   cChannel *NewChannel = NULL;
04107   if (Key != kNone)
04108      lastTime.Set();
04109   switch (int(Key)) {
04110     case k0:
04111          if (number == 0) {
04112             // keep the "Toggle channels" function working
04113             cRemote::Put(Key);
04114             return osEnd;
04115             }
04116     case k1 ... k9:
04117          group = -1;
04118          if (number >= 0) {
04119             if (number > Channels.MaxNumber())
04120                number = Key - k0;
04121             else
04122                number = number * 10 + Key - k0;
04123             channel = Channels.GetByNumber(number);
04124             Refresh();
04125             withInfo = false;
04126             // Lets see if there can be any useful further input:
04127             int n = channel ? number * 10 : 0;
04128             int m = 10;
04129             cChannel *ch = channel;
04130             while (ch && (ch = Channels.Next(ch)) != NULL) {
04131                   if (!ch->GroupSep()) {
04132                      if (n <= ch->Number() && ch->Number() < n + m) {
04133                         n = 0;
04134                         break;
04135                         }
04136                      if (ch->Number() > n) {
04137                         n *= 10;
04138                         m *= 10;
04139                         }
04140                      }
04141                   }
04142             if (n > 0) {
04143                // This channel is the only one that fits the input, so let's take it right away:
04144                NewChannel = channel;
04145                withInfo = true;
04146                number = 0;
04147                Refresh();
04148                }
04149             }
04150          break;
04151     case kLeft|k_Repeat:
04152     case kLeft:
04153     case kRight|k_Repeat:
04154     case kRight:
04155     case kNext|k_Repeat:
04156     case kNext:
04157     case kPrev|k_Repeat:
04158     case kPrev:
04159          withInfo = false;
04160          number = 0;
04161          if (group < 0) {
04162             cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
04163             if (channel)
04164                group = channel->Index();
04165             }
04166          if (group >= 0) {
04167             int SaveGroup = group;
04168             if (NORMALKEY(Key) == kRight || NORMALKEY(Key) == kNext)
04169                group = Channels.GetNextGroup(group) ;
04170             else
04171                group = Channels.GetPrevGroup(group < 1 ? 1 : group);
04172             if (group < 0)
04173                group = SaveGroup;
04174             channel = Channels.Get(group);
04175             if (channel) {
04176                Refresh();
04177                if (!channel->GroupSep())
04178                   group = -1;
04179                }
04180             }
04181          break;
04182     case kUp|k_Repeat:
04183     case kUp:
04184     case kDown|k_Repeat:
04185     case kDown:
04186     case kChanUp|k_Repeat:
04187     case kChanUp:
04188     case kChanDn|k_Repeat:
04189     case kChanDn: {
04190          eKeys k = NORMALKEY(Key);
04191          cChannel *ch = NextAvailableChannel(channel, (k == kUp || k == kChanUp) ? 1 : -1);
04192          if (ch)
04193             channel = ch;
04194          else if (channel && channel->Number() != cDevice::CurrentChannel())
04195             Key = k; // immediately switches channel when hitting the beginning/end of the channel list with k_Repeat
04196          }
04197          // no break here
04198     case kUp|k_Release:
04199     case kDown|k_Release:
04200     case kChanUp|k_Release:
04201     case kChanDn|k_Release:
04202     case kNext|k_Release:
04203     case kPrev|k_Release:
04204          if (!(Key & k_Repeat) && channel && channel->Number() != cDevice::CurrentChannel())
04205             NewChannel = channel;
04206          withInfo = true;
04207          group = -1;
04208          number = 0;
04209          Refresh();
04210          break;
04211     case kNone:
04212          if (number && Setup.ChannelEntryTimeout && int(lastTime.Elapsed()) > Setup.ChannelEntryTimeout) {
04213             channel = Channels.GetByNumber(number);
04214             if (channel)
04215                NewChannel = channel;
04216             withInfo = true;
04217             number = 0;
04218             Refresh();
04219             lastTime.Set();
04220             }
04221          break;
04222     //TODO
04223     //XXX case kGreen:  return osEventNow;
04224     //XXX case kYellow: return osEventNext;
04225     case kOk:
04226          if (group >= 0) {
04227             channel = Channels.Get(Channels.GetNextNormal(group));
04228             if (channel)
04229                NewChannel = channel;
04230             withInfo = true;
04231             group = -1;
04232             Refresh();
04233             }
04234          else if (number > 0) {
04235             channel = Channels.GetByNumber(number);
04236             if (channel)
04237                NewChannel = channel;
04238             withInfo = true;
04239             number = 0;
04240             Refresh();
04241             }
04242          else
04243             return osEnd;
04244          break;
04245     default:
04246          if ((Key & (k_Repeat | k_Release)) == 0) {
04247             cRemote::Put(Key);
04248             return osEnd;
04249             }
04250     };
04251   if (!timeout || lastTime.Elapsed() < (uint64_t)(Setup.ChannelInfoTime * 1000)) {
04252      if (Key == kNone && !number && group < 0 && !NewChannel && channel && channel->Number() != cDevice::CurrentChannel()) {
04253         // makes sure a channel switch through the SVDRP CHAN command is displayed
04254         channel = Channels.GetByNumber(cDevice::CurrentChannel());
04255         Refresh();
04256         lastTime.Set();
04257         }
04258      DisplayInfo();
04259      displayChannel->Flush();
04260      if (NewChannel) {
04261         SetTrackDescriptions(NewChannel->Number()); // to make them immediately visible in the channel display
04262         Channels.SwitchTo(NewChannel->Number());
04263         SetTrackDescriptions(NewChannel->Number()); // switching the channel has cleared them
04264         channel = NewChannel;
04265         }
04266      return osContinue;
04267      }
04268   return osEnd;
04269 }
04270 
04271 // --- cDisplayVolume --------------------------------------------------------
04272 
04273 #define VOLUMETIMEOUT 1000 //ms
04274 #define MUTETIMEOUT   5000 //ms
04275 
04276 cDisplayVolume *cDisplayVolume::currentDisplayVolume = NULL;
04277 
04278 cDisplayVolume::cDisplayVolume(void)
04279 :cOsdObject(true)
04280 {
04281   currentDisplayVolume = this;
04282   timeout.Set(cDevice::PrimaryDevice()->IsMute() ? MUTETIMEOUT : VOLUMETIMEOUT);
04283   displayVolume = Skins.Current()->DisplayVolume();
04284   Show();
04285 }
04286 
04287 cDisplayVolume::~cDisplayVolume()
04288 {
04289   delete displayVolume;
04290   currentDisplayVolume = NULL;
04291 }
04292 
04293 void cDisplayVolume::Show(void)
04294 {
04295   displayVolume->SetVolume(cDevice::CurrentVolume(), MAXVOLUME, cDevice::PrimaryDevice()->IsMute());
04296 }
04297 
04298 cDisplayVolume *cDisplayVolume::Create(void)
04299 {
04300   if (!currentDisplayVolume)
04301      new cDisplayVolume;
04302   return currentDisplayVolume;
04303 }
04304 
04305 void cDisplayVolume::Process(eKeys Key)
04306 {
04307   if (currentDisplayVolume)
04308      currentDisplayVolume->ProcessKey(Key);
04309 }
04310 
04311 eOSState cDisplayVolume::ProcessKey(eKeys Key)
04312 {
04313   switch (int(Key)) {
04314     case kVolUp|k_Repeat:
04315     case kVolUp:
04316     case kVolDn|k_Repeat:
04317     case kVolDn:
04318          Show();
04319          timeout.Set(VOLUMETIMEOUT);
04320          break;
04321     case kMute:
04322          if (cDevice::PrimaryDevice()->IsMute()) {
04323             Show();
04324             timeout.Set(MUTETIMEOUT);
04325             }
04326          else
04327             timeout.Set();
04328          break;
04329     case kNone: break;
04330     default: if ((Key & k_Release) == 0) {
04331                 cRemote::Put(Key);
04332                 return osEnd;
04333                 }
04334     }
04335   return timeout.TimedOut() ? osEnd : osContinue;
04336 }
04337 
04338 // --- cDisplayTracks --------------------------------------------------------
04339 
04340 #define TRACKTIMEOUT 5000 //ms
04341 
04342 cDisplayTracks *cDisplayTracks::currentDisplayTracks = NULL;
04343 
04344 cDisplayTracks::cDisplayTracks(void)
04345 :cOsdObject(true)
04346 {
04347   cDevice::PrimaryDevice()->EnsureAudioTrack();
04348   SetTrackDescriptions(!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring() ? cDevice::CurrentChannel() : 0);
04349   currentDisplayTracks = this;
04350   numTracks = track = 0;
04351   audioChannel = cDevice::PrimaryDevice()->GetAudioChannel();
04352   eTrackType CurrentAudioTrack = cDevice::PrimaryDevice()->GetCurrentAudioTrack();
04353   for (int i = ttAudioFirst; i <= ttDolbyLast; i++) {
04354       const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(i));
04355       if (TrackId && TrackId->id) {
04356          types[numTracks] = eTrackType(i);
04357          descriptions[numTracks] = strdup(*TrackId->description ? TrackId->description : *TrackId->language ? TrackId->language : *itoa(i));
04358          if (i == CurrentAudioTrack)
04359             track = numTracks;
04360          numTracks++;
04361          }
04362       }
04363   descriptions[numTracks] = NULL;
04364   timeout.Set(TRACKTIMEOUT);
04365   displayTracks = Skins.Current()->DisplayTracks(tr("Button$Audio"), numTracks, descriptions);
04366   Show();
04367 }
04368 
04369 cDisplayTracks::~cDisplayTracks()
04370 {
04371   delete displayTracks;
04372   currentDisplayTracks = NULL;
04373   for (int i = 0; i < numTracks; i++)
04374       free(descriptions[i]);
04375   cStatus::MsgOsdClear();
04376 }
04377 
04378 void cDisplayTracks::Show(void)
04379 {
04380   int ac = IS_AUDIO_TRACK(types[track]) ? audioChannel : -1;
04381   displayTracks->SetTrack(track, descriptions);
04382   displayTracks->SetAudioChannel(ac);
04383   displayTracks->Flush();
04384   cStatus::MsgSetAudioTrack(track, descriptions);
04385   cStatus::MsgSetAudioChannel(ac);
04386 }
04387 
04388 cDisplayTracks *cDisplayTracks::Create(void)
04389 {
04390   if (cDevice::PrimaryDevice()->NumAudioTracks() > 0) {
04391      if (!currentDisplayTracks)
04392         new cDisplayTracks;
04393      return currentDisplayTracks;
04394      }
04395   Skins.Message(mtWarning, tr("No audio available!"));
04396   return NULL;
04397 }
04398 
04399 void cDisplayTracks::Process(eKeys Key)
04400 {
04401   if (currentDisplayTracks)
04402      currentDisplayTracks->ProcessKey(Key);
04403 }
04404 
04405 eOSState cDisplayTracks::ProcessKey(eKeys Key)
04406 {
04407   int oldTrack = track;
04408   int oldAudioChannel = audioChannel;
04409   switch (int(Key)) {
04410     case kUp|k_Repeat:
04411     case kUp:
04412     case kDown|k_Repeat:
04413     case kDown:
04414          if (NORMALKEY(Key) == kUp && track > 0)
04415             track--;
04416          else if (NORMALKEY(Key) == kDown && track < numTracks - 1)
04417             track++;
04418          timeout.Set(TRACKTIMEOUT);
04419          break;
04420     case kLeft|k_Repeat:
04421     case kLeft:
04422     case kRight|k_Repeat:
04423     case kRight: if (IS_AUDIO_TRACK(types[track])) {
04424                     static int ac[] = { 1, 0, 2 };
04425                     audioChannel = ac[cDevice::PrimaryDevice()->GetAudioChannel()];
04426                     if (NORMALKEY(Key) == kLeft && audioChannel > 0)
04427                        audioChannel--;
04428                     else if (NORMALKEY(Key) == kRight && audioChannel < 2)
04429                        audioChannel++;
04430                     audioChannel = ac[audioChannel];
04431                     timeout.Set(TRACKTIMEOUT);
04432                     }
04433          break;
04434     case kAudio|k_Repeat:
04435     case kAudio:
04436          if (++track >= numTracks)
04437             track = 0;
04438          timeout.Set(TRACKTIMEOUT);
04439          break;
04440     case kOk:
04441          if (types[track] != cDevice::PrimaryDevice()->GetCurrentAudioTrack())
04442             oldTrack = -1; // make sure we explicitly switch to that track
04443          timeout.Set();
04444          break;
04445     case kNone: break;
04446     default: if ((Key & k_Release) == 0)
04447                 return osEnd;
04448     }
04449   if (track != oldTrack || audioChannel != oldAudioChannel)
04450      Show();
04451   if (track != oldTrack) {
04452      cDevice::PrimaryDevice()->SetCurrentAudioTrack(types[track]);
04453      Setup.CurrentDolby = IS_DOLBY_TRACK(types[track]);
04454      }
04455   if (audioChannel != oldAudioChannel)
04456      cDevice::PrimaryDevice()->SetAudioChannel(audioChannel);
04457   return timeout.TimedOut() ? osEnd : osContinue;
04458 }
04459 
04460 // --- cDisplaySubtitleTracks ------------------------------------------------
04461 
04462 cDisplaySubtitleTracks *cDisplaySubtitleTracks::currentDisplayTracks = NULL;
04463 
04464 cDisplaySubtitleTracks::cDisplaySubtitleTracks(void)
04465 :cOsdObject(true)
04466 {
04467   SetTrackDescriptions(!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring() ? cDevice::CurrentChannel() : 0);
04468   currentDisplayTracks = this;
04469   numTracks = track = 0;
04470   types[numTracks] = ttNone;
04471   descriptions[numTracks] = strdup(tr("No subtitles"));
04472   numTracks++;
04473   eTrackType CurrentSubtitleTrack = cDevice::PrimaryDevice()->GetCurrentSubtitleTrack();
04474   for (int i = ttSubtitleFirst; i <= ttSubtitleLast; i++) {
04475       const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(i));
04476       if (TrackId && TrackId->id) {
04477          types[numTracks] = eTrackType(i);
04478          descriptions[numTracks] = strdup(*TrackId->description ? TrackId->description : *TrackId->language ? TrackId->language : *itoa(i));
04479          if (i == CurrentSubtitleTrack)
04480             track = numTracks;
04481          numTracks++;
04482          }
04483       }
04484   descriptions[numTracks] = NULL;
04485   timeout.Set(TRACKTIMEOUT);
04486   displayTracks = Skins.Current()->DisplayTracks(tr("Button$Subtitles"), numTracks, descriptions);
04487   Show();
04488 }
04489 
04490 cDisplaySubtitleTracks::~cDisplaySubtitleTracks()
04491 {
04492   delete displayTracks;
04493   currentDisplayTracks = NULL;
04494   for (int i = 0; i < numTracks; i++)
04495       free(descriptions[i]);
04496   cStatus::MsgOsdClear();
04497 }
04498 
04499 void cDisplaySubtitleTracks::Show(void)
04500 {
04501   displayTracks->SetTrack(track, descriptions);
04502   displayTracks->Flush();
04503   cStatus::MsgSetSubtitleTrack(track, descriptions);
04504 }
04505 
04506 cDisplaySubtitleTracks *cDisplaySubtitleTracks::Create(void)
04507 {
04508   if (cDevice::PrimaryDevice()->NumSubtitleTracks() > 0) {
04509      if (!currentDisplayTracks)
04510         new cDisplaySubtitleTracks;
04511      return currentDisplayTracks;
04512      }
04513   Skins.Message(mtWarning, tr("No subtitles available!"));
04514   return NULL;
04515 }
04516 
04517 void cDisplaySubtitleTracks::Process(eKeys Key)
04518 {
04519   if (currentDisplayTracks)
04520      currentDisplayTracks->ProcessKey(Key);
04521 }
04522 
04523 eOSState cDisplaySubtitleTracks::ProcessKey(eKeys Key)
04524 {
04525   int oldTrack = track;
04526   switch (int(Key)) {
04527     case kUp|k_Repeat:
04528     case kUp:
04529     case kDown|k_Repeat:
04530     case kDown:
04531          if (NORMALKEY(Key) == kUp && track > 0)
04532             track--;
04533          else if (NORMALKEY(Key) == kDown && track < numTracks - 1)
04534             track++;
04535          timeout.Set(TRACKTIMEOUT);
04536          break;
04537     case kSubtitles|k_Repeat:
04538     case kSubtitles:
04539          if (++track >= numTracks)
04540             track = 0;
04541          timeout.Set(TRACKTIMEOUT);
04542          break;
04543     case kOk:
04544          if (types[track] != cDevice::PrimaryDevice()->GetCurrentSubtitleTrack())
04545             oldTrack = -1; // make sure we explicitly switch to that track
04546          timeout.Set();
04547          break;
04548     case kNone: break;
04549     default: if ((Key & k_Release) == 0)
04550                 return osEnd;
04551     }
04552   if (track != oldTrack) {
04553      Show();
04554      cDevice::PrimaryDevice()->SetCurrentSubtitleTrack(types[track], true);
04555      }
04556   return timeout.TimedOut() ? osEnd : osContinue;
04557 }
04558 
04559 // --- cRecordControl --------------------------------------------------------
04560 
04561 cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause)
04562 {
04563   // We're going to manipulate an event here, so we need to prevent
04564   // others from modifying any EPG data:
04565   cSchedulesLock SchedulesLock;
04566   cSchedules::Schedules(SchedulesLock);
04567 
04568   event = NULL;
04569   fileName = NULL;
04570   recorder = NULL;
04571   device = Device;
04572   if (!device) device = cDevice::PrimaryDevice();//XXX
04573   timer = Timer;
04574   if (!timer) {
04575      timer = new cTimer(true, Pause);
04576      Timers.Add(timer);
04577      Timers.SetModified();
04578      instantId = cString::sprintf(cDevice::NumDevices() > 1 ? "%s - %d" : "%s", timer->Channel()->Name(), device->CardIndex() + 1);
04579      }
04580   timer->SetPending(true);
04581   timer->SetRecording(true);
04582   event = timer->Event();
04583 
04584   if (event || GetEvent())
04585      dsyslog("Title: '%s' Subtitle: '%s'", event->Title(), event->ShortText());
04586   cRecording Recording(timer, event);
04587   fileName = strdup(Recording.FileName());
04588 
04589   // crude attempt to avoid duplicate recordings:
04590   if (cRecordControls::GetRecordControl(fileName)) {
04591      isyslog("already recording: '%s'", fileName);
04592      if (Timer) {
04593         timer->SetPending(false);
04594         timer->SetRecording(false);
04595         timer->OnOff();
04596         }
04597      else {
04598         Timers.Del(timer);
04599         Timers.SetModified();
04600         if (!cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo()
04601            cReplayControl::SetRecording(fileName, Recording.Name());
04602         }
04603      timer = NULL;
04604      return;
04605      }
04606 
04607   cRecordingUserCommand::InvokeCommand(RUC_BEFORERECORDING, fileName);
04608   isyslog("record %s", fileName);
04609   if (MakeDirs(fileName, true)) {
04610      const cChannel *ch = timer->Channel();
04611      recorder = new cRecorder(fileName, ch, timer->Priority());
04612      if (device->AttachReceiver(recorder)) {
04613         Recording.WriteInfo();
04614         cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true);
04615         if (!Timer && !cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo()
04616            cReplayControl::SetRecording(fileName, Recording.Name());
04617         Recordings.AddByName(fileName);
04618         return;
04619         }
04620      else
04621         DELETENULL(recorder);
04622      }
04623   else
04624      timer->SetDeferred(DEFERTIMER);
04625   if (!Timer) {
04626      Timers.Del(timer);
04627      Timers.SetModified();
04628      timer = NULL;
04629      }
04630 }
04631 
04632 cRecordControl::~cRecordControl()
04633 {
04634   Stop();
04635   free(fileName);
04636 }
04637 
04638 #define INSTANT_REC_EPG_LOOKAHEAD 300 // seconds to look into the EPG data for an instant recording
04639 
04640 bool cRecordControl::GetEvent(void)
04641 {
04642   const cChannel *channel = timer->Channel();
04643   time_t Time = timer->HasFlags(tfInstant) ? timer->StartTime() + INSTANT_REC_EPG_LOOKAHEAD : timer->StartTime() + (timer->StopTime() - timer->StartTime()) / 2;
04644   for (int seconds = 0; seconds <= MAXWAIT4EPGINFO; seconds++) {
04645       {
04646         cSchedulesLock SchedulesLock;
04647         const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
04648         if (Schedules) {
04649            const cSchedule *Schedule = Schedules->GetSchedule(channel);
04650            if (Schedule) {
04651               event = Schedule->GetEventAround(Time);
04652               if (event) {
04653                  if (seconds > 0)
04654                     dsyslog("got EPG info after %d seconds", seconds);
04655                  return true;
04656                  }
04657               }
04658            }
04659       }
04660       if (seconds == 0)
04661          dsyslog("waiting for EPG info...");
04662       cCondWait::SleepMs(1000);
04663       }
04664   dsyslog("no EPG info available");
04665   return false;
04666 }
04667 
04668 void cRecordControl::Stop(bool ExecuteUserCommand)
04669 {
04670   if (timer) {
04671      DELETENULL(recorder);
04672      timer->SetRecording(false);
04673      timer = NULL;
04674      cStatus::MsgRecording(device, NULL, fileName, false);
04675      if (ExecuteUserCommand)
04676         cRecordingUserCommand::InvokeCommand(RUC_AFTERRECORDING, fileName);
04677      }
04678 }
04679 
04680 bool cRecordControl::Process(time_t t)
04681 {
04682   if (!recorder || !recorder->IsAttached() || !timer || !timer->Matches(t)) {
04683      if (timer)
04684         timer->SetPending(false);
04685      return false;
04686      }
04687   AssertFreeDiskSpace(timer->Priority());
04688   return true;
04689 }
04690 
04691 // --- cRecordControls -------------------------------------------------------
04692 
04693 cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL };
04694 int cRecordControls::state = 0;
04695 
04696 bool cRecordControls::Start(cTimer *Timer, bool Pause)
04697 {
04698   static time_t LastNoDiskSpaceMessage = 0;
04699   int FreeMB = 0;
04700   if (Timer) {
04701      AssertFreeDiskSpace(Timer->Priority(), !Timer->Pending());
04702      Timer->SetPending(true);
04703      }
04704   VideoDiskSpace(&FreeMB);
04705   if (FreeMB < MINFREEDISK) {
04706      if (!Timer || time(NULL) - LastNoDiskSpaceMessage > NODISKSPACEDELTA) {
04707         isyslog("not enough disk space to start recording%s%s", Timer ? " timer " : "", Timer ? *Timer->ToDescr() : "");
04708         Skins.Message(mtWarning, tr("Not enough disk space to start recording!"));
04709         LastNoDiskSpaceMessage = time(NULL);
04710         }
04711      return false;
04712      }
04713   LastNoDiskSpaceMessage = 0;
04714 
04715   ChangeState();
04716   int ch = Timer ? Timer->Channel()->Number() : cDevice::CurrentChannel();
04717   cChannel *channel = Channels.GetByNumber(ch);
04718 
04719   if (channel) {
04720      int Priority = Timer ? Timer->Priority() : Pause ? Setup.PausePriority : Setup.DefaultPriority;
04721      cDevice *device = cDevice::GetDevice(channel, Priority, false);
04722      if (device) {
04723         dsyslog("switching device %d to channel %d", device->DeviceNumber() + 1, channel->Number());
04724         if (!device->SwitchChannel(channel, false)) {
04725            ShutdownHandler.RequestEmergencyExit();
04726            return false;
04727            }
04728         if (!Timer || Timer->Matches()) {
04729            for (int i = 0; i < MAXRECORDCONTROLS; i++) {
04730                if (!RecordControls[i]) {
04731                   RecordControls[i] = new cRecordControl(device, Timer, Pause);
04732                   return RecordControls[i]->Process(time(NULL));
04733                   }
04734                }
04735            }
04736         }
04737      else if (!Timer || !Timer->Pending()) {
04738         isyslog("no free DVB device to record channel %d!", ch);
04739         Skins.Message(mtError, tr("No free DVB device to record!"));
04740         }
04741      }
04742   else
04743      esyslog("ERROR: channel %d not defined!", ch);
04744   return false;
04745 }
04746 
04747 void cRecordControls::Stop(const char *InstantId)
04748 {
04749   ChangeState();
04750   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
04751       if (RecordControls[i]) {
04752          const char *id = RecordControls[i]->InstantId();
04753          if (id && strcmp(id, InstantId) == 0) {
04754             cTimer *timer = RecordControls[i]->Timer();
04755             RecordControls[i]->Stop();
04756             if (timer) {
04757                isyslog("deleting timer %s", *timer->ToDescr());
04758                Timers.Del(timer);
04759                Timers.SetModified();
04760                }
04761             break;
04762             }
04763          }
04764       }
04765 }
04766 
04767 bool cRecordControls::PauseLiveVideo(void)
04768 {
04769   Skins.Message(mtStatus, tr("Pausing live video..."));
04770   cReplayControl::SetRecording(NULL, NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed()
04771   if (Start(NULL, true)) {
04772      cReplayControl *rc = new cReplayControl(true);
04773      cControl::Launch(rc);
04774      cControl::Attach();
04775      Skins.Message(mtStatus, NULL);
04776      return true;
04777      }
04778   Skins.Message(mtStatus, NULL);
04779   return false;
04780 }
04781 
04782 const char *cRecordControls::GetInstantId(const char *LastInstantId)
04783 {
04784   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
04785       if (RecordControls[i]) {
04786          if (!LastInstantId && RecordControls[i]->InstantId())
04787             return RecordControls[i]->InstantId();
04788          if (LastInstantId && LastInstantId == RecordControls[i]->InstantId())
04789             LastInstantId = NULL;
04790          }
04791       }
04792   return NULL;
04793 }
04794 
04795 cRecordControl *cRecordControls::GetRecordControl(const char *FileName)
04796 {
04797   if (FileName) {
04798      for (int i = 0; i < MAXRECORDCONTROLS; i++) {
04799          if (RecordControls[i] && strcmp(RecordControls[i]->FileName(), FileName) == 0)
04800             return RecordControls[i];
04801          }
04802      }
04803   return NULL;
04804 }
04805 
04806 void cRecordControls::Process(time_t t)
04807 {
04808   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
04809       if (RecordControls[i]) {
04810          if (!RecordControls[i]->Process(t)) {
04811             DELETENULL(RecordControls[i]);
04812             ChangeState();
04813             }
04814          }
04815       }
04816 }
04817 
04818 void cRecordControls::ChannelDataModified(cChannel *Channel)
04819 {
04820   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
04821       if (RecordControls[i]) {
04822          if (RecordControls[i]->Timer() && RecordControls[i]->Timer()->Channel() == Channel) {
04823             if (RecordControls[i]->Device()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
04824                isyslog("stopping recording due to modification of channel %d", Channel->Number());
04825                RecordControls[i]->Stop();
04826                // This will restart the recording, maybe even from a different
04827                // device in case conditional access has changed.
04828                ChangeState();
04829                }
04830             }
04831          }
04832       }
04833 }
04834 
04835 bool cRecordControls::Active(void)
04836 {
04837   for (int i = 0; i < MAXRECORDCONTROLS; i++) {
04838       if (RecordControls[i])
04839          return true;
04840       }
04841   return false;
04842 }
04843 
04844 void cRecordControls::Shutdown(void)
04845 {
04846   for (int i = 0; i < MAXRECORDCONTROLS; i++)
04847       DELETENULL(RecordControls[i]);
04848   ChangeState();
04849 }
04850 
04851 bool cRecordControls::StateChanged(int &State)
04852 {
04853   int NewState = state;
04854   bool Result = State != NewState;
04855   State = state;
04856   return Result;
04857 }
04858 
04859 // --- cReplayControl --------------------------------------------------------
04860 
04861 #define REPLAYCONTROLSKIPLIMIT   9    // s
04862 #define REPLAYCONTROLSKIPSECONDS 90   // s
04863 #define REPLAYCONTROLSKIPTIMEOUT 5000 // ms
04864 
04865 cReplayControl *cReplayControl::currentReplayControl = NULL;
04866 char *cReplayControl::fileName = NULL;
04867 char *cReplayControl::title = NULL;
04868 
04869 cReplayControl::cReplayControl(bool PauseLive)
04870 :cDvbPlayerControl(fileName, PauseLive)
04871 {
04872   currentReplayControl = this;
04873   displayReplay = NULL;
04874   visible = modeOnly = shown = displayFrames = false;
04875   lastCurrent = lastTotal = -1;
04876   lastPlay = lastForward = false;
04877   lastSpeed = -2; // an invalid value
04878   lastSkipKey = kNone;
04879   lastSkipSeconds = REPLAYCONTROLSKIPSECONDS;
04880   lastSkipTimeout.Set(0);
04881   timeoutShow = 0;
04882   timeSearchActive = false;
04883   cRecording Recording(fileName);
04884   cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true);
04885   marks.Load(fileName, Recording.FramesPerSecond(), Recording.IsPesRecording());
04886   SetTrackDescriptions(false);
04887 }
04888 
04889 cReplayControl::~cReplayControl()
04890 {
04891   Hide();
04892   cStatus::MsgReplaying(this, NULL, fileName, false);
04893   Stop();
04894   if (currentReplayControl == this)
04895      currentReplayControl = NULL;
04896 }
04897 
04898 void cReplayControl::Stop(void)
04899 {
04900   if (Setup.DelTimeshiftRec && fileName) {
04901      cRecordControl* rc = cRecordControls::GetRecordControl(fileName);
04902      if (rc && rc->InstantId()) {
04903         if (Active()) {
04904            if (Setup.DelTimeshiftRec == 2 || Interface->Confirm(tr("Delete timeshift recording?"))) {
04905               cTimer *timer = rc->Timer();
04906               rc->Stop(false); // don't execute user command
04907               if (timer) {
04908                  isyslog("deleting timer %s", *timer->ToDescr());
04909                  Timers.Del(timer);
04910                  Timers.SetModified();
04911                  }
04912               cDvbPlayerControl::Stop();
04913               cRecording *recording = Recordings.GetByName(fileName);;
04914               if (recording) {
04915                  if (recording->Delete()) {
04916                     Recordings.DelByName(fileName);
04917                     ClearLastReplayed(fileName);
04918                     }
04919                  else
04920                     Skins.Message(mtError, tr("Error while deleting recording!"));
04921                  }
04922               return;
04923               }
04924            }
04925         }
04926      }
04927   cDvbPlayerControl::Stop();
04928 }
04929 
04930 void cReplayControl::SetRecording(const char *FileName, const char *Title)
04931 {
04932   free(fileName);
04933   free(title);
04934   fileName = FileName ? strdup(FileName) : NULL;
04935   title = Title ? strdup(Title) : NULL;
04936 }
04937 
04938 const char *cReplayControl::NowReplaying(void)
04939 {
04940   return currentReplayControl ? fileName : NULL;
04941 }
04942 
04943 const char *cReplayControl::LastReplayed(void)
04944 {
04945   return fileName;
04946 }
04947 
04948 void cReplayControl::ClearLastReplayed(const char *FileName)
04949 {
04950   if (fileName && FileName && strcmp(fileName, FileName) == 0) {
04951      free(fileName);
04952      fileName = NULL;
04953      }
04954 }
04955 
04956 void cReplayControl::ShowTimed(int Seconds)
04957 {
04958   if (modeOnly)
04959      Hide();
04960   if (!visible) {
04961      shown = ShowProgress(true);
04962      timeoutShow = (shown && Seconds > 0) ? time(NULL) + Seconds : 0;
04963      }
04964   else if (timeoutShow && Seconds > 0)
04965      timeoutShow = time(NULL) + Seconds;
04966 }
04967 
04968 void cReplayControl::Show(void)
04969 {
04970   ShowTimed();
04971 }
04972 
04973 void cReplayControl::Hide(void)
04974 {
04975   if (visible) {
04976      delete displayReplay;
04977      displayReplay = NULL;
04978      SetNeedsFastResponse(false);
04979      visible = false;
04980      modeOnly = false;
04981      lastPlay = lastForward = false;
04982      lastSpeed = -2; // an invalid value
04983      timeSearchActive = false;
04984      timeoutShow = 0;
04985      }
04986 }
04987 
04988 void cReplayControl::ShowMode(void)
04989 {
04990   if (visible || Setup.ShowReplayMode && !cOsd::IsOpen()) {
04991      bool Play, Forward;
04992      int Speed;
04993      if (GetReplayMode(Play, Forward, Speed) && (!visible || Play != lastPlay || Forward != lastForward || Speed != lastSpeed)) {
04994         bool NormalPlay = (Play && Speed == -1);
04995 
04996         if (!visible) {
04997            if (NormalPlay)
04998               return; // no need to do indicate ">" unless there was a different mode displayed before
04999            visible = modeOnly = true;
05000            displayReplay = Skins.Current()->DisplayReplay(modeOnly);
05001            }
05002 
05003         if (modeOnly && !timeoutShow && NormalPlay)
05004            timeoutShow = time(NULL) + MODETIMEOUT;
05005         displayReplay->SetMode(Play, Forward, Speed);
05006         lastPlay = Play;
05007         lastForward = Forward;
05008         lastSpeed = Speed;
05009         }
05010      }
05011 }
05012 
05013 bool cReplayControl::ShowProgress(bool Initial)
05014 {
05015   int Current, Total;
05016 
05017   if (GetIndex(Current, Total) && Total > 0) {
05018      if (!visible) {
05019         displayReplay = Skins.Current()->DisplayReplay(modeOnly);
05020         displayReplay->SetMarks(&marks);
05021         SetNeedsFastResponse(true);
05022         visible = true;
05023         }
05024      if (Initial) {
05025         if (title)
05026            displayReplay->SetTitle(title);
05027         lastCurrent = lastTotal = -1;
05028         }
05029      if (Current != lastCurrent || Total != lastTotal) {
05030         if (Setup.ShowRemainingTime || Total != lastTotal) {
05031            int Index = Total;
05032            if (Setup.ShowRemainingTime)
05033               Index = Current - Index;
05034            displayReplay->SetTotal(IndexToHMSF(Index, false, FramesPerSecond()));
05035            if (!Initial)
05036               displayReplay->Flush();
05037            }
05038         displayReplay->SetProgress(Current, Total);
05039         if (!Initial)
05040            displayReplay->Flush();
05041         displayReplay->SetCurrent(IndexToHMSF(Current, displayFrames, FramesPerSecond()));
05042         displayReplay->Flush();
05043         lastCurrent = Current;
05044         }
05045      lastTotal = Total;
05046      ShowMode();
05047      return true;
05048      }
05049   return false;
05050 }
05051 
05052 void cReplayControl::TimeSearchDisplay(void)
05053 {
05054   char buf[64];
05055   // TRANSLATORS: note the trailing blank!
05056   strcpy(buf, tr("Jump: "));
05057   int len = strlen(buf);
05058   char h10 = '0' + (timeSearchTime >> 24);
05059   char h1  = '0' + ((timeSearchTime & 0x00FF0000) >> 16);
05060   char m10 = '0' + ((timeSearchTime & 0x0000FF00) >> 8);
05061   char m1  = '0' + (timeSearchTime & 0x000000FF);
05062   char ch10 = timeSearchPos > 3 ? h10 : '-';
05063   char ch1  = timeSearchPos > 2 ? h1  : '-';
05064   char cm10 = timeSearchPos > 1 ? m10 : '-';
05065   char cm1  = timeSearchPos > 0 ? m1  : '-';
05066   sprintf(buf + len, "%c%c:%c%c", ch10, ch1, cm10, cm1);
05067   displayReplay->SetJump(buf);
05068 }
05069 
05070 void cReplayControl::TimeSearchProcess(eKeys Key)
05071 {
05072 #define STAY_SECONDS_OFF_END 10
05073   int Seconds = (timeSearchTime >> 24) * 36000 + ((timeSearchTime & 0x00FF0000) >> 16) * 3600 + ((timeSearchTime & 0x0000FF00) >> 8) * 600 + (timeSearchTime & 0x000000FF) * 60;
05074   int Current = int(round(lastCurrent / FramesPerSecond()));
05075   int Total = int(round(lastTotal / FramesPerSecond()));
05076   switch (Key) {
05077     case k0 ... k9:
05078          if (timeSearchPos < 4) {
05079             timeSearchTime <<= 8;
05080             timeSearchTime |= Key - k0;
05081             timeSearchPos++;
05082             TimeSearchDisplay();
05083             }
05084          break;
05085     case kFastRew:
05086     case kLeft:
05087     case kFastFwd:
05088     case kRight: {
05089          int dir = ((Key == kRight || Key == kFastFwd) ? 1 : -1);
05090          if (dir > 0)
05091             Seconds = min(Total - Current - STAY_SECONDS_OFF_END, Seconds);
05092          SkipSeconds(Seconds * dir);
05093          timeSearchActive = false;
05094          }
05095          break;
05096     case kPlay:
05097     case kUp:
05098     case kPause:
05099     case kDown:
05100     case kOk:
05101          Seconds = min(Total - STAY_SECONDS_OFF_END, Seconds);
05102          Goto(SecondsToFrames(Seconds, FramesPerSecond()), Key == kDown || Key == kPause || Key == kOk);
05103          timeSearchActive = false;
05104          break;
05105     default:
05106          if (!(Key & k_Flags)) // ignore repeat/release keys
05107             timeSearchActive = false;
05108          break;
05109     }
05110 
05111   if (!timeSearchActive) {
05112      if (timeSearchHide)
05113         Hide();
05114      else
05115         displayReplay->SetJump(NULL);
05116      ShowMode();
05117      }
05118 }
05119 
05120 void cReplayControl::TimeSearch(void)
05121 {
05122   timeSearchTime = timeSearchPos = 0;
05123   timeSearchHide = false;
05124   if (modeOnly)
05125      Hide();
05126   if (!visible) {
05127      Show();
05128      if (visible)
05129         timeSearchHide = true;
05130      else
05131         return;
05132      }
05133   timeoutShow = 0;
05134   TimeSearchDisplay();
05135   timeSearchActive = true;
05136 }
05137 
05138 void cReplayControl::MarkToggle(void)
05139 {
05140   int Current, Total;
05141   if (GetIndex(Current, Total, true)) {
05142      cMark *m = marks.Get(Current);
05143      lastCurrent = -1; // triggers redisplay
05144      if (m)
05145         marks.Del(m);
05146      else {
05147         marks.Add(Current);
05148         bool Play, Forward;
05149         int Speed;
05150         if (GetReplayMode(Play, Forward, Speed) && !Play)
05151            Goto(Current, true);
05152         }
05153      ShowTimed(2);
05154      marks.Save();
05155      }
05156 }
05157 
05158 void cReplayControl::MarkJump(bool Forward)
05159 {
05160   if (marks.Count()) {
05161      int Current, Total;
05162      if (GetIndex(Current, Total)) {
05163         cMark *m = Forward ? marks.GetNext(Current) : marks.GetPrev(Current);
05164         if (m) {
05165            bool Play2, Forward2;
05166            int Speed;
05167            if (Setup.JumpPlay && GetReplayMode(Play2, Forward2, Speed) &&
05168                Play2 && Forward && m->Position() < Total - SecondsToFrames(3, FramesPerSecond())) {
05169               Goto(m->Position());
05170               Play();
05171               }
05172            else {
05173               Goto(m->Position(), true);
05174               displayFrames = true;
05175               }
05176            }
05177         }
05178      }
05179 }
05180 
05181 void cReplayControl::MarkMove(bool Forward)
05182 {
05183   int Current, Total;
05184   if (GetIndex(Current, Total)) {
05185      cMark *m = marks.Get(Current);
05186      if (m) {
05187         displayFrames = true;
05188         int p = SkipFrames(Forward ? 1 : -1);
05189         cMark *m2;
05190         if (Forward) {
05191            if ((m2 = marks.Next(m)) != NULL && m2->Position() <= p)
05192               return;
05193            }
05194         else {
05195            if ((m2 = marks.Prev(m)) != NULL && m2->Position() >= p)
05196               return;
05197            }
05198         m->SetPosition(p);
05199         Goto(m->Position(), true);
05200         marks.Save();
05201         }
05202      }
05203 }
05204 
05205 void cReplayControl::EditCut(void)
05206 {
05207   if (fileName) {
05208      Hide();
05209      if (!cCutter::Active()) {
05210         if (!marks.Count())
05211            Skins.Message(mtError, tr("No editing marks defined!"));
05212         else if (!cCutter::Start(fileName, NULL, false))
05213            Skins.Message(mtError, tr("Can't start editing process!"));
05214         else
05215            Skins.Message(mtInfo, tr("Editing process started"));
05216         }
05217      else
05218         Skins.Message(mtError, tr("Editing process already active!"));
05219      ShowMode();
05220      }
05221 }
05222 
05223 void cReplayControl::EditTest(void)
05224 {
05225   int Current, Total;
05226   if (GetIndex(Current, Total)) {
05227      cMark *m = marks.Get(Current);
05228      if (!m)
05229         m = marks.GetNext(Current);
05230      if (m) {
05231         if ((m->Index() & 0x01) != 0 && !Setup.PlayJump)
05232            m = marks.Next(m);
05233         if (m) {
05234            Goto(m->Position() - SecondsToFrames(3, FramesPerSecond()));
05235            Play();
05236            }
05237         }
05238      }
05239 }
05240 
05241 cOsdObject *cReplayControl::GetInfo(void)
05242 {
05243   cRecording *Recording = Recordings.GetByName(cReplayControl::LastReplayed());
05244   if (Recording)
05245      return new cMenuRecording(Recording, false);
05246   return NULL;
05247 }
05248 
05249 eOSState cReplayControl::ProcessKey(eKeys Key)
05250 {
05251   if (!Active())
05252      return osEnd;
05253   if (Key == kNone)
05254      marks.Update();
05255   if (visible) {
05256      if (timeoutShow && time(NULL) > timeoutShow) {
05257         Hide();
05258         ShowMode();
05259         timeoutShow = 0;
05260         }
05261      else if (modeOnly)
05262         ShowMode();
05263      else
05264         shown = ShowProgress(!shown) || shown;
05265      }
05266   bool DisplayedFrames = displayFrames;
05267   displayFrames = false;
05268   if (timeSearchActive && Key != kNone) {
05269      TimeSearchProcess(Key);
05270      return osContinue;
05271      }
05272   bool DoShowMode = true;
05273   switch (int(Key)) {
05274     // Positioning:
05275     case kPlay:
05276     case kUp:      Play(); break;
05277     case kPause:
05278     case kDown:    Pause(); break;
05279     case kFastRew|k_Release:
05280     case kLeft|k_Release:
05281                    if (Setup.MultiSpeedMode) break;
05282     case kFastRew:
05283     case kLeft:    Backward(); break;
05284     case kFastFwd|k_Release:
05285     case kRight|k_Release:
05286                    if (Setup.MultiSpeedMode) break;
05287     case kFastFwd:
05288     case kRight:   Forward(); break;
05289     case kRed:     TimeSearch(); break;
05290     case kGreen|k_Repeat:
05291     case kGreen:   SkipSeconds(-60); break;
05292     case kYellow|k_Repeat:
05293     case kYellow:  SkipSeconds( 60); break;
05294     case k1|k_Repeat:
05295     case k1:       SkipSeconds(-20); break;
05296     case k3|k_Repeat:
05297     case k3:       SkipSeconds( 20); break;
05298     case kPrev|k_Repeat:
05299     case kPrev:    if (lastSkipTimeout.TimedOut()) {
05300                       lastSkipSeconds = REPLAYCONTROLSKIPSECONDS;
05301                       lastSkipKey = kPrev;
05302                    }
05303                    else if (RAWKEY(lastSkipKey) != kPrev && lastSkipSeconds > (2 * REPLAYCONTROLSKIPLIMIT)) {
05304                       lastSkipSeconds /= 2;
05305                       lastSkipKey = kNone;
05306                    }
05307                    lastSkipTimeout.Set(REPLAYCONTROLSKIPTIMEOUT);
05308                    SkipSeconds(-lastSkipSeconds); break;
05309     case kNext|k_Repeat:
05310     case kNext:    if (lastSkipTimeout.TimedOut()) {
05311                       lastSkipSeconds = REPLAYCONTROLSKIPSECONDS;
05312                       lastSkipKey = kNext;      
05313                    }
05314                    else if (RAWKEY(lastSkipKey) != kNext && lastSkipSeconds > (2 * REPLAYCONTROLSKIPLIMIT)) {
05315                       lastSkipSeconds /= 2;
05316                       lastSkipKey = kNone;
05317                    }
05318                    lastSkipTimeout.Set(REPLAYCONTROLSKIPTIMEOUT);
05319                    SkipSeconds(lastSkipSeconds); break;
05320     case kStop:
05321     case kBlue:    Hide();
05322                    Stop();
05323                    return osEnd;
05324     default: {
05325       DoShowMode = false;
05326       switch (int(Key)) {
05327         // Editing:
05328         case kMarkToggle:      MarkToggle(); break;
05329         case kMarkJumpBack|k_Repeat:
05330         case kMarkJumpBack:    MarkJump(false); break;
05331         case kMarkJumpForward|k_Repeat:
05332         case kMarkJumpForward: MarkJump(true); break;
05333         case kMarkMoveBack|k_Repeat:
05334         case kMarkMoveBack:    MarkMove(false); break;
05335         case kMarkMoveForward|k_Repeat:
05336         case kMarkMoveForward: MarkMove(true); break;
05337         case kEditCut:         EditCut(); break;
05338         case kEditTest:        EditTest(); break;
05339         default: {
05340           displayFrames = DisplayedFrames;
05341           switch (Key) {
05342             // Menu control:
05343             case kOk:      if (visible && !modeOnly) {
05344                               Hide();
05345                               DoShowMode = true;
05346                               }
05347                            else
05348                               Show();
05349                            break;
05350             case kBack:    if (Setup.DelTimeshiftRec) { 
05351                               cRecordControl* rc = cRecordControls::GetRecordControl(fileName);
05352                               return rc && rc->InstantId() ? osEnd : osRecordings;
05353                               }
05354                            return osRecordings;
05355             default:       return osUnknown;
05356             }
05357           }
05358         }
05359       }
05360     }
05361   if (DoShowMode)
05362      ShowMode();
05363   return osContinue;
05364 }