vdr
1.7.27
|
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(¶meter, "%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 }