/* * menu.c: The actual menu implementations * * See the main source file 'vdr.c' for copyright information and * how to reach the author. * * $Id: menu.c 4.4 2015/09/06 09:28:24 kls Exp $ */ #include "menu.h" #include #include #include #include #include #include #include "channels.h" #include "config.h" #include "cutter.h" #include "eitscan.h" #include "i18n.h" #include "interface.h" #include "plugin.h" #include "recording.h" #include "remote.h" #include "shutdown.h" #include "sourceparams.h" #include "sources.h" #include "status.h" #include "themes.h" #include "timers.h" #include "transfer.h" #include "videodir.h" #define MAXWAIT4EPGINFO 3 // seconds #define MODETIMEOUT 3 // seconds #define NEWTIMERLIMIT 120 // seconds until the start time of a new timer created from the Schedule menu, // within which it will go directly into the "Edit timer" menu to allow // further parameter settings #define DEFERTIMER 60 // seconds by which a timer is deferred in case of problems #define MAXRECORDCONTROLS (MAXDEVICES * MAXRECEIVERS) #define MAXINSTANTRECTIME (24 * 60 - 1) // 23:59 hours #define MAXWAITFORCAMMENU 10 // seconds to wait for the CAM menu to open #define CAMMENURETYTIMEOUT 3 // seconds after which opening the CAM menu is retried #define CAMRESPONSETIMEOUT 5 // seconds to wait for a response from a CAM #define MINFREEDISK 300 // minimum free disk space (in MB) required to start recording #define NODISKSPACEDELTA 300 // seconds between "Not enough disk space to start recording!" messages #define MAXCHNAMWIDTH 16 // maximum number of characters of channels' short names shown in schedules menus #define CHNUMWIDTH (numdigits(cChannels::MaxNumber()) + 1) #define CHNAMWIDTH (min(MAXCHNAMWIDTH, cChannels::MaxShortChannelNameLength() + 1)) // --- cMenuEditCaItem ------------------------------------------------------- class cMenuEditCaItem : public cMenuEditIntItem { protected: virtual void Set(void); public: cMenuEditCaItem(const char *Name, int *Value); eOSState ProcessKey(eKeys Key); }; cMenuEditCaItem::cMenuEditCaItem(const char *Name, int *Value) :cMenuEditIntItem(Name, Value, 0) { Set(); } void cMenuEditCaItem::Set(void) { if (*value == CA_FTA) SetValue(tr("Free To Air")); else if (*value >= CA_ENCRYPTED_MIN) SetValue(tr("encrypted")); else cMenuEditIntItem::Set(); } eOSState cMenuEditCaItem::ProcessKey(eKeys Key) { eOSState state = cMenuEditItem::ProcessKey(Key); if (state == osUnknown) { if (NORMALKEY(Key) == kLeft && *value >= CA_ENCRYPTED_MIN) *value = CA_FTA; else return cMenuEditIntItem::ProcessKey(Key); Set(); state = osContinue; } return state; } // --- cMenuEditSrcItem ------------------------------------------------------ class cMenuEditSrcItem : public cMenuEditIntItem { private: const cSource *source; protected: virtual void Set(void); public: cMenuEditSrcItem(const char *Name, int *Value); eOSState ProcessKey(eKeys Key); }; cMenuEditSrcItem::cMenuEditSrcItem(const char *Name, int *Value) :cMenuEditIntItem(Name, Value, 0) { source = Sources.Get(*Value); Set(); } void cMenuEditSrcItem::Set(void) { if (source) SetValue(cString::sprintf("%s - %s", *cSource::ToString(source->Code()), source->Description())); else cMenuEditIntItem::Set(); } eOSState cMenuEditSrcItem::ProcessKey(eKeys Key) { eOSState state = cMenuEditItem::ProcessKey(Key); if (state == osUnknown) { bool IsRepeat = Key & k_Repeat; Key = NORMALKEY(Key); if (Key == kLeft) { // TODO might want to increase the delta if repeated quickly? if (source) { if (source->Prev()) source = (cSource *)source->Prev(); else if (!IsRepeat) source = Sources.Last(); *value = source->Code(); } } else if (Key == kRight) { if (source) { if (source->Next()) source = (cSource *)source->Next(); else if (!IsRepeat) source = Sources.First(); } else source = Sources.First(); if (source) *value = source->Code(); } else return state; // we don't call cMenuEditIntItem::ProcessKey(Key) here since we don't accept numerical input Set(); state = osContinue; } return state; } // --- cMenuEditChannel ------------------------------------------------------ class cMenuEditChannel : public cOsdMenu { private: cStateKey *channelsStateKey; cChannel *channel; cChannel data; cSourceParam *sourceParam; char name[256]; void Setup(void); public: cMenuEditChannel(cStateKey *ChannelsStateKey, cChannel *Channel, bool New = false); cChannel *Channel(void) { return channel; } virtual eOSState ProcessKey(eKeys Key); }; cMenuEditChannel::cMenuEditChannel(cStateKey *ChannelsStateKey, cChannel *Channel, bool New) :cOsdMenu(tr("Edit channel"), 16) { SetMenuCategory(mcChannelEdit); channelsStateKey = ChannelsStateKey; channel = Channel; sourceParam = NULL; *name = 0; if (channel) { data = *channel; strn0cpy(name, data.name, sizeof(name)); if (New) { channel = NULL; // clear non-editable members: data.nid = 0; data.tid = 0; data.rid = 0; *data.shortName = 0; *data.provider = 0; *data.portalName = 0; } } Setup(); } void cMenuEditChannel::Setup(void) { int current = Current(); Clear(); // Parameters for all types of sources: Add(new cMenuEditStrItem( tr("Name"), name, sizeof(name))); Add(new cMenuEditSrcItem( tr("Source"), &data.source)); Add(new cMenuEditIntItem( tr("Frequency"), &data.frequency)); Add(new cMenuEditIntItem( tr("Vpid"), &data.vpid, 0, 0x1FFF)); Add(new cMenuEditIntItem( tr("Ppid"), &data.ppid, 0, 0x1FFF)); Add(new cMenuEditIntItem( tr("Apid1"), &data.apids[0], 0, 0x1FFF)); Add(new cMenuEditIntItem( tr("Apid2"), &data.apids[1], 0, 0x1FFF)); Add(new cMenuEditIntItem( tr("Dpid1"), &data.dpids[0], 0, 0x1FFF)); Add(new cMenuEditIntItem( tr("Dpid2"), &data.dpids[1], 0, 0x1FFF)); Add(new cMenuEditIntItem( tr("Spid1"), &data.spids[0], 0, 0x1FFF)); Add(new cMenuEditIntItem( tr("Spid2"), &data.spids[1], 0, 0x1FFF)); Add(new cMenuEditIntItem( tr("Tpid"), &data.tpid, 0, 0x1FFF)); Add(new cMenuEditCaItem( tr("CA"), &data.caids[0])); Add(new cMenuEditIntItem( tr("Sid"), &data.sid, 1, 0xFFFF)); Add(new cMenuEditIntItem( tr("Nid"), &data.nid, 0)); Add(new cMenuEditIntItem( tr("Tid"), &data.tid, 0)); /* XXX not yet used Add(new cMenuEditIntItem( tr("Rid"), &data.rid, 0)); XXX*/ // Parameters for specific types of sources: sourceParam = SourceParams.Get(**cSource::ToString(data.source)); if (sourceParam) { sourceParam->SetData(&data); cOsdItem *Item; while ((Item = sourceParam->GetOsdItem()) != NULL) Add(Item); } SetCurrent(Get(current)); Display(); } eOSState cMenuEditChannel::ProcessKey(eKeys Key) { int oldSource = data.source; eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { if (Key == kOk) { cChannels *Channels =cChannels::GetChannelsWrite(*channelsStateKey); bool Modified = false; if (sourceParam) sourceParam->GetData(&data); if (Channels->HasUniqueChannelID(&data, channel)) { data.name = strcpyrealloc(data.name, name); if (channel) { *channel = data; isyslog("edited channel %d %s", channel->Number(), *channel->ToText()); state = osBack; } else { channel = new cChannel; *channel = data; Channels->Add(channel); Channels->ReNumber(); isyslog("added channel %d %s", channel->Number(), *channel->ToText()); state = osUser1; } Channels->SetModifiedByUser(); Modified = true; } else { Skins.Message(mtError, tr("Channel settings are not unique!")); state = osContinue; } channelsStateKey->Remove(Modified); } } if (Key != kNone && (data.source & cSource::st_Mask) != (oldSource & cSource::st_Mask)) { LOCK_CHANNELS_WRITE; if (sourceParam) sourceParam->GetData(&data); Setup(); } return state; } // --- cMenuChannelItem ------------------------------------------------------ class cMenuChannelItem : public cOsdItem { public: enum eChannelSortMode { csmNumber, csmName, csmProvider }; private: static eChannelSortMode sortMode; const cChannel *channel; public: cMenuChannelItem(const cChannel *Channel); static void SetSortMode(eChannelSortMode SortMode) { sortMode = SortMode; } static void IncSortMode(void) { sortMode = eChannelSortMode((sortMode == csmProvider) ? csmNumber : sortMode + 1); } static eChannelSortMode SortMode(void) { return sortMode; } virtual int Compare(const cListObject &ListObject) const; virtual void Set(void); const cChannel *Channel(void) { return channel; } virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable); }; cMenuChannelItem::eChannelSortMode cMenuChannelItem::sortMode = csmNumber; cMenuChannelItem::cMenuChannelItem(const cChannel *Channel) { channel = Channel; if (channel->GroupSep()) SetSelectable(false); Set(); } int cMenuChannelItem::Compare(const cListObject &ListObject) const { cMenuChannelItem *p = (cMenuChannelItem *)&ListObject; int r = -1; if (sortMode == csmProvider) r = strcoll(channel->Provider(), p->channel->Provider()); if (sortMode == csmName || r == 0) r = strcoll(channel->Name(), p->channel->Name()); if (sortMode == csmNumber || r == 0) r = channel->Number() - p->channel->Number(); return r; } void cMenuChannelItem::Set(void) { cString buffer; if (!channel->GroupSep()) { if (sortMode == csmProvider) buffer = cString::sprintf("%d\t%s - %s", channel->Number(), channel->Provider(), channel->Name()); else buffer = cString::sprintf("%d\t%s", channel->Number(), channel->Name()); } else buffer = cString::sprintf("---\t%s ----------------------------------------------------------------", channel->Name()); SetText(buffer); } void cMenuChannelItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable) { if (!DisplayMenu->SetItemChannel(channel, Index, Current, Selectable, sortMode == csmProvider)) DisplayMenu->SetItem(Text(), Index, Current, Selectable); } // --- cMenuChannels --------------------------------------------------------- #define CHANNELNUMBERTIMEOUT 1000 //ms class cMenuChannels : public cOsdMenu { private: cStateKey channelsStateKey; int number; cTimeMs numberTimer; void Set(bool Force = false); cChannel *GetChannel(int Index); void Propagate(cChannels *Channels); protected: eOSState Number(eKeys Key); eOSState Switch(void); eOSState Edit(void); eOSState New(void); eOSState Delete(void); virtual void Move(int From, int To); public: cMenuChannels(void); ~cMenuChannels(); virtual eOSState ProcessKey(eKeys Key); }; cMenuChannels::cMenuChannels(void) :cOsdMenu(tr("Channels"), CHNUMWIDTH) { SetMenuCategory(mcChannel); number = 0; Set(); } cMenuChannels::~cMenuChannels() { } void cMenuChannels::Set(bool Force) { if (Force) channelsStateKey.Reset(); if (const cChannels *Channels = cChannels::GetChannelsRead(channelsStateKey)) { const cChannel *CurrentChannel = GetChannel(Current()); if (!CurrentChannel) CurrentChannel = Channels->GetByNumber(cDevice::CurrentChannel()); cMenuChannelItem *CurrentItem = NULL; Clear(); for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep() || cMenuChannelItem::SortMode() == cMenuChannelItem::csmNumber && *Channel->Name()) { cMenuChannelItem *Item = new cMenuChannelItem(Channel); Add(Item); if (Channel == CurrentChannel) CurrentItem = Item; } } SetMenuSortMode(cMenuChannelItem::SortMode() == cMenuChannelItem::csmName ? msmName : cMenuChannelItem::SortMode() == cMenuChannelItem::csmProvider ? msmProvider : msmNumber); if (cMenuChannelItem::SortMode() != cMenuChannelItem::csmNumber) Sort(); SetCurrent(CurrentItem); SetHelp(tr("Button$Edit"), tr("Button$New"), tr("Button$Delete"), tr("Button$Mark")); Display(); channelsStateKey.Remove(); } } cChannel *cMenuChannels::GetChannel(int Index) { cMenuChannelItem *p = (cMenuChannelItem *)Get(Index); return p ? (cChannel *)p->Channel() : NULL; } void cMenuChannels::Propagate(cChannels *Channels) { Channels->ReNumber(); for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) ci->Set(); Display(); Channels->SetModifiedByUser(); } eOSState cMenuChannels::Number(eKeys Key) { if (HasSubMenu()) return osContinue; if (numberTimer.TimedOut()) number = 0; if (!number && Key == k0) { cMenuChannelItem::IncSortMode(); Set(true); } else { LOCK_CHANNELS_READ; number = number * 10 + Key - k0; for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) { if (!ci->Channel()->GroupSep() && ci->Channel()->Number() == number) { SetCurrent(ci); Display(); break; } } numberTimer.Set(CHANNELNUMBERTIMEOUT); } return osContinue; } eOSState cMenuChannels::Switch(void) { if (HasSubMenu()) return osContinue; LOCK_CHANNELS_READ; cChannel *ch = GetChannel(Current()); if (ch) return cDevice::PrimaryDevice()->SwitchChannel(ch, true) ? osEnd : osContinue; return osEnd; } eOSState cMenuChannels::Edit(void) { if (HasSubMenu() || Count() == 0) return osContinue; LOCK_CHANNELS_READ; cChannel *ch = GetChannel(Current()); if (ch) return AddSubMenu(new cMenuEditChannel(&channelsStateKey, ch)); return osContinue; } eOSState cMenuChannels::New(void) { if (HasSubMenu()) return osContinue; LOCK_CHANNELS_READ; return AddSubMenu(new cMenuEditChannel(&channelsStateKey, GetChannel(Current()), true)); } eOSState cMenuChannels::Delete(void) { if (!HasSubMenu() && Count() > 0) { LOCK_TIMERS_READ; // must lock timers before channels! cChannels *Channels = cChannels::GetChannelsWrite(channelsStateKey); int Index = Current(); cChannel *Channel = GetChannel(Current()); if (!Channels->Contains(Channel)) { channelsStateKey.Remove(false); channelsStateKey.Reset(); // makes sure the menu is refreshed return osContinue; } bool Deleted = false; int CurrentChannelNr = cDevice::CurrentChannel(); cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr); int DeletedChannel = Channel->Number(); // Check if there is a timer using this channel: if (Timers->UsesChannel(Channel)) { Skins.Message(mtError, tr("Channel is being used by a timer!")); return osContinue; } if (Interface->Confirm(tr("Delete channel?"))) { if (CurrentChannel && Channel == CurrentChannel) { int n = Channels->GetNextNormal(CurrentChannel->Index()); if (n < 0) n = Channels->GetPrevNormal(CurrentChannel->Index()); CurrentChannel = Channels->Get(n); CurrentChannelNr = 0; // triggers channel switch below } Channels->Del(Channel); cOsdMenu::Del(Index); Propagate(Channels); Channels->SetModifiedByUser(); isyslog("channel %d deleted", DeletedChannel); Deleted = true; if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) Channels->SwitchTo(CurrentChannel->Number()); else cDevice::SetCurrentChannel(CurrentChannel); } } channelsStateKey.Remove(Deleted); } return osContinue; } void cMenuChannels::Move(int From, int To) { if (cChannels *Channels = cChannels::GetChannelsWrite(channelsStateKey)) { int CurrentChannelNr = cDevice::CurrentChannel(); cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr); cChannel *FromChannel = GetChannel(From); cChannel *ToChannel = GetChannel(To); if (FromChannel && ToChannel) { int FromNumber = FromChannel->Number(); int ToNumber = ToChannel->Number(); Channels->Move(FromChannel, ToChannel); cOsdMenu::Move(From, To); Propagate(Channels); Channels->SetModifiedByUser(); isyslog("channel %d moved to %d", FromNumber, ToNumber); if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) Channels->SwitchTo(CurrentChannel->Number()); else cDevice::SetCurrentChannel(CurrentChannel); } } channelsStateKey.Remove(); } } eOSState cMenuChannels::ProcessKey(eKeys Key) { if (!HasSubMenu()) Set(); // react on any changes to the channels list eOSState state = cOsdMenu::ProcessKey(Key); switch (state) { case osUser1: { if (cMenuEditChannel *MenuEditChannel = dynamic_cast(SubMenu())) { if (cChannel *Channel = MenuEditChannel->Channel()) { LOCK_CHANNELS_READ; Add(new cMenuChannelItem(Channel), true); return CloseSubMenu(); } } } break; default: if (state == osUnknown) { switch (Key) { case k0 ... k9: return Number(Key); case kOk: return Switch(); case kRed: return Edit(); case kGreen: return New(); case kYellow: return Delete(); case kBlue: if (!HasSubMenu()) Mark(); break; default: break; } } } return state; } // --- cMenuText ------------------------------------------------------------- cMenuText::cMenuText(const char *Title, const char *Text, eDvbFont Font) :cOsdMenu(Title) { SetMenuCategory(mcText); text = NULL; font = Font; SetText(Text); } cMenuText::~cMenuText() { free(text); } void cMenuText::SetText(const char *Text) { free(text); text = Text ? strdup(Text) : NULL; } void cMenuText::Display(void) { cOsdMenu::Display(); DisplayMenu()->SetText(text, font == fontFix); //XXX define control character in text to choose the font??? if (text) cStatus::MsgOsdTextItem(text); } eOSState cMenuText::ProcessKey(eKeys Key) { switch (int(Key)) { case kUp|k_Repeat: case kUp: case kDown|k_Repeat: case kDown: case kLeft|k_Repeat: case kLeft: case kRight|k_Repeat: case kRight: DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight); cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft); return osContinue; default: break; } eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kOk: return osBack; default: state = osContinue; } } return state; } // --- cMenuFolderItem ------------------------------------------------------- class cMenuFolderItem : public cOsdItem { private: cNestedItem *folder; public: cMenuFolderItem(cNestedItem *Folder); cNestedItem *Folder(void) { return folder; } }; cMenuFolderItem::cMenuFolderItem(cNestedItem *Folder) :cOsdItem(Folder->Text()) { folder = Folder; if (folder->SubItems()) SetText(cString::sprintf("%s...", folder->Text())); } // --- cMenuEditFolder ------------------------------------------------------- class cMenuEditFolder : public cOsdMenu { private: cList *list; cNestedItem *folder; char name[PATH_MAX]; int subFolder; eOSState Confirm(void); public: cMenuEditFolder(const char *Dir, cList *List, cNestedItem *Folder = NULL); cString GetFolder(void); virtual eOSState ProcessKey(eKeys Key); }; cMenuEditFolder::cMenuEditFolder(const char *Dir, cList *List, cNestedItem *Folder) :cOsdMenu(Folder ? tr("Edit folder") : tr("New folder"), 12) { SetMenuCategory(mcFolder); list = List; folder = Folder; if (folder) { strn0cpy(name, folder->Text(), sizeof(name)); subFolder = folder->SubItems() != NULL; } else { *name = 0; subFolder = 0; cRemote::Put(kRight, true); // go right into string editing mode } if (!isempty(Dir)) { cOsdItem *DirItem = new cOsdItem(Dir); DirItem->SetSelectable(false); Add(DirItem); } Add(new cMenuEditStrItem( tr("Name"), name, sizeof(name))); Add(new cMenuEditBoolItem(tr("Sub folder"), &subFolder)); } cString cMenuEditFolder::GetFolder(void) { return folder ? folder->Text() : ""; } eOSState cMenuEditFolder::Confirm(void) { if (!folder || strcmp(folder->Text(), name) != 0) { // each name may occur only once in a folder list for (cNestedItem *Folder = list->First(); Folder; Folder = list->Next(Folder)) { if (strcmp(Folder->Text(), name) == 0) { Skins.Message(mtError, tr("Folder name already exists!")); return osContinue; } } char *p = strpbrk(name, "\\{}#~"); // FOLDERDELIMCHAR if (p) { Skins.Message(mtError, cString::sprintf(tr("Folder name must not contain '%c'!"), *p)); return osContinue; } } if (folder) { folder->SetText(name); folder->SetSubItems(subFolder); } else list->Add(folder = new cNestedItem(name, subFolder)); return osEnd; } eOSState cMenuEditFolder::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kOk: return Confirm(); case kRed: case kGreen: case kYellow: case kBlue: return osContinue; default: break; } } return state; } // --- cMenuFolder ----------------------------------------------------------- cMenuFolder::cMenuFolder(const char *Title, cNestedItemList *NestedItemList, const char *Path) :cOsdMenu(Title) { SetMenuCategory(mcFolder); list = nestedItemList = NestedItemList; firstFolder = NULL; editing = false; helpKeys = -1; Set(); DescendPath(Path); Display(); SetHelpKeys(); } cMenuFolder::cMenuFolder(const char *Title, cList *List, cNestedItemList *NestedItemList, const char *Dir, const char *Path) :cOsdMenu(Title) { SetMenuCategory(mcFolder); list = List; nestedItemList = NestedItemList; dir = Dir; firstFolder = NULL; editing = false; helpKeys = -1; Set(); DescendPath(Path); Display(); SetHelpKeys(); } void cMenuFolder::SetHelpKeys(void) { if (HasSubMenu()) return; int NewHelpKeys = 0; if (firstFolder) { if (cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current())) { if (Folder->Folder()->SubItems()) NewHelpKeys = 1; } } if (NewHelpKeys != helpKeys) { helpKeys = NewHelpKeys; SetHelp(NewHelpKeys > 0 ? tr("Button$Open") : NULL, tr("Button$New"), firstFolder ? tr("Button$Delete") : NULL, firstFolder ? tr("Button$Edit") : NULL); } } #define FOLDERDELIMCHARSUBST 0x01 static void AddRecordingFolders(const cRecordings *Recordings, cList *List, char *Path) { if (Path) { char *p = strchr(Path, FOLDERDELIMCHARSUBST); if (p) *p++ = 0; cNestedItem *Folder; for (Folder = List->First(); Folder; Folder = List->Next(Folder)) { if (strcmp(Path, Folder->Text()) == 0) break; } if (!Folder) List->Add(Folder = new cNestedItem(Path)); if (p) { Folder->SetSubItems(true); AddRecordingFolders(Recordings, Folder->SubItems(), p); } } else { cStringList Dirs; for (const cRecording *Recording = Recordings->First(); Recording; Recording = Recordings->Next(Recording)) { cString Folder = Recording->Folder(); strreplace((char *)*Folder, FOLDERDELIMCHAR, FOLDERDELIMCHARSUBST); // makes sure parent folders come before subfolders if (Dirs.Find(Folder) < 0) Dirs.Append(strdup(Folder)); } Dirs.Sort(); for (int i = 0; i < Dirs.Size(); i++) { if (char *s = Dirs[i]) AddRecordingFolders(Recordings, &Folders, s); } } } void cMenuFolder::Set(const char *CurrentFolder) { static cStateKey RecordingsStateKey; if (list == &Folders) { if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(RecordingsStateKey)) { AddRecordingFolders(Recordings, &Folders, NULL); RecordingsStateKey.Remove(); } } firstFolder = NULL; Clear(); if (!isempty(dir)) { cOsdItem *DirItem = new cOsdItem(dir); DirItem->SetSelectable(false); Add(DirItem); } list->Sort(); for (cNestedItem *Folder = list->First(); Folder; Folder = list->Next(Folder)) { cOsdItem *FolderItem = new cMenuFolderItem(Folder); Add(FolderItem, CurrentFolder ? strcmp(Folder->Text(), CurrentFolder) == 0 : false); if (!firstFolder) firstFolder = FolderItem; } } void cMenuFolder::DescendPath(const char *Path) { if (Path) { const char *p = strchr(Path, FOLDERDELIMCHAR); if (p) { for (cMenuFolderItem *Folder = (cMenuFolderItem *)firstFolder; Folder; Folder = (cMenuFolderItem *)Next(Folder)) { if (strncmp(Folder->Folder()->Text(), Path, p - Path) == 0) { SetCurrent(Folder); if (Folder->Folder()->SubItems() && strchr(p + 1, FOLDERDELIMCHAR)) 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)); break; } } } } } eOSState cMenuFolder::Select(bool Open) { if (firstFolder) { cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current()); if (Folder) { if (Open && Folder->Folder()->SubItems()) return AddSubMenu(new cMenuFolder(Title(), Folder->Folder()->SubItems(), nestedItemList, !isempty(dir) ? *cString::sprintf("%s%c%s", *dir, FOLDERDELIMCHAR, Folder->Folder()->Text()) : Folder->Folder()->Text())); else return osEnd; } } return osContinue; } eOSState cMenuFolder::New(void) { editing = true; return AddSubMenu(new cMenuEditFolder(dir, list)); } eOSState cMenuFolder::Delete(void) { if (!HasSubMenu() && firstFolder) { cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current()); if (Folder && Interface->Confirm(Folder->Folder()->SubItems() ? tr("Delete folder and all sub folders?") : tr("Delete folder?"))) { list->Del(Folder->Folder()); Del(Folder->Index()); firstFolder = Get(isempty(dir) ? 0 : 1); Display(); SetHelpKeys(); nestedItemList->Save(); } } return osContinue; } eOSState cMenuFolder::Edit(void) { if (!HasSubMenu() && firstFolder) { cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current()); if (Folder) { editing = true; return AddSubMenu(new cMenuEditFolder(dir, list, Folder->Folder())); } } return osContinue; } eOSState cMenuFolder::SetFolder(void) { if (cMenuEditFolder *mef = dynamic_cast(SubMenu())) { Set(mef->GetFolder()); SetHelpKeys(); Display(); nestedItemList->Save(); } return CloseSubMenu(); } cString cMenuFolder::GetFolder(void) { if (firstFolder) { cMenuFolderItem *Folder = (cMenuFolderItem *)Get(Current()); if (Folder) { if (cMenuFolder *mf = dynamic_cast(SubMenu())) return cString::sprintf("%s%c%s", Folder->Folder()->Text(), FOLDERDELIMCHAR, *mf->GetFolder()); return Folder->Folder()->Text(); } } return ""; } eOSState cMenuFolder::ProcessKey(eKeys Key) { if (!HasSubMenu()) editing = false; eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kOk: return Select(false); case kRed: return Select(true); case kGreen: return New(); case kYellow: return Delete(); case kBlue: return Edit(); default: state = osContinue; } } else if (state == osEnd && HasSubMenu() && editing) state = SetFolder(); SetHelpKeys(); return state; } // --- cMenuEditTimer -------------------------------------------------------- const cTimer *cMenuEditTimer::addedTimer = NULL; cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New) :cOsdMenu(tr("Edit timer"), 12) { SetMenuCategory(mcTimerEdit); addedTimer = NULL; file = NULL; day = firstday = NULL; timer = Timer; addIfConfirmed = New; if (timer) { data = *timer; if (New) data.SetFlags(tfActive); channel = data.Channel()->Number(); Add(new cMenuEditBitItem( tr("Active"), &data.flags, tfActive)); Add(new cMenuEditChanItem(tr("Channel"), &channel)); Add(day = new cMenuEditDateItem(tr("Day"), &data.day, &data.weekdays)); Add(new cMenuEditTimeItem(tr("Start"), &data.start)); Add(new cMenuEditTimeItem(tr("Stop"), &data.stop)); Add(new cMenuEditBitItem( tr("VPS"), &data.flags, tfVps)); Add(new cMenuEditIntItem( tr("Priority"), &data.priority, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Lifetime"), &data.lifetime, 0, MAXLIFETIME)); Add(file = new cMenuEditStrItem( tr("File"), data.file, sizeof(data.file))); SetFirstDayItem(); } SetHelpKeys(); } cMenuEditTimer::~cMenuEditTimer() { if (timer && addIfConfirmed) delete timer; // apparently it wasn't confirmed } const cTimer *cMenuEditTimer::AddedTimer(void) { const cTimer *Timer = addedTimer; addedTimer = NULL; return Timer; } void cMenuEditTimer::SetHelpKeys(void) { SetHelp(tr("Button$Folder"), data.weekdays ? tr("Button$Single") : tr("Button$Repeating")); } void cMenuEditTimer::SetFirstDayItem(void) { if (!firstday && !data.IsSingleEvent()) { Add(firstday = new cMenuEditDateItem(tr("First day"), &data.day)); Display(); } else if (firstday && data.IsSingleEvent()) { Del(firstday->Index()); firstday = NULL; Display(); } } eOSState cMenuEditTimer::SetFolder(void) { if (cMenuFolder *mf = dynamic_cast(SubMenu())) { cString Folder = mf->GetFolder(); char *p = strrchr(data.file, FOLDERDELIMCHAR); if (p) p++; else p = data.file; if (!isempty(*Folder)) strn0cpy(data.file, cString::sprintf("%s%c%s", *Folder, FOLDERDELIMCHAR, p), sizeof(data.file)); else if (p != data.file) memmove(data.file, p, strlen(p) + 1); SetCurrent(file); Display(); } return CloseSubMenu(); } eOSState cMenuEditTimer::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kOk: if (timer) { LOCK_TIMERS_WRITE; if (!addIfConfirmed && !Timers->Contains(timer)) { Skins.Message(mtWarning, tr("Timer has been deleted!")); break; } LOCK_CHANNELS_READ; if (const cChannel *Channel = Channels->GetByNumber(channel)) data.channel = Channel; else { Skins.Message(mtError, tr("*** Invalid Channel ***")); break; } if (!*data.file) strcpy(data.file, data.Channel()->ShortName(true)); *timer = data; if (addIfConfirmed) { Timers->Add(timer); addedTimer = timer; } isyslog("timer %s %s (%s)", *timer->ToDescr(), addIfConfirmed ? "added" : "modified", timer->HasFlags(tfActive) ? "active" : "inactive"); LOCK_SCHEDULES_READ; timer->SetEventFromSchedule(Schedules); timer->Matches(); addIfConfirmed = false; } return osBack; case kRed: return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, data.file)); case kGreen: if (day) { day->ToggleRepeating(); SetCurrent(day); SetFirstDayItem(); SetHelpKeys(); Display(); } return osContinue; case kYellow: case kBlue: return osContinue; default: break; } } else if (state == osEnd && HasSubMenu()) state = SetFolder(); if (Key != kNone) SetFirstDayItem(); return state; } // --- cMenuTimerItem -------------------------------------------------------- class cMenuTimerItem : public cOsdItem { private: const cTimer *timer; public: cMenuTimerItem(const cTimer *Timer); virtual int Compare(const cListObject &ListObject) const; virtual void Set(void); const cTimer *Timer(void) { return timer; } virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable); }; cMenuTimerItem::cMenuTimerItem(const cTimer *Timer) { timer = Timer; Set(); } int cMenuTimerItem::Compare(const cListObject &ListObject) const { return timer->Compare(*((cMenuTimerItem *)&ListObject)->timer); } void cMenuTimerItem::Set(void) { cString day, name(""); if (timer->WeekDays()) day = timer->PrintDay(0, timer->WeekDays(), false); else if (timer->Day() - time(NULL) < 28 * SECSINDAY) { day = itoa(timer->GetMDay(timer->Day())); name = WeekDayName(timer->Day()); } else { struct tm tm_r; time_t Day = timer->Day(); localtime_r(&Day, &tm_r); char buffer[16]; strftime(buffer, sizeof(buffer), "%Y%m%d", &tm_r); day = buffer; } const char *File = Setup.FoldersInTimerMenu ? NULL : strrchr(timer->File(), FOLDERDELIMCHAR); if (File && strcmp(File + 1, TIMERMACRO_TITLE) && strcmp(File + 1, TIMERMACRO_EPISODE)) File++; else File = timer->File(); SetText(cString::sprintf("%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s%s", !(timer->HasFlags(tfActive)) ? ' ' : timer->FirstDay() ? '!' : timer->Recording() ? '#' : '>', timer->Channel()->Number(), *name, *name && **name ? " " : "", *day, timer->Start() / 100, timer->Start() % 100, timer->Stop() / 100, timer->Stop() % 100, timer->Remote() ? *cString::sprintf("@%s: ", timer->Remote()) : "", File)); } void cMenuTimerItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable) { if (!DisplayMenu->SetItemTimer(timer, Index, Current, Selectable)) DisplayMenu->SetItem(Text(), Index, Current, Selectable); } // --- cMenuTimers ----------------------------------------------------------- class cMenuTimers : public cOsdMenu { private: cStateKey timersStateKey; int helpKeys; void Set(void); eOSState Edit(void); eOSState New(void); eOSState Delete(void); eOSState OnOff(void); eOSState Info(void); cTimer *GetTimer(void); void SetHelpKeys(void); public: cMenuTimers(void); virtual ~cMenuTimers(); virtual eOSState ProcessKey(eKeys Key); }; cMenuTimers::cMenuTimers(void) :cOsdMenu(tr("Timers"), 2, CHNUMWIDTH, 10, 6, 6) { SetMenuCategory(mcTimer); helpKeys = -1; cMenuEditTimer::AddedTimer(); // to clear any leftovers Set(); } cMenuTimers::~cMenuTimers() { } void cMenuTimers::Set(void) { if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) { const cTimer *CurrentTimer = GetTimer(); cMenuTimerItem *CurrentItem = NULL; Clear(); for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) { cMenuTimerItem *Item = new cMenuTimerItem(Timer); Add(Item); if (Timer == CurrentTimer) CurrentItem = Item; } Sort(); SetCurrent(CurrentItem ? CurrentItem : First()); SetHelpKeys(); Display(); timersStateKey.Remove(); } } cTimer *cMenuTimers::GetTimer(void) { cMenuTimerItem *item = (cMenuTimerItem *)Get(Current()); return item ? (cTimer *)item->Timer() : NULL; } void cMenuTimers::SetHelpKeys(void) { int NewHelpKeys = 0; if (const cTimer *Timer = GetTimer()) { if (Timer->Event()) NewHelpKeys = 2; else NewHelpKeys = 1; } if (NewHelpKeys != helpKeys) { helpKeys = NewHelpKeys; SetHelp(helpKeys > 0 ? tr("Button$On/Off") : NULL, tr("Button$New"), helpKeys > 0 ? tr("Button$Delete") : NULL, helpKeys == 2 ? tr("Button$Info") : NULL); } } eOSState cMenuTimers::OnOff(void) { if (HasSubMenu()) return osContinue; cTimers::GetTimersWrite(timersStateKey); cTimer *Timer = GetTimer(); if (Timer) { Timer->OnOff(); LOCK_SCHEDULES_READ; Timer->SetEventFromSchedule(Schedules); RefreshCurrent(); DisplayCurrent(true); if (Timer->FirstDay()) isyslog("timer %s first day set to %s", *Timer->ToDescr(), *Timer->PrintFirstDay()); else isyslog("timer %s %sactivated", *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "" : "de"); } timersStateKey.Remove(Timer != NULL); return osContinue; } eOSState cMenuTimers::Edit(void) { if (HasSubMenu() || Count() == 0) return osContinue; isyslog("editing timer %s", *GetTimer()->ToDescr()); return AddSubMenu(new cMenuEditTimer(GetTimer())); } eOSState cMenuTimers::New(void) { if (HasSubMenu()) return osContinue; return AddSubMenu(new cMenuEditTimer(new cTimer, true)); } eOSState cMenuTimers::Delete(void) { cTimers *Timers = cTimers::GetTimersWrite(timersStateKey); // Check if this timer is active: cTimer *Timer = GetTimer(); if (Timer) { if (Interface->Confirm(tr("Delete timer?"))) { if (Timer->Recording()) { if (Interface->Confirm(tr("Timer still recording - really delete?"))) { Timer->Skip(); cRecordControls::Process(Timers, time(NULL)); } else Timer = NULL; } if (Timer) { isyslog("deleting timer %s", *Timer->ToDescr()); Timers->Del(Timer); cOsdMenu::Del(Current()); Display(); } } } timersStateKey.Remove(Timer != NULL); return osContinue; } eOSState cMenuTimers::Info(void) { if (HasSubMenu() || Count() == 0) return osContinue; LOCK_TIMERS_READ; LOCK_CHANNELS_READ; cTimer *Timer = GetTimer(); if (Timer && Timer->Event()) return AddSubMenu(new cMenuEvent(Timers, Channels, Timer->Event())); return osContinue; } eOSState cMenuTimers::ProcessKey(eKeys Key) { if (!HasSubMenu()) Set(); eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kOk: return Edit(); case kRed: state = OnOff(); break; // must go through SetHelpKeys()! case kGreen: return New(); case kYellow: state = Delete(); break; case kInfo: case kBlue: return Info(); break; default: break; } } if (const cTimer *Timer = cMenuEditTimer::AddedTimer()) { // a newly created timer was confirmed with Ok and the proper item needs to be added: LOCK_TIMERS_READ; Add(new cMenuTimerItem(Timer), true); Display(); } if (Key != kNone) SetHelpKeys(); return state; } // --- cMenuEvent ------------------------------------------------------------ cMenuEvent::cMenuEvent(const cTimers *Timers, const cChannels *Channels, const cEvent *Event, bool CanSwitch, bool Buttons) :cOsdMenu(tr("Event")) { SetMenuCategory(mcEvent); event = Event; if (event) { if (const cChannel *Channel = Channels->GetByChannelID(event->ChannelID(), true)) { SetTitle(Channel->Name()); if (Buttons) { eTimerMatch TimerMatch = tmNone; Timers->GetMatch(event, &TimerMatch); SetHelp(TimerMatch == tmFull ? tr("Button$Timer") : tr("Button$Record"), NULL, NULL, CanSwitch ? tr("Button$Switch") : NULL); } } } } void cMenuEvent::Display(void) { cOsdMenu::Display(); DisplayMenu()->SetEvent(event); if (event->Description()) cStatus::MsgOsdTextItem(event->Description()); } eOSState cMenuEvent::ProcessKey(eKeys Key) { switch (int(Key)) { case kUp|k_Repeat: case kUp: case kDown|k_Repeat: case kDown: case kLeft|k_Repeat: case kLeft: case kRight|k_Repeat: case kRight: DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight); cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft); return osContinue; case kInfo: return osBack; default: break; } eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kGreen: case kYellow: return osContinue; case kOk: return osBack; default: break; } } return state; } // --- cMenuScheduleItem ----------------------------------------------------- class cMenuScheduleItem : public cOsdItem { public: enum eScheduleSortMode { ssmAllThis, ssmThisThis, ssmThisAll, ssmAllAll }; // "which event(s) on which channel(s)" private: static eScheduleSortMode sortMode; public: const cEvent *event; const cChannel *channel; bool withDate; eTimerMatch timerMatch; cMenuScheduleItem(const cTimers *Timers, const cEvent *Event, const cChannel *Channel = NULL, bool WithDate = false); static void SetSortMode(eScheduleSortMode SortMode) { sortMode = SortMode; } static void IncSortMode(void) { sortMode = eScheduleSortMode((sortMode == ssmAllAll) ? ssmAllThis : sortMode + 1); } static eScheduleSortMode SortMode(void) { return sortMode; } virtual int Compare(const cListObject &ListObject) const; bool Update(const cTimers *Timers, bool Force = false); virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable); }; cMenuScheduleItem::eScheduleSortMode cMenuScheduleItem::sortMode = ssmAllThis; cMenuScheduleItem::cMenuScheduleItem(const cTimers *Timers, const cEvent *Event, const cChannel *Channel, bool WithDate) { event = Event; channel = Channel; withDate = WithDate; timerMatch = tmNone; Update(Timers, true); } int cMenuScheduleItem::Compare(const cListObject &ListObject) const { cMenuScheduleItem *p = (cMenuScheduleItem *)&ListObject; int r = -1; if (sortMode != ssmAllThis) r = strcoll(event->Title(), p->event->Title()); if (sortMode == ssmAllThis || r == 0) r = event->StartTime() - p->event->StartTime(); return r; } static const char *TimerMatchChars = " tT"; bool cMenuScheduleItem::Update(const cTimers *Timers, bool Force) { eTimerMatch OldTimerMatch = timerMatch; Timers->GetMatch(event, &timerMatch); if (Force || timerMatch != OldTimerMatch) { cString buffer; char t = TimerMatchChars[timerMatch]; char v = event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' '; char r = event->SeenWithin(30) && event->IsRunning() ? '*' : ' '; const char *csn = channel ? channel->ShortName(true) : NULL; cString eds = event->GetDateString(); if (channel && withDate) 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()); else if (channel) 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()); else buffer = cString::sprintf("%.*s\t%s\t%c%c%c\t%s", Utf8SymChars(eds, 6), *eds, *event->GetTimeString(), t, v, r, event->Title()); SetText(buffer); return true; } return false; } void cMenuScheduleItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable) { if (!DisplayMenu->SetItemEvent(event, Index, Current, Selectable, channel, withDate, timerMatch)) DisplayMenu->SetItem(Text(), Index, Current, Selectable); } // --- cMenuWhatsOn ---------------------------------------------------------- class cMenuWhatsOn : public cOsdMenu { private: bool now; bool canSwitch; int helpKeys; cStateKey timersStateKey; eOSState Record(void); eOSState Switch(void); static int currentChannel; static const cEvent *scheduleEvent; bool Update(void); void SetHelpKeys(void); public: cMenuWhatsOn(const cTimers *Timers, const cChannels *Channels, const cSchedules *Schedules, bool Now, int CurrentChannelNr); static int CurrentChannel(void) { return currentChannel; } static void SetCurrentChannel(int ChannelNr) { currentChannel = ChannelNr; } static const cEvent *ScheduleEvent(void); virtual eOSState ProcessKey(eKeys Key); }; int cMenuWhatsOn::currentChannel = 0; const cEvent *cMenuWhatsOn::scheduleEvent = NULL; cMenuWhatsOn::cMenuWhatsOn(const cTimers *Timers, const cChannels *Channels, const cSchedules *Schedules, bool Now, int CurrentChannelNr) :cOsdMenu(Now ? tr("What's on now?") : tr("What's on next?"), CHNUMWIDTH, CHNAMWIDTH, 6, 4) { SetMenuCategory(Now ? mcScheduleNow : mcScheduleNext); now = Now; canSwitch = false; helpKeys = 0; for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { if (!Channel->GroupSep()) { if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { if (const cEvent *Event = Now ? Schedule->GetPresentEvent() : Schedule->GetFollowingEvent()) Add(new cMenuScheduleItem(Timers, Event, Channel), Channel->Number() == CurrentChannelNr); } } } currentChannel = CurrentChannelNr; Display(); SetHelpKeys(); } bool cMenuWhatsOn::Update(void) { bool result = false; if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) { for (cOsdItem *item = First(); item; item = Next(item)) { if (((cMenuScheduleItem *)item)->Update(Timers)) result = true; } timersStateKey.Remove(); } return result; } void cMenuWhatsOn::SetHelpKeys(void) { cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current()); canSwitch = false; int NewHelpKeys = 0; if (item) { if (item->timerMatch == tmFull) NewHelpKeys |= 0x02; // "Timer" else NewHelpKeys |= 0x01; // "Record" if (now) NewHelpKeys |= 0x04; // "Next" else NewHelpKeys |= 0x08; // "Now" LOCK_CHANNELS_READ; if (const cChannel *Channel = Channels->GetByChannelID(item->event->ChannelID(), true)) { if (Channel->Number() != cDevice::CurrentChannel()) { NewHelpKeys |= 0x10; // "Switch" canSwitch = true; } } } if (NewHelpKeys != helpKeys) { const char *Red[] = { NULL, tr("Button$Record"), tr("Button$Timer") }; SetHelp(Red[NewHelpKeys & 0x03], now ? tr("Button$Next") : tr("Button$Now"), tr("Button$Schedule"), canSwitch ? tr("Button$Switch") : NULL); helpKeys = NewHelpKeys; } } const cEvent *cMenuWhatsOn::ScheduleEvent(void) { const cEvent *ei = scheduleEvent; scheduleEvent = NULL; return ei; } eOSState cMenuWhatsOn::Switch(void) { cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current()); if (item) { LOCK_CHANNELS_READ; const cChannel *Channel = Channels->GetByChannelID(item->event->ChannelID(), true); if (Channel) { if (!cDevice::PrimaryDevice()->SwitchChannel(Channel, true)) Channel = NULL; } if (Channel) return osEnd; } Skins.Message(mtError, tr("Can't switch channel!")); return osContinue; } eOSState cMenuWhatsOn::Record(void) { if (cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current())) { { LOCK_TIMERS_WRITE; LOCK_SCHEDULES_READ; Timers->SetExplicitModify(); if (item->timerMatch == tmFull) { if (cTimer *Timer = Timers->GetMatch(item->event)) return AddSubMenu(new cMenuEditTimer(Timer)); } cTimer *Timer = new cTimer(item->event); if (cTimer *t = Timers->GetTimer(Timer)) { delete Timer; Timer = t; return AddSubMenu(new cMenuEditTimer(Timer)); } if (Timer->Matches(0, false, NEWTIMERLIMIT)) return AddSubMenu(new cMenuEditTimer(Timer, true)); Timers->Add(Timer); Timers->SetModified(); isyslog("timer %s added (active)", *Timer->ToDescr()); } if (HasSubMenu()) CloseSubMenu(); if (Update()) Display(); SetHelpKeys(); } return osContinue; } eOSState cMenuWhatsOn::ProcessKey(eKeys Key) { bool HadSubMenu = HasSubMenu(); eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kRecord: case kRed: return Record(); case kYellow: state = osBack; // continue with kGreen case kGreen: { cMenuScheduleItem *mi = (cMenuScheduleItem *)Get(Current()); if (mi) { scheduleEvent = mi->event; currentChannel = mi->channel->Number(); } } break; case kBlue: if (canSwitch) return Switch(); break; case kInfo: case kOk: if (Count()) { LOCK_TIMERS_READ; LOCK_CHANNELS_READ; return AddSubMenu(new cMenuEvent(Timers, Channels, ((cMenuScheduleItem *)Get(Current()))->event, canSwitch, true)); } break; default: break; } } else if (!HasSubMenu()) { if (HadSubMenu && Update()) Display(); if (Key != kNone) SetHelpKeys(); } return state; } // --- cMenuSchedule --------------------------------------------------------- class cMenuSchedule : public cOsdMenu { private: cStateKey timersStateKey; cStateKey schedulesStateKey; int scheduleState; bool now, next; bool canSwitch; int helpKeys; void Set(const cChannel *Channel = NULL, bool Force = false); eOSState Number(void); eOSState Record(void); eOSState Switch(void); bool PrepareScheduleAllThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel); bool PrepareScheduleThisThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel); bool PrepareScheduleThisAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel); bool PrepareScheduleAllAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel); bool Update(void); void SetHelpKeys(void); public: cMenuSchedule(void); virtual ~cMenuSchedule(); virtual eOSState ProcessKey(eKeys Key); }; cMenuSchedule::cMenuSchedule(void) :cOsdMenu("") { SetMenuCategory(mcSchedule); scheduleState = -1; now = next = false; canSwitch = false; helpKeys = 0; cMenuScheduleItem::SetSortMode(cMenuScheduleItem::ssmAllThis); cMenuWhatsOn::SetCurrentChannel(cDevice::CurrentChannel()); Set(NULL, true); } cMenuSchedule::~cMenuSchedule() { cMenuWhatsOn::ScheduleEvent(); // makes sure any posted data is cleared } void cMenuSchedule::Set(const cChannel *Channel, bool Force) { if (Force) { schedulesStateKey.Reset(); scheduleState = -1; } LOCK_TIMERS_READ; LOCK_CHANNELS_READ; if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(schedulesStateKey)) { cMenuScheduleItem *CurrentItem = (cMenuScheduleItem *)Get(Current()); const cEvent *Event = NULL; if (!Channel) { if (CurrentItem) { Event = CurrentItem->event; Channel = Channels->GetByChannelID(Event->ChannelID(), true); } else Channel = Channels->GetByNumber(cDevice::CurrentChannel()); } bool Refresh = false; switch (cMenuScheduleItem::SortMode()) { case cMenuScheduleItem::ssmAllThis: Refresh = PrepareScheduleAllThis(Timers, Schedules, Event, Channel); break; case cMenuScheduleItem::ssmThisThis: Refresh = PrepareScheduleThisThis(Timers, Schedules, Event, Channel); break; case cMenuScheduleItem::ssmThisAll: Refresh = Force && PrepareScheduleThisAll(Timers, Schedules, Event, Channel); break; case cMenuScheduleItem::ssmAllAll: Refresh = Force && PrepareScheduleAllAll(Timers, Schedules, Event, Channel); break; default: esyslog("ERROR: unknown SortMode %d (%s %d)", cMenuScheduleItem::SortMode(), __FUNCTION__, __LINE__); } if (Refresh) { CurrentItem = (cMenuScheduleItem *)Get(Current()); Sort(); SetCurrent(CurrentItem); SetHelpKeys(); Display(); } schedulesStateKey.Remove(); } } bool cMenuSchedule::PrepareScheduleAllThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel) { if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { if (Schedule->Modified(scheduleState)) { Clear(); SetCols(7, 6, 4); SetTitle(cString::sprintf(tr("Schedule - %s"), Channel->Name())); const cEvent *PresentEvent = Event ? Event : Schedule->GetPresentEvent(); time_t now = time(NULL) - Setup.EPGLinger * 60; for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) { if (ev->EndTime() > now || ev == PresentEvent) Add(new cMenuScheduleItem(Timers, ev), ev == PresentEvent); } return true; } } return false; } bool cMenuSchedule::PrepareScheduleThisThis(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel) { if (Event) { if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { if (Schedule->Modified(scheduleState)) { Clear(); SetCols(7, 6, 4); SetTitle(cString::sprintf(tr("This event - %s"), Channel->Name())); time_t now = time(NULL) - Setup.EPGLinger * 60; for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) { if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title())) Add(new cMenuScheduleItem(Timers, ev), ev == Event); } return true; } } } return false; } bool cMenuSchedule::PrepareScheduleThisAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel) { Clear(); SetCols(CHNUMWIDTH, CHNAMWIDTH, 7, 6, 4); SetTitle(tr("This event - all channels")); if (Event) { LOCK_CHANNELS_READ; for (const cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch)) { if (const cSchedule *Schedule = Schedules->GetSchedule(ch)) { time_t now = time(NULL) - Setup.EPGLinger * 60; for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) { if ((ev->EndTime() > now || ev == Event) && !strcmp(ev->Title(), Event->Title())) Add(new cMenuScheduleItem(Timers, ev, ch, true), ev == Event && ch == Channel); } } } } return true; } bool cMenuSchedule::PrepareScheduleAllAll(const cTimers *Timers, const cSchedules *Schedules, const cEvent *Event, const cChannel *Channel) { Clear(); SetCols(CHNUMWIDTH, CHNAMWIDTH, 7, 6, 4); SetTitle(tr("All events - all channels")); LOCK_CHANNELS_READ; cStateKey StateKey; for (const cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch)) { if (const cSchedule *Schedule = Schedules->GetSchedule(ch)) { time_t now = time(NULL) - Setup.EPGLinger * 60; for (const cEvent *ev = Schedule->Events()->First(); ev; ev = Schedule->Events()->Next(ev)) { if (ev->EndTime() > now || ev == Event) Add(new cMenuScheduleItem(Timers, ev, ch, true), ev == Event && ch == Channel); } } } return true; } bool cMenuSchedule::Update(void) { bool result = false; if (const cTimers *Timers = cTimers::GetTimersRead(timersStateKey)) { for (cOsdItem *item = First(); item; item = Next(item)) { if (((cMenuScheduleItem *)item)->Update(Timers)) result = true; } timersStateKey.Remove(); } return result; } void cMenuSchedule::SetHelpKeys(void) { cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current()); canSwitch = false; int NewHelpKeys = 0; if (item) { if (item->timerMatch == tmFull) NewHelpKeys |= 0x02; // "Timer" else NewHelpKeys |= 0x01; // "Record" LOCK_CHANNELS_READ; if (const cChannel *Channel = Channels->GetByChannelID(item->event->ChannelID(), true)) { if (Channel->Number() != cDevice::CurrentChannel()) { NewHelpKeys |= 0x10; // "Switch" canSwitch = true; } } } if (NewHelpKeys != helpKeys) { const char *Red[] = { NULL, tr("Button$Record"), tr("Button$Timer") }; SetHelp(Red[NewHelpKeys & 0x03], tr("Button$Now"), tr("Button$Next"), canSwitch ? tr("Button$Switch") : NULL); helpKeys = NewHelpKeys; } } eOSState cMenuSchedule::Number(void) { cMenuScheduleItem::IncSortMode(); Set(NULL, true); return osContinue; } eOSState cMenuSchedule::Record(void) { if (cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current())) { { LOCK_TIMERS_WRITE; LOCK_SCHEDULES_READ; Timers->SetExplicitModify(); if (item->timerMatch == tmFull) { if (cTimer *Timer = Timers->GetMatch(item->event)) return AddSubMenu(new cMenuEditTimer(Timer)); } cTimer *Timer = new cTimer(item->event); if (cTimer *t = Timers->GetTimer(Timer)) { delete Timer; Timer = t; return AddSubMenu(new cMenuEditTimer(Timer)); } if (Timer->Matches(0, false, NEWTIMERLIMIT)) return AddSubMenu(new cMenuEditTimer(Timer, true)); Timers->Add(Timer); Timers->SetModified(); isyslog("timer %s added (active)", *Timer->ToDescr()); } if (HasSubMenu()) CloseSubMenu(); if (Update()) Display(); SetHelpKeys(); } return osContinue; } eOSState cMenuSchedule::Switch(void) { cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current()); if (item) { LOCK_CHANNELS_READ; const cChannel *Channel = NULL; if (Channel = Channels->GetByChannelID(item->event->ChannelID(), true)) { if (!Channels->SwitchTo(Channel->Number())) Channel = NULL; } if (Channel) return osEnd; } Skins.Message(mtError, tr("Can't switch channel!")); return osContinue; } eOSState cMenuSchedule::ProcessKey(eKeys Key) { if (!HasSubMenu()) Set(); // react on any changes to the schedules list bool HadSubMenu = HasSubMenu(); eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case k0: return Number(); case kRecord: case kRed: return Record(); case kGreen: { LOCK_TIMERS_READ; LOCK_CHANNELS_READ; LOCK_SCHEDULES_READ; if (!now && !next) { int ChannelNr = 0; if (Count()) { if (const cChannel *Channel = Channels->GetByChannelID(((cMenuScheduleItem *)Get(Current()))->event->ChannelID(), true)) ChannelNr = Channel->Number(); } now = true; return AddSubMenu(new cMenuWhatsOn(Timers, Channels, Schedules, now, ChannelNr)); } now = !now; next = !next; return AddSubMenu(new cMenuWhatsOn(Timers, Channels, Schedules, now, cMenuWhatsOn::CurrentChannel())); } case kYellow: { LOCK_TIMERS_READ; LOCK_CHANNELS_READ; LOCK_SCHEDULES_READ; return AddSubMenu(new cMenuWhatsOn(Timers, Channels, Schedules, false, cMenuWhatsOn::CurrentChannel())); } case kBlue: if (canSwitch) return Switch(); break; case kInfo: case kOk: if (Count()) { LOCK_TIMERS_READ; LOCK_CHANNELS_READ; LOCK_SCHEDULES_READ; return AddSubMenu(new cMenuEvent(Timers, Channels, ((cMenuScheduleItem *)Get(Current()))->event, canSwitch, true)); } break; default: break; } } else if (!HasSubMenu()) { now = next = false; if (const cEvent *ei = cMenuWhatsOn::ScheduleEvent()) { LOCK_CHANNELS_READ; if (const cChannel *Channel = Channels->GetByChannelID(ei->ChannelID(), true)) { cMenuScheduleItem::SetSortMode(cMenuScheduleItem::ssmAllThis); Set(Channel, true); } } else if (HadSubMenu && Update()) Display(); if (Key != kNone) SetHelpKeys(); } return state; } // --- cMenuCommands --------------------------------------------------------- cMenuCommands::cMenuCommands(const char *Title, cList *Commands, const char *Parameters) :cOsdMenu(Title) { SetMenuCategory(mcCommand); result = NULL; SetHasHotkeys(); commands = Commands; parameters = Parameters; for (cNestedItem *Command = commands->First(); Command; Command = commands->Next(Command)) { const char *s = Command->Text(); if (Command->SubItems()) Add(new cOsdItem(hk(cString::sprintf("%s...", s)))); else if (Parse(s)) Add(new cOsdItem(hk(title))); } } cMenuCommands::~cMenuCommands() { free(result); } bool cMenuCommands::Parse(const char *s) { const char *p = strchr(s, ':'); if (p) { int l = p - s; if (l > 0) { char t[l + 1]; stripspace(strn0cpy(t, s, l + 1)); l = strlen(t); if (l > 1 && t[l - 1] == '?') { t[l - 1] = 0; confirm = true; } else confirm = false; title = t; command = skipspace(p + 1); return true; } } return false; } eOSState cMenuCommands::Execute(void) { cNestedItem *Command = commands->Get(Current()); if (Command) { if (Command->SubItems()) return AddSubMenu(new cMenuCommands(Title(), Command->SubItems(), parameters)); if (Parse(Command->Text())) { if (!confirm || Interface->Confirm(cString::sprintf("%s?", *title))) { Skins.Message(mtStatus, cString::sprintf("%s...", *title)); free(result); result = NULL; cString cmdbuf; if (!isempty(parameters)) cmdbuf = cString::sprintf("%s %s", *command, *parameters); const char *cmd = *cmdbuf ? *cmdbuf : *command; dsyslog("executing command '%s'", cmd); cPipe p; if (p.Open(cmd, "r")) { int l = 0; int c; while ((c = fgetc(p)) != EOF) { if (l % 20 == 0) { if (char *NewBuffer = (char *)realloc(result, l + 21)) result = NewBuffer; else { esyslog("ERROR: out of memory"); break; } } result[l++] = char(c); } if (result) result[l] = 0; p.Close(); } else esyslog("ERROR: can't open pipe for command '%s'", cmd); Skins.Message(mtStatus, NULL); if (result) return AddSubMenu(new cMenuText(title, result, fontFix)); return osEnd; } } } return osContinue; } eOSState cMenuCommands::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kRed: case kGreen: case kYellow: case kBlue: return osContinue; case kOk: return Execute(); default: break; } } return state; } // --- cMenuCam -------------------------------------------------------------- static bool CamMenuIsOpen = false; class cMenuCam : public cOsdMenu { private: cCamSlot *camSlot; cCiMenu *ciMenu; cCiEnquiry *ciEnquiry; char *input; int offset; time_t lastCamExchange; void GenerateTitle(const char *s = NULL); void QueryCam(void); void AddMultiLineItem(const char *s); void Set(void); eOSState Select(void); public: cMenuCam(cCamSlot *CamSlot); virtual ~cMenuCam(); virtual eOSState ProcessKey(eKeys Key); }; cMenuCam::cMenuCam(cCamSlot *CamSlot) :cOsdMenu("", 1) // tab necessary for enquiry! { SetMenuCategory(mcCam); camSlot = CamSlot; ciMenu = NULL; ciEnquiry = NULL; input = NULL; offset = 0; lastCamExchange = time(NULL); SetNeedsFastResponse(true); QueryCam(); CamMenuIsOpen = true; } cMenuCam::~cMenuCam() { if (ciMenu) ciMenu->Abort(); delete ciMenu; if (ciEnquiry) ciEnquiry->Abort(); delete ciEnquiry; free(input); CamMenuIsOpen = false; } void cMenuCam::GenerateTitle(const char *s) { SetTitle(cString::sprintf("CAM %d - %s", camSlot->SlotNumber(), (s && *s) ? s : camSlot->GetCamName())); } void cMenuCam::QueryCam(void) { delete ciMenu; ciMenu = NULL; delete ciEnquiry; ciEnquiry = NULL; if (camSlot->HasUserIO()) { ciMenu = camSlot->GetMenu(); ciEnquiry = camSlot->GetEnquiry(); } Set(); } void cMenuCam::Set(void) { if (ciMenu) { Clear(); free(input); input = NULL; dsyslog("CAM %d: Menu ------------------", camSlot->SlotNumber()); offset = 0; SetHasHotkeys(ciMenu->Selectable()); GenerateTitle(ciMenu->TitleText()); dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->TitleText()); if (*ciMenu->SubTitleText()) { dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->SubTitleText()); AddMultiLineItem(ciMenu->SubTitleText()); offset = Count(); } for (int i = 0; i < ciMenu->NumEntries(); i++) { Add(new cOsdItem(hk(ciMenu->Entry(i)), osUnknown, ciMenu->Selectable())); dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->Entry(i)); } if (*ciMenu->BottomText()) { AddMultiLineItem(ciMenu->BottomText()); dsyslog("CAM %d: '%s'", camSlot->SlotNumber(), ciMenu->BottomText()); } cRemote::TriggerLastActivity(); } else if (ciEnquiry) { Clear(); int Length = ciEnquiry->ExpectedLength(); free(input); input = MALLOC(char, Length + 1); *input = 0; GenerateTitle(); Add(new cOsdItem(ciEnquiry->Text(), osUnknown, false)); Add(new cOsdItem("", osUnknown, false)); Add(new cMenuEditNumItem("", input, Length, ciEnquiry->Blind())); } Display(); } void cMenuCam::AddMultiLineItem(const char *s) { while (s && *s) { const char *p = strchr(s, '\n'); int l = p ? p - s : strlen(s); cOsdItem *item = new cOsdItem; item->SetSelectable(false); item->SetText(strndup(s, l), false); Add(item); s = p ? p + 1 : p; } } eOSState cMenuCam::Select(void) { if (ciMenu) { if (ciMenu->Selectable()) { ciMenu->Select(Current() - offset); dsyslog("CAM %d: select %d", camSlot->SlotNumber(), Current() - offset); } else ciMenu->Cancel(); } else if (ciEnquiry) { if (ciEnquiry->ExpectedLength() < 0xFF && int(strlen(input)) != ciEnquiry->ExpectedLength()) { char buffer[64]; snprintf(buffer, sizeof(buffer), tr("Please enter %d digits!"), ciEnquiry->ExpectedLength()); Skins.Message(mtError, buffer); return osContinue; } ciEnquiry->Reply(input); dsyslog("CAM %d: entered '%s'", camSlot->SlotNumber(), ciEnquiry->Blind() ? "****" : input); } QueryCam(); return osContinue; } eOSState cMenuCam::ProcessKey(eKeys Key) { if (!camSlot->HasMMI()) return osBack; eOSState state = cOsdMenu::ProcessKey(Key); if (ciMenu || ciEnquiry) { lastCamExchange = time(NULL); if (state == osUnknown) { switch (Key) { case kOk: return Select(); default: break; } } else if (state == osBack) { if (ciMenu) ciMenu->Cancel(); if (ciEnquiry) ciEnquiry->Cancel(); QueryCam(); return osContinue; } if (ciMenu && ciMenu->HasUpdate()) { QueryCam(); return osContinue; } } else if (time(NULL) - lastCamExchange < CAMRESPONSETIMEOUT) QueryCam(); else { Skins.Message(mtError, tr("CAM not responding!")); return osBack; } return state; } // --- CamControl ------------------------------------------------------------ cOsdObject *CamControl(void) { for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) { if (CamSlot->HasUserIO()) return new cMenuCam(CamSlot); } return NULL; } bool CamMenuActive(void) { return CamMenuIsOpen; } // --- cMenuPathEdit --------------------------------------------------------- class cMenuPathEdit : public cOsdMenu { private: cString path; char folder[PATH_MAX]; char name[NAME_MAX]; cMenuEditStrItem *folderItem; int pathIsInUse; eOSState SetFolder(void); eOSState Folder(void); eOSState ApplyChanges(void); public: cMenuPathEdit(const char *Path); virtual eOSState ProcessKey(eKeys Key); }; cMenuPathEdit::cMenuPathEdit(const char *Path) :cOsdMenu(tr("Edit path"), 12) { SetMenuCategory(mcRecordingEdit); path = Path; *folder = 0; *name = 0; const char *s = strrchr(path, FOLDERDELIMCHAR); if (s) { strn0cpy(folder, cString(path, s), sizeof(folder)); s++; } else s = path; strn0cpy(name, s, sizeof(name)); { LOCK_RECORDINGS_READ; pathIsInUse = Recordings->PathIsInUse(path); } cOsdItem *p; Add(p = folderItem = new cMenuEditStrItem(tr("Folder"), folder, sizeof(folder))); p->SetSelectable(!pathIsInUse); Add(p = new cMenuEditStrItem(tr("Name"), name, sizeof(name))); p->SetSelectable(!pathIsInUse); if (pathIsInUse) { Add(new cOsdItem("", osUnknown, false)); Add(new cOsdItem(tr("This folder is currently in use - no changes are possible!"), osUnknown, false)); } Display(); if (!pathIsInUse) SetHelp(tr("Button$Folder")); } eOSState cMenuPathEdit::SetFolder(void) { if (cMenuFolder *mf = dynamic_cast(SubMenu())) { strn0cpy(folder, mf->GetFolder(), sizeof(folder)); SetCurrent(folderItem); Display(); } return CloseSubMenu(); } eOSState cMenuPathEdit::Folder(void) { return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, path)); } eOSState cMenuPathEdit::ApplyChanges(void) { if (!*name) { *name = ' '; // name must not be empty! name[1] = 0; } cString NewPath = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name; NewPath.CompactChars(FOLDERDELIMCHAR); if (strcmp(NewPath, path)) { LOCK_RECORDINGS_WRITE; Recordings->SetExplicitModify(); int NumRecordings = Recordings->GetNumRecordingsInPath(path); if (NumRecordings > 1 && !Interface->Confirm(cString::sprintf(tr("Move entire folder containing %d recordings?"), NumRecordings))) return osContinue; if (!Recordings->MoveRecordings(path, NewPath)) { Skins.Message(mtError, tr("Error while moving folder!")); return osContinue; } cMenuRecordings::SetPath(NewPath); // makes sure the Recordings menu will reposition to the new path Recordings->SetModified(); return osUser1; } return osBack; } eOSState cMenuPathEdit::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { if (!pathIsInUse) { switch (Key) { case kRed: return Folder(); case kOk: return ApplyChanges(); default: break; } } else if (Key == kOk) return osBack; } else if (state == osEnd && HasSubMenu()) state = SetFolder(); return state; } // --- cMenuRecordingEdit ---------------------------------------------------- class cMenuRecordingEdit : public cOsdMenu { private: const cRecording *recording; cString originalFileName; cStateKey recordingsStateKey; char folder[PATH_MAX]; char name[NAME_MAX]; int priority; int lifetime; cMenuEditStrItem *folderItem; cMenuEditStrItem *nameItem; const char *buttonFolder; const char *buttonAction; const char *buttonDeleteMarks; const char *actionCancel; const char *doCut; int recordingIsInUse; void Set(void); void SetHelpKeys(void); bool RefreshRecording(void); eOSState SetFolder(void); eOSState Folder(void); eOSState Action(void); eOSState RemoveName(void); eOSState DeleteMarks(void); eOSState ApplyChanges(void); public: cMenuRecordingEdit(const cRecording *Recording); virtual eOSState ProcessKey(eKeys Key); }; cMenuRecordingEdit::cMenuRecordingEdit(const cRecording *Recording) :cOsdMenu(tr("Edit recording"), 12) { SetMenuCategory(mcRecordingEdit); recording = Recording; originalFileName = recording->FileName(); strn0cpy(folder, recording->Folder(), sizeof(folder)); strn0cpy(name, recording->BaseName(), sizeof(name)); priority = recording->Priority(); lifetime = recording->Lifetime(); folderItem = NULL; nameItem = NULL; buttonFolder = NULL; buttonAction = NULL; buttonDeleteMarks = NULL; actionCancel = NULL; doCut = NULL; recordingIsInUse = ruNone; Set(); } void cMenuRecordingEdit::Set(void) { int current = Current(); Clear(); recordingIsInUse = recording->IsInUse(); cOsdItem *p; Add(p = folderItem = new cMenuEditStrItem(tr("Folder"), folder, sizeof(folder))); p->SetSelectable(!recordingIsInUse); Add(p = nameItem = new cMenuEditStrItem(tr("Name"), name, sizeof(name))); p->SetSelectable(!recordingIsInUse); Add(p = new cMenuEditIntItem(tr("Priority"), &priority, 0, MAXPRIORITY)); p->SetSelectable(!recordingIsInUse); Add(p = new cMenuEditIntItem(tr("Lifetime"), &lifetime, 0, MAXLIFETIME)); p->SetSelectable(!recordingIsInUse); if (recordingIsInUse) { Add(new cOsdItem("", osUnknown, false)); Add(new cOsdItem(tr("This recording is currently in use - no changes are possible!"), osUnknown, false)); } SetCurrent(Get(current)); Display(); SetHelpKeys(); } void cMenuRecordingEdit::SetHelpKeys(void) { buttonFolder = !recordingIsInUse ? tr("Button$Folder") : NULL; buttonAction = NULL; buttonDeleteMarks = NULL; actionCancel = NULL; doCut = NULL; if ((recordingIsInUse & ruCut) != 0) buttonAction = actionCancel = ((recordingIsInUse & ruPending) != 0) ? tr("Button$Cancel cutting") : tr("Button$Stop cutting"); else if ((recordingIsInUse & ruMove) != 0) buttonAction = actionCancel = ((recordingIsInUse & ruPending) != 0) ? tr("Button$Cancel moving") : tr("Button$Stop moving"); else if ((recordingIsInUse & ruCopy) != 0) buttonAction = actionCancel = ((recordingIsInUse & ruPending) != 0) ? tr("Button$Cancel copying") : tr("Button$Stop copying"); else if (recording->HasMarks()) { buttonAction = doCut = tr("Button$Cut"); buttonDeleteMarks = tr("Button$Delete marks"); } SetHelp(buttonFolder, buttonAction, buttonDeleteMarks); } bool cMenuRecordingEdit::RefreshRecording(void) { if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(recordingsStateKey)) { if ((recording = Recordings->GetByName(originalFileName)) != NULL) Set(); else { recordingsStateKey.Remove(); Skins.Message(mtWarning, tr("Recording vanished!")); return false; } recordingsStateKey.Remove(); } return true; } eOSState cMenuRecordingEdit::SetFolder(void) { if (cMenuFolder *mf = dynamic_cast(SubMenu())) { strn0cpy(folder, mf->GetFolder(), sizeof(folder)); SetCurrent(folderItem); Display(); } return CloseSubMenu(); } eOSState cMenuRecordingEdit::Folder(void) { return AddSubMenu(new cMenuFolder(tr("Select folder"), &Folders, recording->Name())); } eOSState cMenuRecordingEdit::Action(void) { if (actionCancel) RecordingsHandler.Del(recording->FileName()); else if (doCut) { if (access(cCutter::EditedFileName(recording->FileName()), F_OK) != 0 || Interface->Confirm(tr("Edited version already exists - overwrite?"))) { if (!RecordingsHandler.Add(ruCut, recording->FileName())) Skins.Message(mtError, tr("Error while queueing recording for cutting!")); } } recordingIsInUse = recording->IsInUse(); RefreshRecording(); SetHelpKeys(); return osContinue; } eOSState cMenuRecordingEdit::RemoveName(void) { if (Get(Current()) == nameItem) { if (Interface->Confirm(tr("Rename recording to folder name?"))) { char *s = strrchr(folder, FOLDERDELIMCHAR); if (s) *s++ = 0; else s = folder; strn0cpy(name, s, sizeof(name)); if (s == folder) *s = 0; Set(); } } return osContinue; } eOSState cMenuRecordingEdit::DeleteMarks(void) { if (buttonDeleteMarks && Interface->Confirm(tr("Delete editing marks for this recording?"))) { if (cMarks::DeleteMarksFile(recording)) SetHelpKeys(); else Skins.Message(mtError, tr("Error while deleting editing marks!")); } return osContinue; } eOSState cMenuRecordingEdit::ApplyChanges(void) { cStateKey StateKey; cRecordings *Recordings = cRecordings::GetRecordingsWrite(StateKey); cRecording *Recording = Recordings->GetByName(recording->FileName()); if (!Recording) { Skins.Message(mtWarning, tr("Recording vanished!")); return osBack; } bool Modified = false; if (priority != recording->Priority() || lifetime != recording->Lifetime()) { if (!Recording->ChangePriorityLifetime(priority, lifetime)) { Skins.Message(mtError, tr("Error while changing priority/lifetime!")); StateKey.Remove(Modified); return osContinue; } Modified = true; } if (!*name) { *name = ' '; // name must not be empty! name[1] = 0; } cString NewName = *folder ? cString::sprintf("%s%c%s", folder, FOLDERDELIMCHAR, name) : name; NewName.CompactChars(FOLDERDELIMCHAR); if (strcmp(NewName, Recording->Name())) { if (!Recording->ChangeName(NewName)) { Skins.Message(mtError, tr("Error while changing folder/name!")); StateKey.Remove(Modified); return osContinue; } Modified = true; } if (Modified) { cMenuRecordings::SetRecording(Recording->FileName()); // makes sure the Recordings menu will reposition to the renamed recording Recordings->TouchUpdate(); StateKey.Remove(Modified); return osUser1; } StateKey.Remove(Modified); return osBack; } eOSState cMenuRecordingEdit::ProcessKey(eKeys Key) { if (!HasSubMenu()) { if (!RefreshRecording()) return osBack; // the recording has vanished, so close this menu } eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case k0: return RemoveName(); case kRed: return buttonFolder ? Folder() : osContinue; case kGreen: return buttonAction ? Action() : osContinue; case kYellow: return buttonDeleteMarks ? DeleteMarks() : osContinue; case kOk: return !recordingIsInUse ? ApplyChanges() : osBack; default: break; } } else if (state == osEnd && HasSubMenu()) state = SetFolder(); return state; } // --- cMenuRecording -------------------------------------------------------- class cMenuRecording : public cOsdMenu { private: const cRecording *recording; cString originalFileName; cStateKey recordingsStateKey; bool withButtons; bool RefreshRecording(void); public: cMenuRecording(const cRecording *Recording, bool WithButtons = false); virtual void Display(void); virtual eOSState ProcessKey(eKeys Key); }; cMenuRecording::cMenuRecording(const cRecording *Recording, bool WithButtons) :cOsdMenu(tr("Recording info")) { SetMenuCategory(mcRecordingInfo); recording = Recording; originalFileName = recording->FileName(); withButtons = WithButtons; if (withButtons) SetHelp(tr("Button$Play"), tr("Button$Rewind"), NULL, tr("Button$Edit")); } bool cMenuRecording::RefreshRecording(void) { if (const cRecordings *Recordings = cRecordings::GetRecordingsRead(recordingsStateKey)) { if ((recording = Recordings->GetByName(originalFileName)) != NULL) Display(); else { recordingsStateKey.Remove(); Skins.Message(mtWarning, tr("Recording vanished!")); return false; } recordingsStateKey.Remove(); } return true; } void cMenuRecording::Display(void) { if (HasSubMenu()) { SubMenu()->Display(); return; } cOsdMenu::Display(); DisplayMenu()->SetRecording(recording); if (recording->Info()->Description()) cStatus::MsgOsdTextItem(recording->Info()->Description()); } eOSState cMenuRecording::ProcessKey(eKeys Key) { if (HasSubMenu()) return cOsdMenu::ProcessKey(Key); else if (!RefreshRecording()) return osBack; // the recording has vanished, so close this menu switch (int(Key)) { case kUp|k_Repeat: case kUp: case kDown|k_Repeat: case kDown: case kLeft|k_Repeat: case kLeft: case kRight|k_Repeat: case kRight: DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight); cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft); return osContinue; case kInfo: return osBack; default: break; } eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kRed: if (withButtons) Key = kOk; // will play the recording, even if recording commands are defined case kGreen: if (!withButtons) break; cRemote::Put(Key, true); // continue with osBack to close the info menu and process the key case kOk: return osBack; case kBlue: if (withButtons) return AddSubMenu(new cMenuRecordingEdit(recording)); break; default: break; } } return state; } // --- cMenuRecordingItem ---------------------------------------------------- class cMenuRecordingItem : public cOsdItem { private: const cRecording *recording; int level; char *name; int totalEntries, newEntries; public: cMenuRecordingItem(const cRecording *Recording, int Level); ~cMenuRecordingItem(); void IncrementCounter(bool New); const char *Name(void) { return name; } int Level(void) { return level; } const cRecording *Recording(void) { return recording; } bool IsDirectory(void) { return name != NULL; } void SetRecording(const cRecording *Recording) { recording = Recording; } virtual void SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable); }; cMenuRecordingItem::cMenuRecordingItem(const cRecording *Recording, int Level) { recording = Recording; level = Level; name = NULL; totalEntries = newEntries = 0; SetText(Recording->Title('\t', true, Level)); if (*Text() == '\t') name = strdup(Text() + 2); // 'Text() + 2' to skip the two '\t' } cMenuRecordingItem::~cMenuRecordingItem() { free(name); } void cMenuRecordingItem::IncrementCounter(bool New) { totalEntries++; if (New) newEntries++; SetText(cString::sprintf("%d\t\t%d\t%s", totalEntries, newEntries, name)); } void cMenuRecordingItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable) { if (!DisplayMenu->SetItemRecording(recording, Index, Current, Selectable, level, totalEntries, newEntries)) DisplayMenu->SetItem(Text(), Index, Current, Selectable); } // --- cMenuRecordings ------------------------------------------------------- cString cMenuRecordings::path; cString cMenuRecordings::fileName; cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus, const cRecordingFilter *Filter) :cOsdMenu(Base ? Base : tr("Recordings"), 9, 6, 6) { SetMenuCategory(mcRecording); base = Base ? strdup(Base) : NULL; level = Setup.RecordingDirs ? Level : -1; filter = Filter; helpKeys = -1; Display(); // this keeps the higher level menus from showing up briefly when pressing 'Back' during replay Set(); if (Current() < 0) SetCurrent(First()); else if (OpenSubMenus && (cReplayControl::LastReplayed() || *path || *fileName)) { if (!*path || Level < strcountchr(path, FOLDERDELIMCHAR)) { if (Open(true)) return; } } Display(); SetHelpKeys(); } cMenuRecordings::~cMenuRecordings() { if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current())) { if (!ri->IsDirectory()) SetRecording(ri->Recording()->FileName()); } free(base); } void cMenuRecordings::SetHelpKeys(void) { cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); int NewHelpKeys = 0; if (ri) { if (ri->IsDirectory()) NewHelpKeys = 1; else NewHelpKeys = 2; } if (NewHelpKeys != helpKeys) { switch (NewHelpKeys) { case 0: SetHelp(NULL); break; case 1: SetHelp(tr("Button$Open"), NULL, NULL, tr("Button$Edit")); break; case 2: SetHelp(RecordingCommands.Count() ? tr("Commands") : tr("Button$Play"), tr("Button$Rewind"), tr("Button$Delete"), tr("Button$Info")); default: ; } helpKeys = NewHelpKeys; } } void cMenuRecordings::Set(bool Refresh) { if (cRecordings::GetRecordingsRead(recordingsStateKey)) { recordingsStateKey.Remove(); const char *CurrentRecording = *fileName ? *fileName : cReplayControl::LastReplayed(); cRecordings *Recordings = cRecordings::GetRecordingsWrite(recordingsStateKey); // write access is necessary for sorting! cMenuRecordingItem *LastItem = NULL; if (Refresh) { if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current())) CurrentRecording = ri->Recording()->FileName(); } Clear(); GetRecordingsSortMode(DirectoryName()); Recordings->Sort(); for (const cRecording *Recording = Recordings->First(); Recording; Recording = Recordings->Next(Recording)) { if ((!filter || filter->Filter(Recording)) && (!base || (strstr(Recording->Name(), base) == Recording->Name() && Recording->Name()[strlen(base)] == FOLDERDELIMCHAR))) { cMenuRecordingItem *Item = new cMenuRecordingItem(Recording, level); cMenuRecordingItem *LastDir = NULL; if (Item->IsDirectory()) { // Sorting may ignore non-alphanumeric characters, so we need to explicitly handle directories in case they only differ in such characters: for (cMenuRecordingItem *p = LastItem; p; p = dynamic_cast(p->Prev())) { if (p->Name() && strcmp(p->Name(), Item->Name()) == 0) { LastDir = p; break; } } } if (*Item->Text() && !LastDir) { Add(Item); LastItem = Item; if (Item->IsDirectory()) LastDir = Item; } else delete Item; if (LastItem || LastDir) { if (*path) { if (strcmp(path, Recording->Folder()) == 0) SetCurrent(LastDir ? LastDir : LastItem); } else if (CurrentRecording && strcmp(CurrentRecording, Recording->FileName()) == 0) SetCurrent(LastDir ? LastDir : LastItem); } if (LastDir) LastDir->IncrementCounter(Recording->IsNew()); } } SetMenuSortMode(RecordingsSortMode == rsmName ? msmName : msmTime); recordingsStateKey.Remove(false); // sorting doesn't count as a real modification } if (Refresh) Display(); } void cMenuRecordings::SetPath(const char *Path) { path = Path; } void cMenuRecordings::SetRecording(const char *FileName) { fileName = FileName; } cString cMenuRecordings::DirectoryName(void) { cString d(cVideoDirectory::Name()); if (base) { char *s = ExchangeChars(strdup(base), true); d = AddDirectory(d, s); free(s); } return d; } bool cMenuRecordings::Open(bool OpenSubMenus) { cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); if (ri && ri->IsDirectory() && (!*path || strcountchr(path, FOLDERDELIMCHAR) > 0)) { const char *t = ri->Name(); cString buffer; if (base) { buffer = cString::sprintf("%s%c%s", base, FOLDERDELIMCHAR, t); t = buffer; } AddSubMenu(new cMenuRecordings(t, level + 1, OpenSubMenus, filter)); return true; } return false; } eOSState cMenuRecordings::Play(void) { cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); if (ri) { if (ri->IsDirectory()) Open(); else { cReplayControl::SetRecording(ri->Recording()->FileName()); return osReplay; } } return osContinue; } eOSState cMenuRecordings::Rewind(void) { if (HasSubMenu() || Count() == 0) return osContinue; cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); if (ri && !ri->IsDirectory()) { cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording cResumeFile ResumeFile(ri->Recording()->FileName(), ri->Recording()->IsPesRecording()); ResumeFile.Delete(); return Play(); } return osContinue; } eOSState cMenuRecordings::Delete(void) { if (HasSubMenu() || Count() == 0) return osContinue; cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); if (ri && !ri->IsDirectory()) { if (Interface->Confirm(tr("Delete recording?"))) { if (cRecordControl *rc = cRecordControls::GetRecordControl(ri->Recording()->FileName())) { if (Interface->Confirm(tr("Timer still recording - really delete?"))) { if (cTimer *Timer = rc->Timer()) { LOCK_TIMERS_WRITE; Timer->Skip(); cRecordControls::Process(Timers, time(NULL)); if (Timer->IsSingleEvent()) { isyslog("deleting timer %s", *Timer->ToDescr()); Timers->Del(Timer); } } } else return osContinue; } cRecordings *Recordings = cRecordings::GetRecordingsWrite(recordingsStateKey); Recordings->SetExplicitModify(); cRecording *Recording = Recordings->GetByName(ri->Recording()->FileName()); if (!Recording) { Skins.Message(mtWarning, tr("Recording vanished!")); recordingsStateKey.Remove(); return osContinue; } cString FileName = Recording->FileName(); if (RecordingsHandler.GetUsage(FileName)) { if (Interface->Confirm(tr("Recording is being edited - really delete?"))) { RecordingsHandler.Del(FileName); Recording = Recordings->GetByName(FileName); // RecordingsHandler.Del() might have deleted it if it was the edited version // we continue with the code below even if Recording is NULL, // in order to have the menu updated etc. } else { recordingsStateKey.Remove(); return osContinue; } } if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), FileName) == 0) cControl::Shutdown(); if (!Recording || Recording->Delete()) { cReplayControl::ClearLastReplayed(FileName); Recordings->DelByName(FileName); cOsdMenu::Del(Current()); SetHelpKeys(); cVideoDiskUsage::ForceCheck(); Display(); Recordings->SetModified(); recordingsStateKey.Remove(); if (!Count()) return osBack; return osUser2; } else Skins.Message(mtError, tr("Error while deleting recording!")); recordingsStateKey.Remove(false); } } return osContinue; } eOSState cMenuRecordings::Info(void) { if (HasSubMenu() || Count() == 0) return osContinue; if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current())) { if (ri->IsDirectory()) return AddSubMenu(new cMenuPathEdit(cString(ri->Recording()->Name(), strchrn(ri->Recording()->Name(), FOLDERDELIMCHAR, ri->Level() + 1)))); else return AddSubMenu(new cMenuRecording(ri->Recording(), true)); } return osContinue; } eOSState cMenuRecordings::Commands(eKeys Key) { if (HasSubMenu() || Count() == 0) return osContinue; cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); if (ri && !ri->IsDirectory()) { cMenuCommands *menu; eOSState state = AddSubMenu(menu = new cMenuCommands(tr("Recording commands"), &RecordingCommands, cString::sprintf("\"%s\"", *strescape(ri->Recording()->FileName(), "\\\"$")))); if (Key != kNone) state = menu->ProcessKey(Key); return state; } return osContinue; } eOSState cMenuRecordings::Sort(void) { if (HasSubMenu()) return osContinue; IncRecordingsSortMode(DirectoryName()); Set(true); return osContinue; } eOSState cMenuRecordings::ProcessKey(eKeys Key) { if (!HasSubMenu()) Set(); // react on any changes to the recordings list bool HadSubMenu = HasSubMenu(); eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kPlayPause: case kPlay: case kOk: return Play(); case kRed: return (helpKeys > 1 && RecordingCommands.Count()) ? Commands() : Play(); case kGreen: return Rewind(); case kYellow: return Delete(); case kInfo: case kBlue: return Info(); case k0: return Sort(); case k1...k9: return Commands(Key); default: break; } } else if (state == osUser1) { // a recording or path was renamed, so let's refresh the menu CloseSubMenu(false); if (base) return state; // closes all recording menus except for the top one Set(); // this is the top level menu, so we refresh it... Open(true); // ...and open any necessary submenus to show the new name Display(); path = NULL; fileName = NULL; } else if (state == osUser2) { // a recording in a sub folder was deleted, so update the current item cOsdMenu *m = HasSubMenu() ? SubMenu() : this; if (cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current())) { if (cMenuRecordingItem *riSub = (cMenuRecordingItem *)m->Get(m->Current())) ri->SetRecording(riSub->Recording()); } } if (Key == kYellow && HadSubMenu && !HasSubMenu()) { // the last recording in a subdirectory was deleted, so let's go back up cOsdMenu::Del(Current()); if (!Count()) return osBack; Display(); } if (!HasSubMenu()) { if (Key != kNone) SetHelpKeys(); } return state; } // --- cMenuSetupBase -------------------------------------------------------- class cMenuSetupBase : public cMenuSetupPage { protected: cSetup data; virtual void Store(void); public: cMenuSetupBase(void); }; cMenuSetupBase::cMenuSetupBase(void) { data = Setup; } void cMenuSetupBase::Store(void) { Setup = data; cOsdProvider::UpdateOsdSize(true); Setup.Save(); } // --- cMenuSetupOSD --------------------------------------------------------- class cMenuSetupOSD : public cMenuSetupBase { private: const char *useSmallFontTexts[3]; const char *recSortModeTexts[2]; const char *keyColorTexts[4]; int osdLanguageIndex; int numSkins; int originalSkinIndex; int skinIndex; const char **skinDescriptions; cThemes themes; int originalThemeIndex; int themeIndex; cStringList fontOsdNames, fontSmlNames, fontFixNames; int fontOsdIndex, fontSmlIndex, fontFixIndex; virtual void Set(void); public: cMenuSetupOSD(void); virtual ~cMenuSetupOSD(); virtual eOSState ProcessKey(eKeys Key); }; cMenuSetupOSD::cMenuSetupOSD(void) { SetMenuCategory(mcSetupOsd); osdLanguageIndex = I18nCurrentLanguage(); numSkins = Skins.Count(); skinIndex = originalSkinIndex = Skins.Current()->Index(); skinDescriptions = new const char*[numSkins]; themes.Load(Skins.Current()->Name()); themeIndex = originalThemeIndex = Skins.Current()->Theme() ? themes.GetThemeIndex(Skins.Current()->Theme()->Description()) : 0; cFont::GetAvailableFontNames(&fontOsdNames); cFont::GetAvailableFontNames(&fontSmlNames); cFont::GetAvailableFontNames(&fontFixNames, true); fontOsdNames.Insert(strdup(DefaultFontOsd)); fontSmlNames.Insert(strdup(DefaultFontSml)); fontFixNames.Insert(strdup(DefaultFontFix)); fontOsdIndex = max(0, fontOsdNames.Find(Setup.FontOsd)); fontSmlIndex = max(0, fontSmlNames.Find(Setup.FontSml)); fontFixIndex = max(0, fontFixNames.Find(Setup.FontFix)); Set(); } cMenuSetupOSD::~cMenuSetupOSD() { delete[] skinDescriptions; } void cMenuSetupOSD::Set(void) { int current = Current(); for (cSkin *Skin = Skins.First(); Skin; Skin = Skins.Next(Skin)) skinDescriptions[Skin->Index()] = Skin->Description(); useSmallFontTexts[0] = tr("never"); useSmallFontTexts[1] = tr("skin dependent"); useSmallFontTexts[2] = tr("always"); recSortModeTexts[0] = tr("by name"); recSortModeTexts[1] = tr("by time"); keyColorTexts[0] = tr("Key$Red"); keyColorTexts[1] = tr("Key$Green"); keyColorTexts[2] = tr("Key$Yellow"); keyColorTexts[3] = tr("Key$Blue"); Clear(); SetSection(tr("OSD")); Add(new cMenuEditStraItem(tr("Setup.OSD$Language"), &osdLanguageIndex, I18nNumLanguagesWithLocale(), &I18nLanguages()->At(0))); Add(new cMenuEditStraItem(tr("Setup.OSD$Skin"), &skinIndex, numSkins, skinDescriptions)); if (themes.NumThemes()) Add(new cMenuEditStraItem(tr("Setup.OSD$Theme"), &themeIndex, themes.NumThemes(), themes.Descriptions())); Add(new cMenuEditPrcItem( tr("Setup.OSD$Left (%)"), &data.OSDLeftP, 0.0, 0.5)); Add(new cMenuEditPrcItem( tr("Setup.OSD$Top (%)"), &data.OSDTopP, 0.0, 0.5)); Add(new cMenuEditPrcItem( tr("Setup.OSD$Width (%)"), &data.OSDWidthP, 0.5, 1.0)); Add(new cMenuEditPrcItem( tr("Setup.OSD$Height (%)"), &data.OSDHeightP, 0.5, 1.0)); Add(new cMenuEditIntItem( tr("Setup.OSD$Message time (s)"), &data.OSDMessageTime, 1, 60)); Add(new cMenuEditStraItem(tr("Setup.OSD$Use small font"), &data.UseSmallFont, 3, useSmallFontTexts)); Add(new cMenuEditBoolItem(tr("Setup.OSD$Anti-alias"), &data.AntiAlias)); Add(new cMenuEditStraItem(tr("Setup.OSD$Default font"), &fontOsdIndex, fontOsdNames.Size(), &fontOsdNames[0])); Add(new cMenuEditStraItem(tr("Setup.OSD$Small font"), &fontSmlIndex, fontSmlNames.Size(), &fontSmlNames[0])); Add(new cMenuEditStraItem(tr("Setup.OSD$Fixed font"), &fontFixIndex, fontFixNames.Size(), &fontFixNames[0])); Add(new cMenuEditPrcItem( tr("Setup.OSD$Default font size (%)"), &data.FontOsdSizeP, 0.01, 0.1, 1)); Add(new cMenuEditPrcItem( tr("Setup.OSD$Small font size (%)"), &data.FontSmlSizeP, 0.01, 0.1, 1)); Add(new cMenuEditPrcItem( tr("Setup.OSD$Fixed font size (%)"), &data.FontFixSizeP, 0.01, 0.1, 1)); Add(new cMenuEditBoolItem(tr("Setup.OSD$Channel info position"), &data.ChannelInfoPos, tr("bottom"), tr("top"))); Add(new cMenuEditIntItem( tr("Setup.OSD$Channel info time (s)"), &data.ChannelInfoTime, 1, 60)); Add(new cMenuEditBoolItem(tr("Setup.OSD$Info on channel switch"), &data.ShowInfoOnChSwitch)); Add(new cMenuEditBoolItem(tr("Setup.OSD$Timeout requested channel info"), &data.TimeoutRequChInfo)); Add(new cMenuEditBoolItem(tr("Setup.OSD$Scroll pages"), &data.MenuScrollPage)); Add(new cMenuEditBoolItem(tr("Setup.OSD$Scroll wraps"), &data.MenuScrollWrap)); Add(new cMenuEditBoolItem(tr("Setup.OSD$Menu key closes"), &data.MenuKeyCloses)); Add(new cMenuEditBoolItem(tr("Setup.OSD$Recording directories"), &data.RecordingDirs)); Add(new cMenuEditBoolItem(tr("Setup.OSD$Folders in timer menu"), &data.FoldersInTimerMenu)); Add(new cMenuEditBoolItem(tr("Setup.OSD$Always sort folders first"), &data.AlwaysSortFoldersFirst)); Add(new cMenuEditStraItem(tr("Setup.OSD$Default sort mode for recordings"), &data.DefaultSortModeRec, 2, recSortModeTexts)); Add(new cMenuEditBoolItem(tr("Setup.OSD$Number keys for characters"), &data.NumberKeysForChars)); Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 0"), &data.ColorKey0, 4, keyColorTexts)); Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 1"), &data.ColorKey1, 4, keyColorTexts)); Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 2"), &data.ColorKey2, 4, keyColorTexts)); Add(new cMenuEditStraItem(tr("Setup.OSD$Color key 3"), &data.ColorKey3, 4, keyColorTexts)); SetCurrent(Get(current)); Display(); } eOSState cMenuSetupOSD::ProcessKey(eKeys Key) { bool ModifiedAppearance = false; if (Key == kOk) { I18nSetLocale(data.OSDLanguage); if (skinIndex != originalSkinIndex) { cSkin *Skin = Skins.Get(skinIndex); if (Skin) { Utf8Strn0Cpy(data.OSDSkin, Skin->Name(), sizeof(data.OSDSkin)); Skins.SetCurrent(Skin->Name()); ModifiedAppearance = true; } } if (themes.NumThemes() && Skins.Current()->Theme()) { Skins.Current()->Theme()->Load(themes.FileName(themeIndex)); Utf8Strn0Cpy(data.OSDTheme, themes.Name(themeIndex), sizeof(data.OSDTheme)); ModifiedAppearance |= themeIndex != originalThemeIndex; } if (!(DoubleEqual(data.OSDLeftP, Setup.OSDLeftP) && DoubleEqual(data.OSDTopP, Setup.OSDTopP) && DoubleEqual(data.OSDWidthP, Setup.OSDWidthP) && DoubleEqual(data.OSDHeightP, Setup.OSDHeightP))) ModifiedAppearance = true; if (data.UseSmallFont != Setup.UseSmallFont || data.AntiAlias != Setup.AntiAlias) ModifiedAppearance = true; Utf8Strn0Cpy(data.FontOsd, fontOsdNames[fontOsdIndex], sizeof(data.FontOsd)); Utf8Strn0Cpy(data.FontSml, fontSmlNames[fontSmlIndex], sizeof(data.FontSml)); Utf8Strn0Cpy(data.FontFix, fontFixNames[fontFixIndex], sizeof(data.FontFix)); if (strcmp(data.FontOsd, Setup.FontOsd) || !DoubleEqual(data.FontOsdSizeP, Setup.FontOsdSizeP)) ModifiedAppearance = true; if (strcmp(data.FontSml, Setup.FontSml) || !DoubleEqual(data.FontSmlSizeP, Setup.FontSmlSizeP)) ModifiedAppearance = true; if (strcmp(data.FontFix, Setup.FontFix) || !DoubleEqual(data.FontFixSizeP, Setup.FontFixSizeP)) ModifiedAppearance = true; if (data.AlwaysSortFoldersFirst != Setup.AlwaysSortFoldersFirst || data.RecordingDirs != Setup.RecordingDirs) { LOCK_RECORDINGS_WRITE; Recordings->ClearSortNames(); } } int oldSkinIndex = skinIndex; int oldOsdLanguageIndex = osdLanguageIndex; eOSState state = cMenuSetupBase::ProcessKey(Key); if (ModifiedAppearance) { cOsdProvider::UpdateOsdSize(true); SetDisplayMenu(); } if (osdLanguageIndex != oldOsdLanguageIndex || skinIndex != oldSkinIndex) { strn0cpy(data.OSDLanguage, I18nLocale(osdLanguageIndex), sizeof(data.OSDLanguage)); int OriginalOSDLanguage = I18nCurrentLanguage(); I18nSetLanguage(osdLanguageIndex); cSkin *Skin = Skins.Get(skinIndex); if (Skin) { char *d = themes.NumThemes() ? strdup(themes.Descriptions()[themeIndex]) : NULL; themes.Load(Skin->Name()); if (skinIndex != oldSkinIndex) themeIndex = d ? themes.GetThemeIndex(d) : 0; free(d); } Set(); I18nSetLanguage(OriginalOSDLanguage); } return state; } // --- cMenuSetupEPG --------------------------------------------------------- class cMenuSetupEPG : public cMenuSetupBase { private: int originalNumLanguages; int numLanguages; void Setup(void); public: cMenuSetupEPG(void); virtual eOSState ProcessKey(eKeys Key); }; cMenuSetupEPG::cMenuSetupEPG(void) { SetMenuCategory(mcSetupEpg); for (numLanguages = 0; numLanguages < I18nLanguages()->Size() && data.EPGLanguages[numLanguages] >= 0; numLanguages++) ; originalNumLanguages = numLanguages; SetSection(tr("EPG")); SetHelp(tr("Button$Scan")); Setup(); } void cMenuSetupEPG::Setup(void) { int current = Current(); Clear(); Add(new cMenuEditIntItem( tr("Setup.EPG$EPG scan timeout (h)"), &data.EPGScanTimeout)); Add(new cMenuEditIntItem( tr("Setup.EPG$EPG bugfix level"), &data.EPGBugfixLevel, 0, MAXEPGBUGFIXLEVEL)); Add(new cMenuEditIntItem( tr("Setup.EPG$EPG linger time (min)"), &data.EPGLinger, 0)); Add(new cMenuEditBoolItem(tr("Setup.EPG$Set system time"), &data.SetSystemTime)); if (data.SetSystemTime) Add(new cMenuEditTranItem(tr("Setup.EPG$Use time from transponder"), &data.TimeTransponder, &data.TimeSource)); // TRANSLATORS: note the plural! Add(new cMenuEditIntItem( tr("Setup.EPG$Preferred languages"), &numLanguages, 0, I18nLanguages()->Size())); for (int i = 0; i < numLanguages; i++) // TRANSLATORS: note the singular! Add(new cMenuEditStraItem(tr("Setup.EPG$Preferred language"), &data.EPGLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0))); SetCurrent(Get(current)); Display(); } eOSState cMenuSetupEPG::ProcessKey(eKeys Key) { if (Key == kOk) { bool Modified = numLanguages != originalNumLanguages; if (!Modified) { for (int i = 0; i < numLanguages; i++) { if (data.EPGLanguages[i] != ::Setup.EPGLanguages[i]) { Modified = true; break; } } } if (Modified) cSchedules::ResetVersions(); } int oldnumLanguages = numLanguages; int oldSetSystemTime = data.SetSystemTime; eOSState state = cMenuSetupBase::ProcessKey(Key); if (Key != kNone) { if (numLanguages != oldnumLanguages || data.SetSystemTime != oldSetSystemTime) { for (int i = oldnumLanguages; i < numLanguages; i++) { data.EPGLanguages[i] = 0; for (int l = 0; l < I18nLanguages()->Size(); l++) { int k; for (k = 0; k < oldnumLanguages; k++) { if (data.EPGLanguages[k] == l) break; } if (k >= oldnumLanguages) { data.EPGLanguages[i] = l; break; } } } data.EPGLanguages[numLanguages] = -1; Setup(); } if (Key == kRed) { EITScanner.ForceScan(); return osEnd; } } return state; } // --- cMenuSetupDVB --------------------------------------------------------- class cMenuSetupDVB : public cMenuSetupBase { private: int originalNumAudioLanguages; int numAudioLanguages; int originalNumSubtitleLanguages; int numSubtitleLanguages; void Setup(void); const char *videoDisplayFormatTexts[3]; const char *updateChannelsTexts[6]; const char *standardComplianceTexts[3]; public: cMenuSetupDVB(void); virtual eOSState ProcessKey(eKeys Key); }; cMenuSetupDVB::cMenuSetupDVB(void) { SetMenuCategory(mcSetupDvb); for (numAudioLanguages = 0; numAudioLanguages < I18nLanguages()->Size() && data.AudioLanguages[numAudioLanguages] >= 0; numAudioLanguages++) ; for (numSubtitleLanguages = 0; numSubtitleLanguages < I18nLanguages()->Size() && data.SubtitleLanguages[numSubtitleLanguages] >= 0; numSubtitleLanguages++) ; originalNumAudioLanguages = numAudioLanguages; originalNumSubtitleLanguages = numSubtitleLanguages; videoDisplayFormatTexts[0] = tr("pan&scan"); videoDisplayFormatTexts[1] = tr("letterbox"); videoDisplayFormatTexts[2] = tr("center cut out"); updateChannelsTexts[0] = tr("no"); updateChannelsTexts[1] = tr("names only"); updateChannelsTexts[2] = tr("PIDs only"); updateChannelsTexts[3] = tr("names and PIDs"); updateChannelsTexts[4] = tr("add new channels"); updateChannelsTexts[5] = tr("add new transponders"); standardComplianceTexts[0] = "DVB"; standardComplianceTexts[1] = "ANSI/SCTE"; standardComplianceTexts[2] = "NORDIG"; SetSection(tr("DVB")); SetHelp(NULL, tr("Button$Audio"), tr("Button$Subtitles"), NULL); Setup(); } void cMenuSetupDVB::Setup(void) { int current = Current(); Clear(); Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDevice::NumDevices())); Add(new cMenuEditStraItem(tr("Setup.DVB$Standard compliance"), &data.StandardCompliance, 3, standardComplianceTexts)); Add(new cMenuEditBoolItem(tr("Setup.DVB$Video format"), &data.VideoFormat, "4:3", "16:9")); if (data.VideoFormat == 0) Add(new cMenuEditStraItem(tr("Setup.DVB$Video display format"), &data.VideoDisplayFormat, 3, videoDisplayFormatTexts)); Add(new cMenuEditBoolItem(tr("Setup.DVB$Use Dolby Digital"), &data.UseDolbyDigital)); Add(new cMenuEditStraItem(tr("Setup.DVB$Update channels"), &data.UpdateChannels, 6, updateChannelsTexts)); Add(new cMenuEditIntItem( tr("Setup.DVB$Audio languages"), &numAudioLanguages, 0, I18nLanguages()->Size())); for (int i = 0; i < numAudioLanguages; i++) Add(new cMenuEditStraItem(tr("Setup.DVB$Audio language"), &data.AudioLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0))); Add(new cMenuEditBoolItem(tr("Setup.DVB$Display subtitles"), &data.DisplaySubtitles)); if (data.DisplaySubtitles) { Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle languages"), &numSubtitleLanguages, 0, I18nLanguages()->Size())); for (int i = 0; i < numSubtitleLanguages; i++) Add(new cMenuEditStraItem(tr("Setup.DVB$Subtitle language"), &data.SubtitleLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0))); Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle offset"), &data.SubtitleOffset, -100, 100)); Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle foreground transparency"), &data.SubtitleFgTransparency, 0, 9)); Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle background transparency"), &data.SubtitleBgTransparency, 0, 10)); } SetCurrent(Get(current)); Display(); } eOSState cMenuSetupDVB::ProcessKey(eKeys Key) { int oldPrimaryDVB = ::Setup.PrimaryDVB; int oldVideoDisplayFormat = ::Setup.VideoDisplayFormat; bool oldVideoFormat = ::Setup.VideoFormat; bool newVideoFormat = data.VideoFormat; bool oldDisplaySubtitles = ::Setup.DisplaySubtitles; bool newDisplaySubtitles = data.DisplaySubtitles; int oldnumAudioLanguages = numAudioLanguages; int oldnumSubtitleLanguages = numSubtitleLanguages; eOSState state = cMenuSetupBase::ProcessKey(Key); if (Key != kNone) { switch (Key) { case kGreen: cRemote::Put(kAudio, true); state = osEnd; break; case kYellow: cRemote::Put(kSubtitles, true); state = osEnd; break; default: { bool DoSetup = data.VideoFormat != newVideoFormat; DoSetup |= data.DisplaySubtitles != newDisplaySubtitles; if (numAudioLanguages != oldnumAudioLanguages) { for (int i = oldnumAudioLanguages; i < numAudioLanguages; i++) { data.AudioLanguages[i] = 0; for (int l = 0; l < I18nLanguages()->Size(); l++) { int k; for (k = 0; k < oldnumAudioLanguages; k++) { if (data.AudioLanguages[k] == l) break; } if (k >= oldnumAudioLanguages) { data.AudioLanguages[i] = l; break; } } } data.AudioLanguages[numAudioLanguages] = -1; DoSetup = true; } if (numSubtitleLanguages != oldnumSubtitleLanguages) { for (int i = oldnumSubtitleLanguages; i < numSubtitleLanguages; i++) { data.SubtitleLanguages[i] = 0; for (int l = 0; l < I18nLanguages()->Size(); l++) { int k; for (k = 0; k < oldnumSubtitleLanguages; k++) { if (data.SubtitleLanguages[k] == l) break; } if (k >= oldnumSubtitleLanguages) { data.SubtitleLanguages[i] = l; break; } } } data.SubtitleLanguages[numSubtitleLanguages] = -1; DoSetup = true; } if (DoSetup) Setup(); } } } if (state == osBack && Key == kOk) { if (::Setup.PrimaryDVB != oldPrimaryDVB) state = osSwitchDvb; if (::Setup.VideoDisplayFormat != oldVideoDisplayFormat) cDevice::PrimaryDevice()->SetVideoDisplayFormat(eVideoDisplayFormat(::Setup.VideoDisplayFormat)); if (::Setup.VideoFormat != oldVideoFormat) cDevice::PrimaryDevice()->SetVideoFormat(::Setup.VideoFormat); if (::Setup.DisplaySubtitles != oldDisplaySubtitles) cDevice::PrimaryDevice()->EnsureSubtitleTrack(); cDvbSubtitleConverter::SetupChanged(); } return state; } // --- cMenuSetupLNB --------------------------------------------------------- class cMenuSetupLNB : public cMenuSetupBase { private: cSatCableNumbers satCableNumbers; void Setup(void); public: cMenuSetupLNB(void); virtual eOSState ProcessKey(eKeys Key); }; cMenuSetupLNB::cMenuSetupLNB(void) :satCableNumbers(MAXDEVICES) { SetMenuCategory(mcSetupLnb); satCableNumbers.FromString(data.DeviceBondings); SetSection(tr("LNB")); Setup(); } void cMenuSetupLNB::Setup(void) { int current = Current(); Clear(); Add(new cMenuEditBoolItem(tr("Setup.LNB$Use DiSEqC"), &data.DiSEqC)); if (!data.DiSEqC) { Add(new cMenuEditIntItem( tr("Setup.LNB$SLOF (MHz)"), &data.LnbSLOF)); Add(new cMenuEditIntItem( tr("Setup.LNB$Low LNB frequency (MHz)"), &data.LnbFrequLo)); Add(new cMenuEditIntItem( tr("Setup.LNB$High LNB frequency (MHz)"), &data.LnbFrequHi)); } int NumSatDevices = 0; for (int i = 0; i < cDevice::NumDevices(); i++) { if (cDevice::GetDevice(i)->ProvidesSource(cSource::stSat)) NumSatDevices++; } if (NumSatDevices > 1) { for (int i = 0; i < cDevice::NumDevices(); i++) { if (cDevice::GetDevice(i)->ProvidesSource(cSource::stSat)) Add(new cMenuEditIntItem(cString::sprintf(tr("Setup.LNB$Device %d connected to sat cable"), i + 1), &satCableNumbers.Array()[i], 0, NumSatDevices, tr("Setup.LNB$own"))); else satCableNumbers.Array()[i] = 0; } } Add(new cMenuEditBoolItem(tr("Setup.LNB$Use dish positioner"), &data.UsePositioner)); if (data.UsePositioner) { Add(new cMenuEditIntxItem(tr("Setup.LNB$Site latitude (degrees)"), &data.SiteLat, -900, 900, 10, tr("South"), tr("North"))); Add(new cMenuEditIntxItem(tr("Setup.LNB$Site longitude (degrees)"), &data.SiteLon, -1800, 1800, 10, tr("West"), tr("East"))); Add(new cMenuEditIntxItem(tr("Setup.LNB$Max. positioner swing (degrees)"), &data.PositionerSwing, 0, 900, 10)); Add(new cMenuEditIntxItem(tr("Setup.LNB$Positioner speed (degrees/s)"), &data.PositionerSpeed, 1, 1800, 10)); } SetCurrent(Get(current)); Display(); } eOSState cMenuSetupLNB::ProcessKey(eKeys Key) { int oldDiSEqC = data.DiSEqC; int oldUsePositioner = data.UsePositioner; bool DeviceBondingsChanged = false; if (Key == kOk) { cString NewDeviceBondings = satCableNumbers.ToString(); DeviceBondingsChanged = strcmp(data.DeviceBondings, NewDeviceBondings) != 0; data.DeviceBondings = NewDeviceBondings; } eOSState state = cMenuSetupBase::ProcessKey(Key); if (Key != kNone && (data.DiSEqC != oldDiSEqC || data.UsePositioner != oldUsePositioner)) Setup(); else if (DeviceBondingsChanged) cDvbDevice::BondDevices(data.DeviceBondings); return state; } // --- cMenuSetupCAM --------------------------------------------------------- class cMenuSetupCAMItem : public cOsdItem { private: cCamSlot *camSlot; public: cMenuSetupCAMItem(cCamSlot *CamSlot); cCamSlot *CamSlot(void) { return camSlot; } bool Changed(void); }; cMenuSetupCAMItem::cMenuSetupCAMItem(cCamSlot *CamSlot) { camSlot = CamSlot; SetText(""); Changed(); } bool cMenuSetupCAMItem::Changed(void) { cString AssignedDevice(""); const char *Activating = ""; const char *CamName = camSlot->GetCamName(); if (!CamName) { switch (camSlot->ModuleStatus()) { case msReset: CamName = tr("CAM reset"); break; case msPresent: CamName = tr("CAM present"); break; case msReady: CamName = tr("CAM ready"); break; default: CamName = "-"; break; } } else if (camSlot->IsActivating()) // TRANSLATORS: note the leading blank! Activating = tr(" (activating)"); if (cDevice *Device = camSlot->Device()) AssignedDevice = cString::sprintf(" %s %d", tr("@ device"), Device->CardIndex() + 1); cString buffer = cString::sprintf(" %d %s%s%s", camSlot->SlotNumber(), CamName, *AssignedDevice, Activating); if (strcmp(buffer, Text()) != 0) { SetText(buffer); return true; } return false; } class cMenuSetupCAM : public cMenuSetupBase { private: const char *activationHelp; eOSState Menu(void); eOSState Reset(void); eOSState Activate(void); void SetHelpKeys(void); public: cMenuSetupCAM(void); virtual eOSState ProcessKey(eKeys Key); }; cMenuSetupCAM::cMenuSetupCAM(void) { activationHelp = NULL; SetMenuCategory(mcSetupCam); SetSection(tr("CAM")); SetCols(15); SetHasHotkeys(); for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) Add(new cMenuSetupCAMItem(CamSlot)); SetHelpKeys(); } void cMenuSetupCAM::SetHelpKeys(void) { if (HasSubMenu()) return; cMenuSetupCAMItem *item = (cMenuSetupCAMItem *)Get(Current()); const char *NewActivationHelp = ""; if (item) { cCamSlot *CamSlot = item->CamSlot(); if (CamSlot->IsActivating()) NewActivationHelp = tr("Button$Cancel activation"); else if (CamSlot->CanActivate()) NewActivationHelp = tr("Button$Activate"); } if (NewActivationHelp != activationHelp) { activationHelp = NewActivationHelp; SetHelp(tr("Button$Menu"), tr("Button$Reset"), activationHelp); } } eOSState cMenuSetupCAM::Menu(void) { cMenuSetupCAMItem *item = (cMenuSetupCAMItem *)Get(Current()); if (item) { if (item->CamSlot()->EnterMenu()) { Skins.Message(mtStatus, tr("Opening CAM menu...")); time_t t0 = time(NULL); time_t t1 = t0; while (time(NULL) - t0 <= MAXWAITFORCAMMENU) { if (item->CamSlot()->HasUserIO()) break; if (time(NULL) - t1 >= CAMMENURETYTIMEOUT) { dsyslog("CAM %d: retrying to enter CAM menu...", item->CamSlot()->SlotNumber()); item->CamSlot()->EnterMenu(); t1 = time(NULL); } cCondWait::SleepMs(100); } Skins.Message(mtStatus, NULL); if (item->CamSlot()->HasUserIO()) return AddSubMenu(new cMenuCam(item->CamSlot())); } Skins.Message(mtError, tr("Can't open CAM menu!")); } return osContinue; } eOSState cMenuSetupCAM::Activate(void) { cMenuSetupCAMItem *item = (cMenuSetupCAMItem *)Get(Current()); if (item) { cCamSlot *CamSlot = item->CamSlot(); if (CamSlot->IsActivating()) CamSlot->CancelActivation(); else if (CamSlot->CanActivate()) { if (CamSlot->Priority() < LIVEPRIORITY) { // don't interrupt recordings LOCK_CHANNELS_READ; if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) { for (int i = 0; i < cDevice::NumDevices(); i++) { if (cDevice *Device = cDevice::GetDevice(i)) { if (Device->ProvidesChannel(Channel)) { if (Device->Priority() < LIVEPRIORITY) { // don't interrupt recordings if (CamSlot->CanActivate()) { if (CamSlot->Assign(Device, true)) { // query cControl::Shutdown(); // must end transfer mode before assigning CAM, otherwise it might be unassigned again if (CamSlot->Assign(Device)) { if (Device->SwitchChannel(Channel, true)) { CamSlot->StartActivation(); return osContinue; } } } } } } } } } } Skins.Message(mtError, tr("Can't activate CAM!")); } } return osContinue; } eOSState cMenuSetupCAM::Reset(void) { cMenuSetupCAMItem *item = (cMenuSetupCAMItem *)Get(Current()); if (item) { if (!item->CamSlot()->Device() || Interface->Confirm(tr("CAM is in use - really reset?"))) { if (!item->CamSlot()->Reset()) Skins.Message(mtError, tr("Can't reset CAM!")); } } return osContinue; } eOSState cMenuSetupCAM::ProcessKey(eKeys Key) { eOSState state = HasSubMenu() ? cMenuSetupBase::ProcessKey(Key) : cOsdMenu::ProcessKey(Key); if (!HasSubMenu()) { switch (Key) { case kOk: case kRed: return Menu(); case kGreen: state = Reset(); break; case kYellow: state = Activate(); break; default: break; } for (cMenuSetupCAMItem *ci = (cMenuSetupCAMItem *)First(); ci; ci = (cMenuSetupCAMItem *)ci->Next()) { if (ci->Changed()) DisplayItem(ci); } SetHelpKeys(); } return state; } // --- cMenuSetupRecord ------------------------------------------------------ class cMenuSetupRecord : public cMenuSetupBase { private: const char *pauseKeyHandlingTexts[3]; const char *delTimeshiftRecTexts[3]; public: cMenuSetupRecord(void); }; cMenuSetupRecord::cMenuSetupRecord(void) { SetMenuCategory(mcSetupRecord); pauseKeyHandlingTexts[0] = tr("do not pause live video"); pauseKeyHandlingTexts[1] = tr("confirm pause live video"); pauseKeyHandlingTexts[2] = tr("pause live video"); delTimeshiftRecTexts[0] = tr("no"); delTimeshiftRecTexts[1] = tr("confirm"); delTimeshiftRecTexts[2] = tr("yes"); SetSection(tr("Recording")); Add(new cMenuEditIntItem( tr("Setup.Recording$Margin at start (min)"), &data.MarginStart)); Add(new cMenuEditIntItem( tr("Setup.Recording$Margin at stop (min)"), &data.MarginStop)); Add(new cMenuEditIntItem( tr("Setup.Recording$Default priority"), &data.DefaultPriority, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Setup.Recording$Default lifetime (d)"), &data.DefaultLifetime, 0, MAXLIFETIME)); Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"), &data.PauseKeyHandling, 3, pauseKeyHandlingTexts)); Add(new cMenuEditIntItem( tr("Setup.Recording$Pause priority"), &data.PausePriority, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Setup.Recording$Pause lifetime (d)"), &data.PauseLifetime, 0, MAXLIFETIME)); Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps)); Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0)); Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord)); Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord))); Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 0, MAXINSTANTRECTIME, tr("Setup.Recording$present event"))); Add(new cMenuEditIntItem( tr("Setup.Recording$Max. video file size (MB)"), &data.MaxVideoFileSize, MINVIDEOFILESIZE, MAXVIDEOFILESIZETS)); Add(new cMenuEditBoolItem(tr("Setup.Recording$Split edited files"), &data.SplitEditedFiles)); Add(new cMenuEditStraItem(tr("Setup.Recording$Delete timeshift recording"),&data.DelTimeshiftRec, 3, delTimeshiftRecTexts)); } // --- cMenuSetupReplay ------------------------------------------------------ class cMenuSetupReplay : public cMenuSetupBase { protected: virtual void Store(void); public: cMenuSetupReplay(void); }; cMenuSetupReplay::cMenuSetupReplay(void) { SetMenuCategory(mcSetupReplay); SetSection(tr("Replay")); Add(new cMenuEditBoolItem(tr("Setup.Replay$Multi speed mode"), &data.MultiSpeedMode)); Add(new cMenuEditBoolItem(tr("Setup.Replay$Show replay mode"), &data.ShowReplayMode)); Add(new cMenuEditBoolItem(tr("Setup.Replay$Show remaining time"), &data.ShowRemainingTime)); Add(new cMenuEditIntItem( tr("Setup.Replay$Progress display time (s)"), &data.ProgressDisplayTime, 0, 60)); Add(new cMenuEditBoolItem(tr("Setup.Replay$Pause replay when setting mark"), &data.PauseOnMarkSet)); Add(new cMenuEditBoolItem(tr("Setup.Replay$Pause replay when jumping to a mark"), &data.PauseOnMarkJump)); Add(new cMenuEditBoolItem(tr("Setup.Replay$Skip edited parts"), &data.SkipEdited)); Add(new cMenuEditBoolItem(tr("Setup.Replay$Pause replay at last mark"), &data.PauseAtLastMark)); Add(new cMenuEditIntItem( tr("Setup.Replay$Initial duration for adaptive skipping (s)"), &data.AdaptiveSkipInitial, 10, 600)); Add(new cMenuEditIntItem( tr("Setup.Replay$Reset timeout for adaptive skipping (s)"), &data.AdaptiveSkipTimeout, 0, 10)); Add(new cMenuEditBoolItem(tr("Setup.Replay$Alternate behavior for adaptive skipping"), &data.AdaptiveSkipAlternate)); Add(new cMenuEditBoolItem(tr("Setup.Replay$Use Prev/Next keys for adaptive skipping"), &data.AdaptiveSkipPrevNext)); Add(new cMenuEditIntItem( tr("Setup.Replay$Skip distance with Green/Yellow keys (s)"), &data.SkipSeconds, 5, 600)); Add(new cMenuEditIntItem( tr("Setup.Replay$Skip distance with Green/Yellow keys in repeat (s)"), &data.SkipSecondsRepeat, 5, 600)); Add(new cMenuEditIntItem(tr("Setup.Replay$Resume ID"), &data.ResumeID, 0, 99)); } void cMenuSetupReplay::Store(void) { if (Setup.ResumeID != data.ResumeID) { LOCK_RECORDINGS_WRITE; Recordings->ResetResume(); } cMenuSetupBase::Store(); } // --- cMenuSetupMisc -------------------------------------------------------- class cMenuSetupMisc : public cMenuSetupBase { public: cMenuSetupMisc(void); }; cMenuSetupMisc::cMenuSetupMisc(void) { SetMenuCategory(mcSetupMisc); SetSection(tr("Miscellaneous")); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. event timeout (min)"), &data.MinEventTimeout)); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. user inactivity (min)"), &data.MinUserInactivity)); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$SVDRP timeout (s)"), &data.SVDRPTimeout)); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Zap timeout (s)"), &data.ZapTimeout)); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Channel entry timeout (ms)"), &data.ChannelEntryTimeout, 0)); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Remote control repeat delay (ms)"), &data.RcRepeatDelay, 0)); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Remote control repeat delta (ms)"), &data.RcRepeatDelta, 0)); Add(new cMenuEditChanItem(tr("Setup.Miscellaneous$Initial channel"), &data.InitialChannel, tr("Setup.Miscellaneous$as before"))); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Initial volume"), &data.InitialVolume, -1, 255, tr("Setup.Miscellaneous$as before"))); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Volume steps"), &data.VolumeSteps, 5, 255)); Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Volume linearize"), &data.VolumeLinearize, -20, 20)); Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Channels wrap"), &data.ChannelsWrap)); Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Show channel names with source"), &data.ShowChannelNamesWithSource)); Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Emergency exit"), &data.EmergencyExit)); } // --- cMenuSetupPluginItem -------------------------------------------------- class cMenuSetupPluginItem : public cOsdItem { private: int pluginIndex; public: cMenuSetupPluginItem(const char *Name, int Index); int PluginIndex(void) { return pluginIndex; } }; cMenuSetupPluginItem::cMenuSetupPluginItem(const char *Name, int Index) :cOsdItem(Name) { pluginIndex = Index; } // --- cMenuSetupPlugins ----------------------------------------------------- class cMenuSetupPlugins : public cMenuSetupBase { public: cMenuSetupPlugins(void); virtual eOSState ProcessKey(eKeys Key); }; cMenuSetupPlugins::cMenuSetupPlugins(void) { SetMenuCategory(mcSetupPlugins); SetSection(tr("Plugins")); SetHasHotkeys(); for (int i = 0; ; i++) { cPlugin *p = cPluginManager::GetPlugin(i); if (p) Add(new cMenuSetupPluginItem(hk(cString::sprintf("%s (%s) - %s", p->Name(), p->Version(), p->Description())), i)); else break; } } eOSState cMenuSetupPlugins::ProcessKey(eKeys Key) { eOSState state = HasSubMenu() ? cMenuSetupBase::ProcessKey(Key) : cOsdMenu::ProcessKey(Key); if (Key == kOk) { if (state == osUnknown) { cMenuSetupPluginItem *item = (cMenuSetupPluginItem *)Get(Current()); if (item) { cPlugin *p = cPluginManager::GetPlugin(item->PluginIndex()); if (p) { cMenuSetupPage *menu = p->SetupMenu(); if (menu) { menu->SetPlugin(p); return AddSubMenu(menu); } Skins.Message(mtInfo, tr("This plugin has no setup parameters!")); } } } else if (state == osContinue) { Store(); // Reinitialize OSD and skin, in case any plugin setup change has an influence on these: cOsdProvider::UpdateOsdSize(true); SetDisplayMenu(); Display(); } } return state; } // --- cMenuSetup ------------------------------------------------------------ class cMenuSetup : public cOsdMenu { private: virtual void Set(void); eOSState Restart(void); public: cMenuSetup(void); virtual eOSState ProcessKey(eKeys Key); }; cMenuSetup::cMenuSetup(void) :cOsdMenu("") { SetMenuCategory(mcSetup); Set(); } void cMenuSetup::Set(void) { Clear(); char buffer[64]; snprintf(buffer, sizeof(buffer), "%s - VDR %s", tr("Setup"), VDRVERSION); SetTitle(buffer); SetHasHotkeys(); Add(new cOsdItem(hk(tr("OSD")), osUser1)); Add(new cOsdItem(hk(tr("EPG")), osUser2)); Add(new cOsdItem(hk(tr("DVB")), osUser3)); Add(new cOsdItem(hk(tr("LNB")), osUser4)); Add(new cOsdItem(hk(tr("CAM")), osUser5)); Add(new cOsdItem(hk(tr("Recording")), osUser6)); Add(new cOsdItem(hk(tr("Replay")), osUser7)); Add(new cOsdItem(hk(tr("Miscellaneous")), osUser8)); if (cPluginManager::HasPlugins()) Add(new cOsdItem(hk(tr("Plugins")), osUser9)); Add(new cOsdItem(hk(tr("Restart")), osUser10)); } eOSState cMenuSetup::Restart(void) { if (Interface->Confirm(tr("Really restart?")) && ShutdownHandler.ConfirmRestart(true)) { ShutdownHandler.Exit(1); return osEnd; } return osContinue; } eOSState cMenuSetup::ProcessKey(eKeys Key) { int osdLanguage = I18nCurrentLanguage(); eOSState state = cOsdMenu::ProcessKey(Key); switch (state) { case osUser1: return AddSubMenu(new cMenuSetupOSD); case osUser2: return AddSubMenu(new cMenuSetupEPG); case osUser3: return AddSubMenu(new cMenuSetupDVB); case osUser4: return AddSubMenu(new cMenuSetupLNB); case osUser5: return AddSubMenu(new cMenuSetupCAM); case osUser6: return AddSubMenu(new cMenuSetupRecord); case osUser7: return AddSubMenu(new cMenuSetupReplay); case osUser8: return AddSubMenu(new cMenuSetupMisc); case osUser9: return AddSubMenu(new cMenuSetupPlugins); case osUser10: return Restart(); default: ; } if (I18nCurrentLanguage() != osdLanguage) { Set(); if (!HasSubMenu()) Display(); } return state; } // --- cMenuPluginItem ------------------------------------------------------- class cMenuPluginItem : public cOsdItem { private: int pluginIndex; public: cMenuPluginItem(const char *Name, int Index); int PluginIndex(void) { return pluginIndex; } }; cMenuPluginItem::cMenuPluginItem(const char *Name, int Index) :cOsdItem(Name, osPlugin) { pluginIndex = Index; } // --- cMenuMain ------------------------------------------------------------- // TRANSLATORS: note the leading and trailing blanks! #define STOP_RECORDING trNOOP(" Stop recording ") cOsdObject *cMenuMain::pluginOsdObject = NULL; cMenuMain::cMenuMain(eOSState State, bool OpenSubMenus) :cOsdMenu("") { SetMenuCategory(mcMain); replaying = false; stopReplayItem = NULL; cancelEditingItem = NULL; stopRecordingItem = NULL; recordControlsState = 0; Set(); // Initial submenus: switch (State) { case osSchedule: AddSubMenu(new cMenuSchedule); break; case osChannels: AddSubMenu(new cMenuChannels); break; case osTimers: AddSubMenu(new cMenuTimers); break; case osRecordings: AddSubMenu(new cMenuRecordings(NULL, 0, OpenSubMenus)); break; case osSetup: AddSubMenu(new cMenuSetup); break; case osCommands: AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); break; default: break; } } cOsdObject *cMenuMain::PluginOsdObject(void) { cOsdObject *o = pluginOsdObject; pluginOsdObject = NULL; return o; } void cMenuMain::Set(void) { Clear(); SetTitle("VDR"); SetHasHotkeys(); // Basic menu items: Add(new cOsdItem(hk(tr("Schedule")), osSchedule)); Add(new cOsdItem(hk(tr("Channels")), osChannels)); Add(new cOsdItem(hk(tr("Timers")), osTimers)); Add(new cOsdItem(hk(tr("Recordings")), osRecordings)); // Plugins: for (int i = 0; ; i++) { cPlugin *p = cPluginManager::GetPlugin(i); if (p) { const char *item = p->MainMenuEntry(); if (item) Add(new cMenuPluginItem(hk(item), i)); } else break; } // More basic menu items: Add(new cOsdItem(hk(tr("Setup")), osSetup)); if (Commands.Count()) Add(new cOsdItem(hk(tr("Commands")), osCommands)); Update(true); Display(); } bool cMenuMain::Update(bool Force) { bool result = false; bool NewReplaying = cControl::Control() != NULL; if (Force || NewReplaying != replaying) { replaying = NewReplaying; // Replay control: if (replaying && !stopReplayItem) // TRANSLATORS: note the leading blank! Add(stopReplayItem = new cOsdItem(tr(" Stop replaying"), osStopReplay)); else if (stopReplayItem && !replaying) { Del(stopReplayItem->Index()); stopReplayItem = NULL; } // Color buttons: SetHelp(!replaying ? tr("Button$Record") : NULL, tr("Button$Audio"), replaying || !Setup.PauseKeyHandling ? NULL : tr("Button$Pause"), replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Button$Resume") : tr("Button$Play")); result = true; } // Editing control: bool EditingActive = RecordingsHandler.Active(); if (EditingActive && !cancelEditingItem) { // TRANSLATORS: note the leading blank! Add(cancelEditingItem = new cOsdItem(tr(" Cancel editing"), osCancelEdit)); result = true; } else if (cancelEditingItem && !EditingActive) { Del(cancelEditingItem->Index()); cancelEditingItem = NULL; result = true; } // Record control: if (cRecordControls::StateChanged(recordControlsState)) { while (stopRecordingItem) { cOsdItem *it = Next(stopRecordingItem); Del(stopRecordingItem->Index()); stopRecordingItem = it; } const char *s = NULL; while ((s = cRecordControls::GetInstantId(s)) != NULL) { cOsdItem *item = new cOsdItem(osStopRecord); item->SetText(cString::sprintf("%s%s", tr(STOP_RECORDING), s)); Add(item); if (!stopRecordingItem) stopRecordingItem = item; } result = true; } return result; } eOSState cMenuMain::ProcessKey(eKeys Key) { bool HadSubMenu = HasSubMenu(); int osdLanguage = I18nCurrentLanguage(); eOSState state = cOsdMenu::ProcessKey(Key); HadSubMenu |= HasSubMenu(); switch (state) { case osSchedule: return AddSubMenu(new cMenuSchedule); case osChannels: return AddSubMenu(new cMenuChannels); case osTimers: return AddSubMenu(new cMenuTimers); case osRecordings: return AddSubMenu(new cMenuRecordings); case osSetup: return AddSubMenu(new cMenuSetup); case osCommands: return AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) { if (cOsdItem *item = Get(Current())) { cRecordControls::Stop(item->Text() + strlen(tr(STOP_RECORDING))); return osEnd; } } break; case osCancelEdit: if (Interface->Confirm(tr("Cancel editing?"))) { RecordingsHandler.DelAll(); return osEnd; } break; case osPlugin: { cMenuPluginItem *item = (cMenuPluginItem *)Get(Current()); if (item) { cPlugin *p = cPluginManager::GetPlugin(item->PluginIndex()); if (p) { cOsdObject *menu = p->MainMenuAction(); if (menu) { if (menu->IsMenu()) return AddSubMenu((cOsdMenu *)menu); else { pluginOsdObject = menu; return osPlugin; } } } } state = osEnd; } break; default: switch (Key) { case kRecord: case kRed: if (!HadSubMenu) state = replaying ? osContinue : osRecord; break; case kGreen: if (!HadSubMenu) { cRemote::Put(kAudio, true); state = osEnd; } break; case kYellow: if (!HadSubMenu) state = replaying || !Setup.PauseKeyHandling ? osContinue : osPause; break; case kBlue: if (!HadSubMenu) state = replaying ? osStopReplay : cReplayControl::LastReplayed() ? osReplay : osRecordings; break; default: break; } } if (!HasSubMenu() && Update(HadSubMenu)) Display(); if (Key != kNone) { if (I18nCurrentLanguage() != osdLanguage) { Set(); if (!HasSubMenu()) Display(); } } return state; } // --- SetTrackDescriptions -------------------------------------------------- static void SetTrackDescriptions(int LiveChannel) { cDevice::PrimaryDevice()->ClrAvailableTracks(true); const cComponents *Components = NULL; if (LiveChannel) { LOCK_CHANNELS_READ; if (const cChannel *Channel = Channels->GetByNumber(LiveChannel)) { LOCK_SCHEDULES_READ; if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { const cEvent *Present = Schedule->GetPresentEvent(); if (Present) Components = Present->Components(); } } } else if (cReplayControl::NowReplaying()) { LOCK_RECORDINGS_READ; if (const cRecording *Recording = Recordings->GetByName(cReplayControl::NowReplaying())) Components = Recording->Info()->Components(); } if (Components) { int indexAudio = 0; int indexDolby = 0; int indexSubtitle = 0; for (int i = 0; i < Components->NumComponents(); i++) { const tComponent *p = Components->Component(i); switch (p->stream) { case 2: if (p->type == 0x05) cDevice::PrimaryDevice()->SetAvailableTrack(ttDolby, indexDolby++, 0, LiveChannel ? NULL : p->language, p->description); else cDevice::PrimaryDevice()->SetAvailableTrack(ttAudio, indexAudio++, 0, LiveChannel ? NULL : p->language, p->description); break; case 3: cDevice::PrimaryDevice()->SetAvailableTrack(ttSubtitle, indexSubtitle++, 0, LiveChannel ? NULL : p->language, p->description); break; case 4: cDevice::PrimaryDevice()->SetAvailableTrack(ttDolby, indexDolby++, 0, LiveChannel ? NULL : p->language, p->description); break; default: ; } } } } // --- cDisplayChannel ------------------------------------------------------- cDisplayChannel *cDisplayChannel::currentDisplayChannel = NULL; cDisplayChannel::cDisplayChannel(int Number, bool Switched) :cOsdObject(true) { currentDisplayChannel = this; group = -1; withInfo = !Switched || Setup.ShowInfoOnChSwitch; displayChannel = Skins.Current()->DisplayChannel(withInfo); number = 0; timeout = Switched || Setup.TimeoutRequChInfo; cOsdProvider::OsdSizeChanged(osdState); // just to get the current state positioner = NULL; channel = NULL; LOCK_CHANNELS_READ; channel = Channels->GetByNumber(Number); lastPresent = lastFollowing = NULL; if (channel) { DisplayChannel(); DisplayInfo(); displayChannel->Flush(); } lastTime.Set(); } cDisplayChannel::cDisplayChannel(eKeys FirstKey) :cOsdObject(true) { currentDisplayChannel = this; group = -1; number = 0; timeout = true; lastPresent = lastFollowing = NULL; lastTime.Set(); withInfo = Setup.ShowInfoOnChSwitch; displayChannel = Skins.Current()->DisplayChannel(withInfo); positioner = NULL; channel = NULL; LOCK_CHANNELS_READ; channel = Channels->GetByNumber(cDevice::CurrentChannel()); ProcessKey(FirstKey); } cDisplayChannel::~cDisplayChannel() { delete displayChannel; cStatus::MsgOsdClear(); currentDisplayChannel = NULL; } void cDisplayChannel::DisplayChannel(void) { displayChannel->SetChannel(channel, number); cStatus::MsgOsdChannel(ChannelString(channel, number)); lastPresent = lastFollowing = NULL; } void cDisplayChannel::DisplayInfo(void) { if (withInfo && channel) { LOCK_SCHEDULES_READ; if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) { const cEvent *Present = Schedule->GetPresentEvent(); const cEvent *Following = Schedule->GetFollowingEvent(); if (Present != lastPresent || Following != lastFollowing) { SetTrackDescriptions(channel->Number()); displayChannel->SetEvents(Present, Following); cStatus::MsgOsdProgramme(Present ? Present->StartTime() : 0, Present ? Present->Title() : NULL, Present ? Present->ShortText() : NULL, Following ? Following->StartTime() : 0, Following ? Following->Title() : NULL, Following ? Following->ShortText() : NULL); lastPresent = Present; lastFollowing = Following; } } } } void cDisplayChannel::Refresh(void) { DisplayChannel(); displayChannel->SetEvents(NULL, NULL); } const cChannel *cDisplayChannel::NextAvailableChannel(const cChannel *Channel, int Direction) { if (Direction) { LOCK_CHANNELS_READ; while (Channel) { Channel = Direction > 0 ? Channels->Next(Channel) : Channels->Prev(Channel); if (!Channel && Setup.ChannelsWrap) Channel = Direction > 0 ? Channels->First() : Channels->Last(); if (Channel && !Channel->GroupSep() && cDevice::GetDevice(Channel, LIVEPRIORITY, true, true)) return Channel; } } return NULL; } eOSState cDisplayChannel::ProcessKey(eKeys Key) { if (cOsdProvider::OsdSizeChanged(osdState)) { delete displayChannel; displayChannel = Skins.Current()->DisplayChannel(withInfo); } const cChannel *NewChannel = NULL; if (Key != kNone) lastTime.Set(); switch (int(Key)) { case k0: if (number == 0) { // keep the "Toggle channels" function working cRemote::Put(Key); return osEnd; } case k1 ... k9: group = -1; if (number >= 0) { if (number > cChannels::MaxNumber()) number = Key - k0; else number = number * 10 + Key - k0; LOCK_CHANNELS_READ channel = Channels->GetByNumber(number); Refresh(); withInfo = false; // Lets see if there can be any useful further input: int n = channel ? number * 10 : 0; int m = 10; const cChannel *ch = channel; while (ch && (ch = Channels->Next(ch)) != NULL) { if (!ch->GroupSep()) { if (n <= ch->Number() && ch->Number() < n + m) { n = 0; break; } if (ch->Number() > n) { n *= 10; m *= 10; } } } if (n > 0) { // This channel is the only one that fits the input, so let's take it right away: NewChannel = channel; withInfo = true; number = 0; Refresh(); } } break; case kLeft|k_Repeat: case kLeft: case kRight|k_Repeat: case kRight: case kNext|k_Repeat: case kNext: case kPrev|k_Repeat: case kPrev: { withInfo = false; number = 0; LOCK_CHANNELS_READ; if (group < 0) { if (const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel())) group = Channel->Index(); } if (group >= 0) { int SaveGroup = group; if (NORMALKEY(Key) == kRight || NORMALKEY(Key) == kNext) group = Channels->GetNextGroup(group) ; else group = Channels->GetPrevGroup(group < 1 ? 1 : group); if (group < 0) group = SaveGroup; channel = Channels->Get(group); if (channel) { Refresh(); if (!channel->GroupSep()) group = -1; } } break; } case kUp|k_Repeat: case kUp: case kDown|k_Repeat: case kDown: case kChanUp|k_Repeat: case kChanUp: case kChanDn|k_Repeat: case kChanDn: { eKeys k = NORMALKEY(Key); if (const cChannel *Channel = NextAvailableChannel(channel, (k == kUp || k == kChanUp) ? 1 : -1)) channel = Channel; else if (channel && channel->Number() != cDevice::CurrentChannel()) Key = k; // immediately switches channel when hitting the beginning/end of the channel list with k_Repeat } // no break here case kUp|k_Release: case kDown|k_Release: case kChanUp|k_Release: case kChanDn|k_Release: case kNext|k_Release: case kPrev|k_Release: if (!(Key & k_Repeat) && channel && channel->Number() != cDevice::CurrentChannel()) NewChannel = channel; withInfo = true; group = -1; number = 0; Refresh(); break; case kNone: if (number && Setup.ChannelEntryTimeout && int(lastTime.Elapsed()) > Setup.ChannelEntryTimeout) { LOCK_CHANNELS_READ; channel = Channels->GetByNumber(number); if (channel) NewChannel = channel; withInfo = true; number = 0; Refresh(); lastTime.Set(); } break; //TODO //XXX case kGreen: return osEventNow; //XXX case kYellow: return osEventNext; case kOk: { LOCK_CHANNELS_READ; if (group >= 0) { channel = Channels->Get(Channels->GetNextNormal(group)); if (channel) NewChannel = channel; withInfo = true; group = -1; Refresh(); } else if (number > 0) { channel = Channels->GetByNumber(number); if (channel) NewChannel = channel; withInfo = true; number = 0; Refresh(); } else { return osEnd; } } break; default: if ((Key & (k_Repeat | k_Release)) == 0) { cRemote::Put(Key); return osEnd; } }; if (positioner || !timeout || lastTime.Elapsed() < (uint64_t)(Setup.ChannelInfoTime * 1000)) { LOCK_CHANNELS_READ; if (Key == kNone && !number && group < 0 && !NewChannel && channel && channel->Number() != cDevice::CurrentChannel()) { // makes sure a channel switch through the SVDRP CHAN command is displayed channel = Channels->GetByNumber(cDevice::CurrentChannel()); Refresh(); lastTime.Set(); } DisplayInfo(); if (NewChannel) { SetTrackDescriptions(NewChannel->Number()); // to make them immediately visible in the channel display Channels->SwitchTo(NewChannel->Number()); SetTrackDescriptions(NewChannel->Number()); // switching the channel has cleared them channel = NewChannel; } const cPositioner *Positioner = cDevice::ActualDevice()->Positioner(); bool PositionerMoving = Positioner && Positioner->IsMoving(); SetNeedsFastResponse(PositionerMoving); if (!PositionerMoving) { if (positioner) lastTime.Set(); // to keep the channel display up a few seconds after the target position has been reached Positioner = NULL; } if (Positioner || positioner) // making sure we call SetPositioner(NULL) if there is a switch from "with" to "without" positioner displayChannel->SetPositioner(Positioner); positioner = Positioner; displayChannel->Flush(); return osContinue; } return osEnd; } // --- cDisplayVolume -------------------------------------------------------- #define VOLUMETIMEOUT 1000 //ms #define MUTETIMEOUT 5000 //ms cDisplayVolume *cDisplayVolume::currentDisplayVolume = NULL; cDisplayVolume::cDisplayVolume(void) :cOsdObject(true) { currentDisplayVolume = this; timeout.Set(cDevice::PrimaryDevice()->IsMute() ? MUTETIMEOUT : VOLUMETIMEOUT); displayVolume = Skins.Current()->DisplayVolume(); Show(); } cDisplayVolume::~cDisplayVolume() { delete displayVolume; currentDisplayVolume = NULL; } void cDisplayVolume::Show(void) { displayVolume->SetVolume(cDevice::CurrentVolume(), MAXVOLUME, cDevice::PrimaryDevice()->IsMute()); } cDisplayVolume *cDisplayVolume::Create(void) { if (!currentDisplayVolume) new cDisplayVolume; return currentDisplayVolume; } void cDisplayVolume::Process(eKeys Key) { if (currentDisplayVolume) currentDisplayVolume->ProcessKey(Key); } eOSState cDisplayVolume::ProcessKey(eKeys Key) { switch (int(Key)) { case kVolUp|k_Repeat: case kVolUp: case kVolDn|k_Repeat: case kVolDn: Show(); timeout.Set(VOLUMETIMEOUT); break; case kMute: if (cDevice::PrimaryDevice()->IsMute()) { Show(); timeout.Set(MUTETIMEOUT); } else timeout.Set(); break; case kNone: break; default: if ((Key & k_Release) == 0) { cRemote::Put(Key); return osEnd; } } return timeout.TimedOut() ? osEnd : osContinue; } // --- cDisplayTracks -------------------------------------------------------- #define TRACKTIMEOUT 5000 //ms cDisplayTracks *cDisplayTracks::currentDisplayTracks = NULL; cDisplayTracks::cDisplayTracks(void) :cOsdObject(true) { cDevice::PrimaryDevice()->EnsureAudioTrack(); SetTrackDescriptions(!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring() ? cDevice::CurrentChannel() : 0); currentDisplayTracks = this; numTracks = track = 0; audioChannel = cDevice::PrimaryDevice()->GetAudioChannel(); eTrackType CurrentAudioTrack = cDevice::PrimaryDevice()->GetCurrentAudioTrack(); for (int i = ttAudioFirst; i <= ttDolbyLast; i++) { const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(i)); if (TrackId && TrackId->id) { types[numTracks] = eTrackType(i); descriptions[numTracks] = strdup(*TrackId->description ? TrackId->description : *TrackId->language ? TrackId->language : *itoa(i)); if (i == CurrentAudioTrack) track = numTracks; numTracks++; } } descriptions[numTracks] = NULL; timeout.Set(TRACKTIMEOUT); displayTracks = Skins.Current()->DisplayTracks(tr("Button$Audio"), numTracks, descriptions); Show(); } cDisplayTracks::~cDisplayTracks() { delete displayTracks; currentDisplayTracks = NULL; for (int i = 0; i < numTracks; i++) free(descriptions[i]); cStatus::MsgOsdClear(); } void cDisplayTracks::Show(void) { int ac = IS_AUDIO_TRACK(types[track]) ? audioChannel : -1; displayTracks->SetTrack(track, descriptions); displayTracks->SetAudioChannel(ac); displayTracks->Flush(); cStatus::MsgSetAudioTrack(track, descriptions); cStatus::MsgSetAudioChannel(ac); } cDisplayTracks *cDisplayTracks::Create(void) { if (cDevice::PrimaryDevice()->NumAudioTracks() > 0) { if (!currentDisplayTracks) new cDisplayTracks; return currentDisplayTracks; } Skins.Message(mtWarning, tr("No audio available!")); return NULL; } void cDisplayTracks::Process(eKeys Key) { if (currentDisplayTracks) currentDisplayTracks->ProcessKey(Key); } eOSState cDisplayTracks::ProcessKey(eKeys Key) { int oldTrack = track; int oldAudioChannel = audioChannel; switch (int(Key)) { case kUp|k_Repeat: case kUp: case kDown|k_Repeat: case kDown: if (NORMALKEY(Key) == kUp && track > 0) track--; else if (NORMALKEY(Key) == kDown && track < numTracks - 1) track++; timeout.Set(TRACKTIMEOUT); break; case kLeft|k_Repeat: case kLeft: case kRight|k_Repeat: case kRight: if (IS_AUDIO_TRACK(types[track])) { static int ac[] = { 1, 0, 2 }; audioChannel = ac[cDevice::PrimaryDevice()->GetAudioChannel()]; if (NORMALKEY(Key) == kLeft && audioChannel > 0) audioChannel--; else if (NORMALKEY(Key) == kRight && audioChannel < 2) audioChannel++; audioChannel = ac[audioChannel]; timeout.Set(TRACKTIMEOUT); } break; case kAudio|k_Repeat: case kAudio: if (++track >= numTracks) track = 0; timeout.Set(TRACKTIMEOUT); break; case kOk: if (types[track] != cDevice::PrimaryDevice()->GetCurrentAudioTrack()) oldTrack = -1; // make sure we explicitly switch to that track timeout.Set(); break; case kNone: break; default: if ((Key & k_Release) == 0) return osEnd; } if (track != oldTrack || audioChannel != oldAudioChannel) Show(); if (track != oldTrack) { cDevice::PrimaryDevice()->SetCurrentAudioTrack(types[track]); Setup.CurrentDolby = IS_DOLBY_TRACK(types[track]); } if (audioChannel != oldAudioChannel) cDevice::PrimaryDevice()->SetAudioChannel(audioChannel); return timeout.TimedOut() ? osEnd : osContinue; } // --- cDisplaySubtitleTracks ------------------------------------------------ cDisplaySubtitleTracks *cDisplaySubtitleTracks::currentDisplayTracks = NULL; cDisplaySubtitleTracks::cDisplaySubtitleTracks(void) :cOsdObject(true) { SetTrackDescriptions(!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring() ? cDevice::CurrentChannel() : 0); currentDisplayTracks = this; numTracks = track = 0; types[numTracks] = ttNone; descriptions[numTracks] = strdup(tr("No subtitles")); numTracks++; eTrackType CurrentSubtitleTrack = cDevice::PrimaryDevice()->GetCurrentSubtitleTrack(); for (int i = ttSubtitleFirst; i <= ttSubtitleLast; i++) { const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(i)); if (TrackId && TrackId->id) { types[numTracks] = eTrackType(i); descriptions[numTracks] = strdup(*TrackId->description ? TrackId->description : *TrackId->language ? TrackId->language : *itoa(i)); if (i == CurrentSubtitleTrack) track = numTracks; numTracks++; } } descriptions[numTracks] = NULL; timeout.Set(TRACKTIMEOUT); displayTracks = Skins.Current()->DisplayTracks(tr("Button$Subtitles"), numTracks, descriptions); Show(); } cDisplaySubtitleTracks::~cDisplaySubtitleTracks() { delete displayTracks; currentDisplayTracks = NULL; for (int i = 0; i < numTracks; i++) free(descriptions[i]); cStatus::MsgOsdClear(); } void cDisplaySubtitleTracks::Show(void) { displayTracks->SetTrack(track, descriptions); displayTracks->Flush(); cStatus::MsgSetSubtitleTrack(track, descriptions); } cDisplaySubtitleTracks *cDisplaySubtitleTracks::Create(void) { if (cDevice::PrimaryDevice()->NumSubtitleTracks() > 0) { if (!currentDisplayTracks) new cDisplaySubtitleTracks; return currentDisplayTracks; } Skins.Message(mtWarning, tr("No subtitles available!")); return NULL; } void cDisplaySubtitleTracks::Process(eKeys Key) { if (currentDisplayTracks) currentDisplayTracks->ProcessKey(Key); } eOSState cDisplaySubtitleTracks::ProcessKey(eKeys Key) { int oldTrack = track; switch (int(Key)) { case kUp|k_Repeat: case kUp: case kDown|k_Repeat: case kDown: if (NORMALKEY(Key) == kUp && track > 0) track--; else if (NORMALKEY(Key) == kDown && track < numTracks - 1) track++; timeout.Set(TRACKTIMEOUT); break; case kSubtitles|k_Repeat: case kSubtitles: if (++track >= numTracks) track = 0; timeout.Set(TRACKTIMEOUT); break; case kOk: if (types[track] != cDevice::PrimaryDevice()->GetCurrentSubtitleTrack()) oldTrack = -1; // make sure we explicitly switch to that track timeout.Set(); break; case kNone: break; default: if ((Key & k_Release) == 0) return osEnd; } if (track != oldTrack) { Show(); cDevice::PrimaryDevice()->SetCurrentSubtitleTrack(types[track], true); } return timeout.TimedOut() ? osEnd : osContinue; } // --- cRecordControl -------------------------------------------------------- cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause) { // Whatever happens here, the timers will be modified in some way... Timers->SetModified(); // We're going to work with an event here, so we need to prevent // others from modifying any EPG data: LOCK_SCHEDULES_READ; event = NULL; fileName = NULL; recorder = NULL; device = Device; if (!device) device = cDevice::PrimaryDevice();//XXX timer = Timer; if (!timer) { timer = new cTimer(true, Pause); Timers->Add(timer); instantId = cString::sprintf(cDevice::NumDevices() > 1 ? "%s - %d" : "%s", timer->Channel()->Name(), device->CardIndex() + 1); } timer->SetPending(true); timer->SetRecording(true); event = timer->Event(); if (event || GetEvent()) dsyslog("Title: '%s' Subtitle: '%s'", event->Title(), event->ShortText()); cRecording Recording(timer, event); fileName = strdup(Recording.FileName()); // crude attempt to avoid duplicate recordings: if (cRecordControls::GetRecordControl(fileName)) { isyslog("already recording: '%s'", fileName); if (Timer) { timer->SetPending(false); timer->SetRecording(false); timer->OnOff(); } else { Timers->Del(timer); if (!cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo() cReplayControl::SetRecording(fileName); } timer = NULL; return; } cRecordingUserCommand::InvokeCommand(RUC_BEFORERECORDING, fileName); isyslog("record %s", fileName); if (MakeDirs(fileName, true)) { const cChannel *ch = timer->Channel(); recorder = new cRecorder(fileName, ch, timer->Priority()); if (device->AttachReceiver(recorder)) { Recording.WriteInfo(); cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true); if (!Timer && !cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo() cReplayControl::SetRecording(fileName); LOCK_RECORDINGS_WRITE; Recordings->AddByName(fileName); return; } else DELETENULL(recorder); } else timer->SetDeferred(DEFERTIMER); if (!Timer) { Timers->Del(timer); timer = NULL; } } cRecordControl::~cRecordControl() { Stop(); free(fileName); } #define INSTANT_REC_EPG_LOOKAHEAD 300 // seconds to look into the EPG data for an instant recording bool cRecordControl::GetEvent(void) { const cChannel *Channel = timer->Channel(); time_t Time = timer->HasFlags(tfInstant) ? timer->StartTime() + INSTANT_REC_EPG_LOOKAHEAD : timer->StartTime() + (timer->StopTime() - timer->StartTime()) / 2; for (int seconds = 0; seconds <= MAXWAIT4EPGINFO; seconds++) { { LOCK_SCHEDULES_READ; if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) { event = Schedule->GetEventAround(Time); if (event) { if (seconds > 0) dsyslog("got EPG info after %d seconds", seconds); return true; } } } if (seconds == 0) dsyslog("waiting for EPG info..."); cCondWait::SleepMs(1000); } dsyslog("no EPG info available"); return false; } void cRecordControl::Stop(bool ExecuteUserCommand) { if (timer) { DELETENULL(recorder); timer->SetRecording(false); timer = NULL; cStatus::MsgRecording(device, NULL, fileName, false); if (ExecuteUserCommand) cRecordingUserCommand::InvokeCommand(RUC_AFTERRECORDING, fileName); } } bool cRecordControl::Process(time_t t) { if (!recorder || !recorder->IsAttached() || !timer || !timer->Matches(t)) { if (timer) timer->SetPending(false); return false; } AssertFreeDiskSpace(timer->Priority()); return true; } // --- cRecordControls ------------------------------------------------------- cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL }; int cRecordControls::state = 0; bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause) { static time_t LastNoDiskSpaceMessage = 0; int FreeMB = 0; if (Timer) { AssertFreeDiskSpace(Timer->Priority(), !Timer->Pending()); Timer->SetPending(true); } cVideoDirectory::VideoDiskSpace(&FreeMB); if (FreeMB < MINFREEDISK) { if (!Timer || time(NULL) - LastNoDiskSpaceMessage > NODISKSPACEDELTA) { isyslog("not enough disk space to start recording%s%s", Timer ? " timer " : "", Timer ? *Timer->ToDescr() : ""); Skins.Message(mtWarning, tr("Not enough disk space to start recording!")); LastNoDiskSpaceMessage = time(NULL); } return false; } LastNoDiskSpaceMessage = 0; ChangeState(); LOCK_CHANNELS_READ; int ch = Timer ? Timer->Channel()->Number() : cDevice::CurrentChannel(); if (const cChannel *Channel = Channels->GetByNumber(ch)) { int Priority = Timer ? Timer->Priority() : Pause ? Setup.PausePriority : Setup.DefaultPriority; cDevice *device = cDevice::GetDevice(Channel, Priority, false); if (device) { dsyslog("switching device %d to channel %d (%s)", device->DeviceNumber() + 1, Channel->Number(), Channel->Name()); if (!device->SwitchChannel(Channel, false)) { ShutdownHandler.RequestEmergencyExit(); return false; } if (!Timer || Timer->Matches()) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (!RecordControls[i]) { RecordControls[i] = new cRecordControl(device, Timers, Timer, Pause); return RecordControls[i]->Process(time(NULL)); } } } } else if (!Timer || !Timer->Pending()) { isyslog("no free DVB device to record channel %d (%s)!", ch, Channel->Name()); Skins.Message(mtError, tr("No free DVB device to record!")); } } else esyslog("ERROR: channel %d not defined!", ch); return false; } bool cRecordControls::Start(bool Pause) { LOCK_TIMERS_WRITE; return Start(Timers, NULL, Pause); } void cRecordControls::Stop(const char *InstantId) { LOCK_TIMERS_WRITE; ChangeState(); for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) { const char *id = RecordControls[i]->InstantId(); if (id && strcmp(id, InstantId) == 0) { cTimer *Timer = RecordControls[i]->Timer(); RecordControls[i]->Stop(); if (Timer) { isyslog("deleting timer %s", *Timer->ToDescr()); Timers->Del(Timer); } break; } } } } bool cRecordControls::PauseLiveVideo(void) { Skins.Message(mtStatus, tr("Pausing live video...")); cReplayControl::SetRecording(NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed() if (Start(true)) { cReplayControl *rc = new cReplayControl(true); cControl::Launch(rc); cControl::Attach(); Skins.Message(mtStatus, NULL); return true; } Skins.Message(mtStatus, NULL); return false; } const char *cRecordControls::GetInstantId(const char *LastInstantId) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) { if (!LastInstantId && RecordControls[i]->InstantId()) return RecordControls[i]->InstantId(); if (LastInstantId && LastInstantId == RecordControls[i]->InstantId()) LastInstantId = NULL; } } return NULL; } cRecordControl *cRecordControls::GetRecordControl(const char *FileName) { if (FileName) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i] && strcmp(RecordControls[i]->FileName(), FileName) == 0) return RecordControls[i]; } } return NULL; } cRecordControl *cRecordControls::GetRecordControl(const cTimer *Timer) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i] && RecordControls[i]->Timer() == Timer) return RecordControls[i]; } return NULL; } bool cRecordControls::Process(cTimers *Timers, time_t t) { bool Result = false; for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) { if (!RecordControls[i]->Process(t)) { DELETENULL(RecordControls[i]); ChangeState(); Result = true; } } } return Result; } void cRecordControls::ChannelDataModified(const cChannel *Channel) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) { if (RecordControls[i]->Timer() && RecordControls[i]->Timer()->Channel() == Channel) { if (RecordControls[i]->Device()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder isyslog("stopping recording due to modification of channel %d (%s)", Channel->Number(), Channel->Name()); RecordControls[i]->Stop(); // This will restart the recording, maybe even from a different // device in case conditional access has changed. ChangeState(); } } } } } bool cRecordControls::Active(void) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) return true; } return false; } void cRecordControls::Shutdown(void) { for (int i = 0; i < MAXRECORDCONTROLS; i++) DELETENULL(RecordControls[i]); ChangeState(); } bool cRecordControls::StateChanged(int &State) { int NewState = state; bool Result = State != NewState; State = state; return Result; } // --- cAdaptiveSkipper ------------------------------------------------------ cAdaptiveSkipper::cAdaptiveSkipper(void) { initialValue = NULL; currentValue = 0; framesPerSecond = 0; lastKey = kNone; } void cAdaptiveSkipper::Initialize(int *InitialValue, double FramesPerSecond) { initialValue = InitialValue; framesPerSecond = FramesPerSecond; currentValue = 0; } int cAdaptiveSkipper::GetValue(eKeys Key) { if (!initialValue) return 0; if (timeout.TimedOut()) { currentValue = int(round(*initialValue * framesPerSecond)); lastKey = Key; } else if (Key != lastKey) { currentValue /= 2; if (Setup.AdaptiveSkipAlternate) lastKey = Key; // only halve the value when the direction is changed else lastKey = kNone; // once the direction has changed, every further call halves the value } timeout.Set(Setup.AdaptiveSkipTimeout * 1000); return max(currentValue, 1); } // --- cReplayControl -------------------------------------------------------- cReplayControl *cReplayControl::currentReplayControl = NULL; cString cReplayControl::fileName; cReplayControl::cReplayControl(bool PauseLive) :cDvbPlayerControl(fileName, PauseLive) { cDevice::PrimaryDevice()->SetKeepTracks(PauseLive); currentReplayControl = this; displayReplay = NULL; marksModified = false; visible = modeOnly = shown = displayFrames = false; lastCurrent = lastTotal = -1; lastPlay = lastForward = false; lastSpeed = -2; // an invalid value timeoutShow = 0; timeSearchActive = false; cRecording Recording(fileName); cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true); marks.Load(fileName, Recording.FramesPerSecond(), Recording.IsPesRecording()); SetMarks(&marks); adaptiveSkipper.Initialize(&Setup.AdaptiveSkipInitial, Recording.FramesPerSecond()); SetTrackDescriptions(false); if (Setup.ProgressDisplayTime) ShowTimed(Setup.ProgressDisplayTime); } cReplayControl::~cReplayControl() { cDevice::PrimaryDevice()->SetKeepTracks(false); Hide(); cStatus::MsgReplaying(this, NULL, fileName, false); Stop(); if (currentReplayControl == this) currentReplayControl = NULL; } void cReplayControl::Stop(void) { if (Setup.DelTimeshiftRec && *fileName) { cRecordControl* rc = cRecordControls::GetRecordControl(fileName); if (rc && rc->InstantId()) { if (Active()) { if (Setup.DelTimeshiftRec == 2 || Interface->Confirm(tr("Delete timeshift recording?"))) { { LOCK_TIMERS_WRITE; Timers->SetExplicitModify(); cTimer *Timer = rc->Timer(); rc->Stop(false); // don't execute user command if (Timer) { isyslog("deleting timer %s", *Timer->ToDescr()); Timers->Del(Timer); Timers->SetModified(); } } cDvbPlayerControl::Stop(); LOCK_RECORDINGS_WRITE; Recordings->SetExplicitModify(); if (cRecording *Recording = Recordings->GetByName(fileName)) { if (Recording->Delete()) { Recordings->DelByName(fileName); ClearLastReplayed(fileName); Recordings->SetModified(); } else Skins.Message(mtError, tr("Error while deleting recording!")); } return; } } } } cDvbPlayerControl::Stop(); cMenuRecordings::SetRecording(NULL); // make sure opening the Recordings menu navigates to the last replayed recording } void cReplayControl::SetRecording(const char *FileName) { fileName = FileName; } const char *cReplayControl::NowReplaying(void) { return currentReplayControl ? *fileName : NULL; } const char *cReplayControl::LastReplayed(void) { LOCK_RECORDINGS_READ; if (!Recordings->GetByName(fileName)) fileName = NULL; return fileName; } void cReplayControl::ClearLastReplayed(const char *FileName) { if (*fileName && FileName && strcmp(fileName, FileName) == 0) fileName = NULL; } void cReplayControl::ShowTimed(int Seconds) { if (modeOnly) Hide(); if (!visible) { shown = ShowProgress(true); timeoutShow = (shown && Seconds > 0) ? time(NULL) + Seconds : 0; } else if (timeoutShow && Seconds > 0) timeoutShow = time(NULL) + Seconds; } void cReplayControl::Show(void) { ShowTimed(); } void cReplayControl::Hide(void) { if (visible) { delete displayReplay; displayReplay = NULL; SetNeedsFastResponse(false); visible = false; modeOnly = false; lastPlay = lastForward = false; lastSpeed = -2; // an invalid value timeSearchActive = false; timeoutShow = 0; } if (marksModified) { marks.Save(); marksModified = false; } } void cReplayControl::ShowMode(void) { if (visible || Setup.ShowReplayMode && !cOsd::IsOpen()) { bool Play, Forward; int Speed; if (GetReplayMode(Play, Forward, Speed) && (!visible || Play != lastPlay || Forward != lastForward || Speed != lastSpeed)) { bool NormalPlay = (Play && Speed == -1); if (!visible) { if (NormalPlay) return; // no need to do indicate ">" unless there was a different mode displayed before visible = modeOnly = true; displayReplay = Skins.Current()->DisplayReplay(modeOnly); } if (modeOnly && !timeoutShow && NormalPlay) timeoutShow = time(NULL) + MODETIMEOUT; displayReplay->SetMode(Play, Forward, Speed); lastPlay = Play; lastForward = Forward; lastSpeed = Speed; } } } bool cReplayControl::ShowProgress(bool Initial) { int Current, Total; if (GetIndex(Current, Total) && Total > 0) { if (!visible) { displayReplay = Skins.Current()->DisplayReplay(modeOnly); displayReplay->SetMarks(&marks); SetNeedsFastResponse(true); visible = true; } if (Initial) { if (*fileName) { LOCK_RECORDINGS_READ; if (const cRecording *Recording = Recordings->GetByName(fileName)) displayReplay->SetRecording(Recording); } lastCurrent = lastTotal = -1; } if (Current != lastCurrent || Total != lastTotal) { if (Setup.ShowRemainingTime || Total != lastTotal) { int Index = Total; if (Setup.ShowRemainingTime) Index = Current - Index; displayReplay->SetTotal(IndexToHMSF(Index, false, FramesPerSecond())); if (!Initial) displayReplay->Flush(); } displayReplay->SetProgress(Current, Total); if (!Initial) displayReplay->Flush(); displayReplay->SetCurrent(IndexToHMSF(Current, displayFrames, FramesPerSecond())); displayReplay->Flush(); lastCurrent = Current; } lastTotal = Total; ShowMode(); return true; } return false; } void cReplayControl::TimeSearchDisplay(void) { char buf[64]; // TRANSLATORS: note the trailing blank! strcpy(buf, tr("Jump: ")); int len = strlen(buf); char h10 = '0' + (timeSearchTime >> 24); char h1 = '0' + ((timeSearchTime & 0x00FF0000) >> 16); char m10 = '0' + ((timeSearchTime & 0x0000FF00) >> 8); char m1 = '0' + (timeSearchTime & 0x000000FF); char ch10 = timeSearchPos > 3 ? h10 : '-'; char ch1 = timeSearchPos > 2 ? h1 : '-'; char cm10 = timeSearchPos > 1 ? m10 : '-'; char cm1 = timeSearchPos > 0 ? m1 : '-'; sprintf(buf + len, "%c%c:%c%c", ch10, ch1, cm10, cm1); displayReplay->SetJump(buf); } void cReplayControl::TimeSearchProcess(eKeys Key) { #define STAY_SECONDS_OFF_END 10 int Seconds = (timeSearchTime >> 24) * 36000 + ((timeSearchTime & 0x00FF0000) >> 16) * 3600 + ((timeSearchTime & 0x0000FF00) >> 8) * 600 + (timeSearchTime & 0x000000FF) * 60; int Current = int(round(lastCurrent / FramesPerSecond())); int Total = int(round(lastTotal / FramesPerSecond())); switch (Key) { case k0 ... k9: if (timeSearchPos < 4) { timeSearchTime <<= 8; timeSearchTime |= Key - k0; timeSearchPos++; TimeSearchDisplay(); } break; case kFastRew: case kLeft: case kFastFwd: case kRight: { int dir = ((Key == kRight || Key == kFastFwd) ? 1 : -1); if (dir > 0) Seconds = min(Total - Current - STAY_SECONDS_OFF_END, Seconds); SkipSeconds(Seconds * dir); timeSearchActive = false; } break; case kPlayPause: case kPlay: case kUp: case kPause: case kDown: case kOk: if (timeSearchPos > 0) { Seconds = min(Total - STAY_SECONDS_OFF_END, Seconds); bool Still = Key == kDown || Key == kPause || Key == kOk; Goto(SecondsToFrames(Seconds, FramesPerSecond()), Still); } timeSearchActive = false; break; default: if (!(Key & k_Flags)) // ignore repeat/release keys timeSearchActive = false; break; } if (!timeSearchActive) { if (timeSearchHide) Hide(); else displayReplay->SetJump(NULL); ShowMode(); } } void cReplayControl::TimeSearch(void) { timeSearchTime = timeSearchPos = 0; timeSearchHide = false; if (modeOnly) Hide(); if (!visible) { Show(); if (visible) timeSearchHide = true; else return; } timeoutShow = 0; TimeSearchDisplay(); timeSearchActive = true; } void cReplayControl::MarkToggle(void) { int Current, Total; if (GetIndex(Current, Total, true)) { lastCurrent = -1; // triggers redisplay cStateKey StateKey; marks.Lock(StateKey); if (cMark *m = marks.Get(Current)) marks.Del(m); else { marks.Add(Current); bool Play, Forward; int Speed; if (Setup.PauseOnMarkSet || GetReplayMode(Play, Forward, Speed) && !Play) { Goto(Current, true); displayFrames = true; } } StateKey.Remove(); ShowTimed(2); marksModified = true; } } void cReplayControl::MarkJump(bool Forward) { int Current, Total; if (GetIndex(Current, Total)) { if (marks.Count()) { if (cMark *m = Forward ? marks.GetNext(Current) : marks.GetPrev(Current)) { if (!Setup.PauseOnMarkJump) { bool Playing, Fwd; int Speed; if (GetReplayMode(Playing, Fwd, Speed) && Playing && Forward && m->Position() < Total - SecondsToFrames(3, FramesPerSecond())) { Goto(m->Position()); return; } } Goto(m->Position(), true); displayFrames = true; return; } } // There are either no marks at all, or we already were at the first or last one, // so jump to the very beginning or end: Goto(Forward ? Total : 0, true); } } void cReplayControl::MarkMove(int Frames, bool MarkRequired) { int Current, Total; if (GetIndex(Current, Total)) { bool Play, Forward; int Speed; GetReplayMode(Play, Forward, Speed); cMark *m = marks.Get(Current); if (!Play && m) { displayFrames = true; cMark *m2; if (Frames > 0) { // Handle marks at the same offset: while ((m2 = marks.Next(m)) != NULL && m2->Position() == m->Position()) m = m2; // Don't skip the next mark: if ((m2 = marks.Next(m)) != NULL) Frames = min(Frames, m2->Position() - m->Position() - 1); } else { // Handle marks at the same offset: while ((m2 = marks.Prev(m)) != NULL && m2->Position() == m->Position()) m = m2; // Don't skip the next mark: if ((m2 = marks.Prev(m)) != NULL) Frames = -min(-Frames, m->Position() - m2->Position() - 1); } int p = SkipFrames(Frames); m->SetPosition(p); Goto(m->Position(), true); marksModified = true; } else if (!MarkRequired) Goto(SkipFrames(Frames), !Play); } } void cReplayControl::EditCut(void) { if (*fileName) { Hide(); if (!RecordingsHandler.GetUsage(fileName)) { if (!marks.Count()) Skins.Message(mtError, tr("No editing marks defined!")); else if (!marks.GetNumSequences()) Skins.Message(mtError, tr("No editing sequences defined!")); else if (access(cCutter::EditedFileName(fileName), F_OK) == 0 && !Interface->Confirm(tr("Edited version already exists - overwrite?"))) ; else if (!RecordingsHandler.Add(ruCut, fileName)) Skins.Message(mtError, tr("Can't start editing process!")); else Skins.Message(mtInfo, tr("Editing process started")); } else Skins.Message(mtError, tr("Editing process already active!")); ShowMode(); } } void cReplayControl::EditTest(void) { int Current, Total; if (GetIndex(Current, Total)) { cMark *m = marks.Get(Current); if (!m) m = marks.GetNext(Current); if (m) { if ((m->Index() & 0x01) != 0 && !Setup.SkipEdited) // when skipping edited parts we also need to jump to end marks m = marks.Next(m); if (m) Goto(m->Position() - SecondsToFrames(3, FramesPerSecond())); } } } cOsdObject *cReplayControl::GetInfo(void) { LOCK_RECORDINGS_READ; if (const cRecording *Recording = Recordings->GetByName(cReplayControl::LastReplayed())) return new cMenuRecording(Recording, false); return NULL; } const cRecording *cReplayControl::GetRecording(void) { LOCK_RECORDINGS_READ; if (const cRecording *Recording = Recordings->GetByName(LastReplayed())) return Recording; return NULL; } eOSState cReplayControl::ProcessKey(eKeys Key) { if (!Active()) return osEnd; if (Key == kNone && !marksModified) marks.Update(); if (visible) { if (timeoutShow && time(NULL) > timeoutShow) { Hide(); ShowMode(); timeoutShow = 0; } else if (modeOnly) ShowMode(); else shown = ShowProgress(!shown) || shown; } bool DisplayedFrames = displayFrames; displayFrames = false; if (timeSearchActive && Key != kNone) { TimeSearchProcess(Key); return osContinue; } if (Key == kPlayPause) { bool Play, Forward; int Speed; GetReplayMode(Play, Forward, Speed); if (Speed >= 0) Key = Play ? kPlay : kPause; else Key = Play ? kPause : kPlay; } bool DoShowMode = true; switch (int(Key)) { // Positioning: case kPlay: case kUp: Play(); break; case kPause: case kDown: Pause(); break; case kFastRew|k_Release: case kLeft|k_Release: if (Setup.MultiSpeedMode) break; case kFastRew: case kLeft: Backward(); break; case kFastFwd|k_Release: case kRight|k_Release: if (Setup.MultiSpeedMode) break; case kFastFwd: case kRight: Forward(); break; case kRed: TimeSearch(); break; case kGreen|k_Repeat: SkipSeconds(-Setup.SkipSecondsRepeat); break; case kGreen: SkipSeconds(-Setup.SkipSeconds); break; case kYellow|k_Repeat: SkipSeconds(Setup.SkipSecondsRepeat); break; case kYellow: SkipSeconds(Setup.SkipSeconds); break; case kStop: case kBlue: Hide(); Stop(); return osEnd; default: { DoShowMode = false; switch (int(Key)) { // Editing: case kMarkToggle: MarkToggle(); break; case kPrev|k_Repeat: case kPrev: if (Setup.AdaptiveSkipPrevNext) { MarkMove(-adaptiveSkipper.GetValue(RAWKEY(Key)), false); break; } // fall through... case kMarkJumpBack|k_Repeat: case kMarkJumpBack: MarkJump(false); break; case kNext|k_Repeat: case kNext: if (Setup.AdaptiveSkipPrevNext) { MarkMove(+adaptiveSkipper.GetValue(RAWKEY(Key)), false); break; } // fall through... case kMarkJumpForward|k_Repeat: case kMarkJumpForward: MarkJump(true); break; case kMarkMoveBack|k_Repeat: case kMarkMoveBack: MarkMove(-1, true); break; case kMarkMoveForward|k_Repeat: case kMarkMoveForward: MarkMove(+1, true); break; case kMarkSkipBack|k_Repeat: case kMarkSkipBack: MarkMove(-adaptiveSkipper.GetValue(RAWKEY(Key)), false); break; case kMarkSkipForward|k_Repeat: case kMarkSkipForward: MarkMove(+adaptiveSkipper.GetValue(RAWKEY(Key)), false); break; case kEditCut: EditCut(); break; case kEditTest: EditTest(); break; default: { displayFrames = DisplayedFrames; switch (Key) { // Menu control: case kOk: if (visible && !modeOnly) { Hide(); DoShowMode = true; } else Show(); break; case kBack: Hide(); Stop(); return osRecordings; default: return osUnknown; } } } } } if (DoShowMode) ShowMode(); return osContinue; }