Bug Summary

File:menu.c
Location:line 2040, column 9
Description:Potential memory leak

Annotated Source Code

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