diff --git a/patches/vdr-2.4.0_zapcockpit.patch b/patches/vdr-2.4.0_zapcockpit.patch new file mode 100644 index 0000000..3a2f43f --- /dev/null +++ b/patches/vdr-2.4.0_zapcockpit.patch @@ -0,0 +1,1118 @@ +diff -Nur vdr-2.4.0/config.c vdr-2.4.0.p/config.c +--- vdr-2.4.0/config.c 2018-02-15 15:40:36.000000000 +0100 ++++ vdr-2.4.0.p/config.c 2019-04-04 14:57:48.234702963 +0200 +@@ -417,6 +417,11 @@ + strcpy(SVDRPDefaultHost, ""); + ZapTimeout = 3; + ChannelEntryTimeout = 1000; ++ ZapcockpitUseGroups = 1; ++ ZapcockpitUseHints = 1; ++ ZapcockpitUseInfo = 1; ++ ZapcockpitHideLastGroup = 0; ++ ZapcockpitShowAllChannels = 0; + RcRepeatDelay = 300; + RcRepeatDelta = 100; + DefaultPriority = 50; +@@ -645,6 +650,11 @@ + else if (!strcasecmp(Name, "SVDRPDefaultHost")) strn0cpy(SVDRPDefaultHost, Value, sizeof(SVDRPDefaultHost)); + else if (!strcasecmp(Name, "ZapTimeout")) ZapTimeout = atoi(Value); + else if (!strcasecmp(Name, "ChannelEntryTimeout")) ChannelEntryTimeout= atoi(Value); ++ else if (!strcasecmp(Name, "ZapcockpitUseGroups")) ZapcockpitUseGroups= atoi(Value); ++ else if (!strcasecmp(Name, "ZapcockpitUseHints")) ZapcockpitUseHints = atoi(Value); ++ else if (!strcasecmp(Name, "ZapcockpitUseInfo")) ZapcockpitUseInfo = atoi(Value); ++ else if (!strcasecmp(Name, "ZapcockpitHideLastGroup")) ZapcockpitHideLastGroup = atoi(Value); ++ else if (!strcasecmp(Name, "ZapcockpitShowAllChannels")) ZapcockpitShowAllChannels = atoi(Value); + else if (!strcasecmp(Name, "RcRepeatDelay")) RcRepeatDelay = atoi(Value); + else if (!strcasecmp(Name, "RcRepeatDelta")) RcRepeatDelta = atoi(Value); + else if (!strcasecmp(Name, "DefaultPriority")) DefaultPriority = atoi(Value); +@@ -777,6 +787,11 @@ + Store("SVDRPDefaultHost", SVDRPDefaultHost); + Store("ZapTimeout", ZapTimeout); + Store("ChannelEntryTimeout",ChannelEntryTimeout); ++ Store("ZapcockpitUseGroups",ZapcockpitUseGroups); ++ Store("ZapcockpitUseHints", ZapcockpitUseHints); ++ Store("ZapcockpitUseInfo", ZapcockpitUseInfo); ++ Store("ZapcockpitHideLastGroup", ZapcockpitHideLastGroup); ++ Store("ZapcockpitShowAllChannels", ZapcockpitShowAllChannels); + Store("RcRepeatDelay", RcRepeatDelay); + Store("RcRepeatDelta", RcRepeatDelta); + Store("DefaultPriority", DefaultPriority); +diff -Nur vdr-2.4.0/config.h vdr-2.4.0.p/config.h +--- vdr-2.4.0/config.h 2018-03-19 16:06:46.000000000 +0100 ++++ vdr-2.4.0.p/config.h 2019-04-04 14:57:48.235702949 +0200 +@@ -293,6 +293,11 @@ + char SVDRPDefaultHost[HOST_NAME_MAX]; + int ZapTimeout; + int ChannelEntryTimeout; ++ int ZapcockpitUseGroups; ++ int ZapcockpitUseHints; ++ int ZapcockpitUseInfo; ++ int ZapcockpitHideLastGroup; ++ int ZapcockpitShowAllChannels; + int RcRepeatDelay; + int RcRepeatDelta; + int DefaultPriority, DefaultLifetime; +diff -Nur vdr-2.4.0/menu.c vdr-2.4.0.p/menu.c +--- vdr-2.4.0/menu.c 2019-04-04 15:47:25.722519143 +0200 ++++ vdr-2.4.0.p/menu.c 2019-04-04 15:29:02.650105356 +0200 +@@ -4184,6 +4184,11 @@ + } + 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 cMenuEditBoolItem( tr("Setup.Miscellaneous$Zapcockpit: 2nd ok shows info"), &data.ZapcockpitUseInfo)); ++ Add(new cMenuEditBoolItem( tr("Setup.Miscellaneous$Zapcockpit: Use extended channel group display"), &data.ZapcockpitUseGroups)); ++ Add(new cMenuEditBoolItem( tr("Setup.Miscellaneous$Zapcockpit: Use channel hints"), &data.ZapcockpitUseHints)); ++ Add(new cMenuEditBoolItem( tr("Setup.Miscellaneous$Zapcockpit: Hide last channel group"), &data.ZapcockpitHideLastGroup)); ++ Add(new cMenuEditBoolItem( tr("Setup.Miscellaneous$Zapcockpit: Show \"All Channels\" Item in Group List"), &data.ZapcockpitShowAllChannels)); + 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"))); +@@ -4654,7 +4659,7 @@ + lastTime.Set(); + } + +-cDisplayChannel::cDisplayChannel(eKeys FirstKey) ++cDisplayChannel::cDisplayChannel(eKeys FirstKey, bool processKey) + :cOsdObject(true) + { + currentDisplayChannel = this; +@@ -4672,7 +4677,8 @@ + LOCK_CHANNELS_READ; + channel = Channels->GetByNumber(cDevice::CurrentChannel()); + } +- ProcessKey(FirstKey); ++ if (processKey) ++ ProcessKey(FirstKey); + } + + cDisplayChannel::~cDisplayChannel() +@@ -4922,6 +4928,793 @@ + return osEnd; + } + ++// --- cGroupListItem ------------------------------------------------------- ++const char *cGroupListItem::GroupName(void) { ++ if (channel) ++ return channel->Name(); ++ return tr("Setup.Miscellaneous$All Channels"); ++} ++ ++// --- cDisplayChannelExtended ------------------------------------------------------- ++cDisplayChannelExtended::cDisplayChannelExtended(int Number, bool Switched) ++:cDisplayChannel(Number, Switched) ++{ ++ state = esDefault; ++ keyRightOpensChannellist = -1; ++ numItemsChannel = 0; ++ currentChannel = -1; ++ startChannel = -1; ++ numItemsGroup = 0; ++ currentGroup = -1; ++ startGroup = -1; ++} ++ ++cDisplayChannelExtended::cDisplayChannelExtended(eKeys FirstKey) ++:cDisplayChannel(FirstKey, false) ++{ ++ state = esInit; ++ keyRightOpensChannellist = -1; ++ numItemsChannel = 0; ++ currentChannel = -1; ++ startChannel = -1; ++ numItemsGroup = 0; ++ currentGroup = -1; ++ startGroup = -1; ++} ++ ++cDisplayChannelExtended::~cDisplayChannelExtended() ++{ ++} ++ ++eOSState cDisplayChannelExtended::ProcessKey(eKeys Key) ++{ ++ cSkinDisplayChannelExtended *displayChannelExtended = dynamic_cast(displayChannel); ++ if (!displayChannelExtended) ++ return cDisplayChannel::ProcessKey(Key); ++ ++ if (Key != kNone) ++ lastTime.Set(); ++ ++ bool keyHandeled = false; ++ //number keys are always handled by default state ++ if ((int)Key >= k0 && (int)Key <= k9) { ++ displayChannelExtended->SetViewType(dcDefault); ++ StateNumberKey((int)Key, displayChannelExtended); ++ state = esDefault; ++ } else if (number <= 0) { ++ switch (state) { ++ case esInit: ++ keyHandeled = StateInit((int)Key, displayChannelExtended); ++ break; ++ case esDefault: ++ keyHandeled = StateDefault((int)Key, displayChannelExtended); ++ break; ++ case esChannelInfo: ++ keyHandeled = StateChannelInfo((int)Key, displayChannelExtended); ++ break; ++ case esChannelList: ++ case esChannelListInfo: ++ keyHandeled = StateChannelList((int)Key, displayChannelExtended); ++ break; ++ case esGroupsList: ++ keyHandeled = StateGroupList((int)Key, displayChannelExtended); ++ break; ++ case esGroupsChannelList: ++ case esGroupsChannelListInfo: ++ keyHandeled = StateGroupChannelList((int)Key, displayChannelExtended); ++ break; ++ default: ++ break; ++ } ++ } ++ if (state == esClose) ++ return osEnd; ++ //in extended state, no timeout ++ if (state != esDefault) ++ lastTime.Set(); ++ ++ //do own flush for all lists ++ if (keyHandeled || (Key == kNone && state > esChannelInfo)) { ++ SetNeedsFastResponse(false); ++ displayChannel->Flush(); ++ return osContinue; ++ } ++ ++ return cDisplayChannel::ProcessKey(Key); ++} ++ ++void cDisplayChannelExtended::StateNumberKey(int key, cSkinDisplayChannelExtended *dcExt) ++{ ++ if (!Setup.ZapcockpitUseHints) ++ return; ++ if (number < 0) ++ return; ++ LOCK_CHANNELS_READ; ++ int selectedChannel = number > Channels->MaxNumber() ? key - k0 : number * 10 + key - k0; ++ int candidateStartNumber = selectedChannel * 10; ++ channellist.Clear(); ++ const cChannel *candidatesStart = Channels->GetByNumber(candidateStartNumber); ++ int numHints = 0; ++ for (const cChannel *candidate = candidatesStart; candidate; candidate = Channels->Next(candidate)) { ++ if (candidate->GroupSep()) ++ continue; ++ numHints++; ++ if (candidate->Number() >= candidateStartNumber + 9) ++ break; ++ } ++ if (numHints == 0) ++ return; ++ dcExt->SetNumChannelHints(numHints); ++ for (const cChannel *candidate = candidatesStart; candidate; candidate = Channels->Next(candidate)) { ++ if (candidate->GroupSep()) ++ continue; ++ dcExt->SetChannelHint(candidate); ++ if (candidate->Number() >= candidateStartNumber + 9) ++ break; ++ } ++} ++ ++bool cDisplayChannelExtended::StateInit(int key, cSkinDisplayChannelExtended *dcExt) ++{ ++ if (keyRightOpensChannellist == -1) ++ keyRightOpensChannellist = dcExt->KeyRightOpensChannellist() ? 1 : 0; ++ ++ bool keyHandeled = false; ++ switch (key) { ++ case kLeft|k_Repeat: case kLeft: ++ case kPrev|k_Repeat: case kPrev: { ++ if (!Setup.ZapcockpitUseGroups) ++ return false; ++ cOsdProvider::OsdSizeChanged(osdState); // just to get the current state ++ DisplayChannel(); ++ DisplayInfo(); ++ if (keyRightOpensChannellist) { ++ InitGroupList(dcExt); ++ state = esGroupsList; ++ } else { ++ InitChannelList(dcExt); ++ state = esChannelList; ++ } ++ keyHandeled = true; ++ break; ++ } ++ case kRight|k_Repeat: case kRight: ++ case kNext|k_Repeat: case kNext: { ++ if (!Setup.ZapcockpitUseGroups) ++ return false; ++ cOsdProvider::OsdSizeChanged(osdState); // just to get the current state ++ DisplayChannel(); ++ DisplayInfo(); ++ if (keyRightOpensChannellist) { ++ InitChannelList(dcExt); ++ state = esChannelList; ++ } else { ++ InitGroupList(dcExt); ++ state = esGroupsList; ++ } ++ keyHandeled = true; ++ break; ++ } ++ //other keys are handled by cDisplayChannel::ProcessKeys() ++ default: ++ dcExt->SetViewType(dcDefault); ++ state = esDefault; ++ break; ++ } ++ return keyHandeled; ++} ++ ++bool cDisplayChannelExtended::StateDefault(int key, cSkinDisplayChannelExtended *dcExt) ++{ ++ if (keyRightOpensChannellist == -1) ++ keyRightOpensChannellist = dcExt->KeyRightOpensChannellist() ? 1 : 0; ++ bool keyHandeled = false; ++ switch (key) { ++ //2nd ok opens extended info for current channel ++ case kOk: { ++ if (!Setup.ZapcockpitUseInfo) ++ return false; ++ dcExt->SetViewType(dcChannelInfo); ++ dcExt->SetChannelInfo(channel); ++ state = esChannelInfo; ++ keyHandeled = true; ++ break; ++ } ++ case kLeft|k_Repeat: case kLeft: ++ case kPrev|k_Repeat: case kPrev: { ++ if (!Setup.ZapcockpitUseGroups) ++ return false; ++ if (keyRightOpensChannellist) { ++ InitGroupList(dcExt); ++ state = esGroupsList; ++ } else { ++ InitChannelList(dcExt); ++ state = esChannelList; ++ } ++ keyHandeled = true; ++ break; ++ } ++ case kRight|k_Repeat: case kRight: ++ case kNext|k_Repeat: case kNext: { ++ if (!Setup.ZapcockpitUseGroups) ++ return false; ++ if (keyRightOpensChannellist) { ++ InitChannelList(dcExt); ++ state = esChannelList; ++ } else { ++ InitGroupList(dcExt); ++ state = esGroupsList; ++ } ++ keyHandeled = true; ++ break; ++ } ++ //other keys are handled by cDisplayChannel::ProcessKeys() ++ default: ++ break; ++ } ++ return keyHandeled; ++} ++ ++bool cDisplayChannelExtended::StateChannelInfo(int key, cSkinDisplayChannelExtended *dcExt) ++{ ++ bool keyHandeled = false; ++ switch (key) { ++ //ok closes here ++ case kOk: ++ state = esDefault; ++ break; ++ //channel switching is handled by default state ++ case kUp|k_Repeat: case kUp: ++ case kDown|k_Repeat: case kDown: ++ case kChanUp|k_Repeat: case kChanUp: ++ case kChanDn|k_Repeat: case kChanDn: ++ dcExt->SetViewType(dcDefault); ++ state = esDefault; ++ break; ++ case kUp|k_Release: case kDown|k_Release: ++ case kChanUp|k_Release: case kChanDn|k_Release: ++ case kNext|k_Release: case kPrev|k_Release: ++ dcExt->SetViewType(dcDefault); ++ state = esDefault; ++ break; ++ case kLeft|k_Repeat: case kLeft: ++ case kPrev|k_Repeat: case kPrev: { ++ if (!Setup.ZapcockpitUseGroups) ++ return false; ++ if (keyRightOpensChannellist) { ++ InitGroupList(dcExt); ++ state = esGroupsList; ++ } else { ++ InitChannelList(dcExt); ++ state = esChannelList; ++ } ++ keyHandeled = true; ++ break; ++ } ++ case kRight|k_Repeat: case kRight: ++ case kNext|k_Repeat: case kNext: { ++ if (!Setup.ZapcockpitUseGroups) ++ return false; ++ if (keyRightOpensChannellist) { ++ InitChannelList(dcExt); ++ state = esChannelList; ++ } else { ++ InitGroupList(dcExt); ++ state = esGroupsList; ++ } ++ keyHandeled = true; ++ break; ++ } ++ default: ++ break; ++ } ++ return keyHandeled; ++} ++ ++bool cDisplayChannelExtended::StateChannelList(int key, cSkinDisplayChannelExtended *dcExt) ++{ ++ bool keyHandeled = false; ++ switch (key) { ++ //ok switches to the selected channel ++ case kOk: { ++ bool ok = SwitchChannel(); ++ dcExt->SetViewType(dcDefault); ++ if (!ok) ++ keyHandeled = true; ++ state = esDefault; ++ break; ++ } ++ //scrolling up / down ++ case kUp|k_Repeat: case kUp: ++ state = esChannelList; ++ dcExt->SetViewType(dcChannelList); ++ CursorUp(dcExt); ++ keyHandeled = true; ++ break; ++ case kDown|k_Repeat: case kDown: ++ state = esChannelList; ++ dcExt->SetViewType(dcChannelList); ++ CursorDown(dcExt); ++ keyHandeled = true; ++ break; ++ case kLeft|k_Repeat: case kLeft: { ++ keyHandeled = true; ++ if (keyRightOpensChannellist) { ++ if (state == esChannelList) { ++ state = esClose; ++ } else if (state == esChannelListInfo) { ++ dcExt->SetViewType(dcChannelList); ++ state = esChannelList; ++ } ++ } else ++ ShowChannellistInfo(dcExt, dcChannelListInfo); ++ break; ++ } ++ //right shows extended info of currently selected channel ++ case kRight|k_Repeat: case kRight: { ++ keyHandeled = true; ++ if (keyRightOpensChannellist) ++ ShowChannellistInfo(dcExt, dcChannelListInfo); ++ else { ++ if (state == esChannelList) { ++ state = esClose; ++ } else if (state == esChannelListInfo) { ++ dcExt->SetViewType(dcChannelList); ++ state = esChannelList; ++ } ++ } ++ break; ++ } ++ default: ++ break; ++ } ++ return keyHandeled; ++} ++ ++bool cDisplayChannelExtended::StateGroupList(int key, cSkinDisplayChannelExtended *dcExt) ++{ ++ bool keyHandeled = false; ++ switch (key) { ++ //ok switches to first channel in group ++ case kOk: { ++ bool ok = SwitchChannel(); ++ dcExt->SetViewType(dcDefault); ++ if (!ok) ++ keyHandeled = true; ++ state = esDefault; ++ break; ++ } ++ //scrolling up / down ++ case kUp|k_Repeat: case kUp: ++ state = esGroupsList; ++ CursorUp(dcExt); ++ dcExt->SetViewType(dcGroupsList); ++ keyHandeled = true; ++ break; ++ case kDown|k_Repeat: case kDown: ++ state = esGroupsList; ++ CursorDown(dcExt); ++ dcExt->SetViewType(dcGroupsList); ++ keyHandeled = true; ++ break; ++ case kLeft|k_Repeat: case kLeft: ++ keyHandeled = true; ++ if (keyRightOpensChannellist) { ++ state = esGroupsChannelList; ++ InitGroupChannelList(dcExt); ++ } else ++ state = esClose; ++ break; ++ case kRight|k_Repeat: case kRight: ++ keyHandeled = true; ++ if (keyRightOpensChannellist) ++ state = esClose; ++ else { ++ state = esGroupsChannelList; ++ InitGroupChannelList(dcExt); ++ } ++ break; ++ default: ++ break; ++ } ++ return keyHandeled; ++} ++ ++bool cDisplayChannelExtended::StateGroupChannelList(int key, cSkinDisplayChannelExtended *dcExt) ++{ ++ bool keyHandeled = false; ++ switch (key) { ++ //ok switches to the selected channel ++ case kOk: { ++ bool ok = SwitchChannel(); ++ dcExt->SetViewType(dcDefault); ++ if (!ok) ++ keyHandeled = true; ++ state = esDefault; ++ break; ++ } ++ //scrolling up / down ++ case kUp|k_Repeat: case kUp: ++ state = esGroupsChannelList; ++ dcExt->SetViewType(dcGroupsChannelList); ++ CursorUp(dcExt); ++ keyHandeled = true; ++ break; ++ case kDown|k_Repeat: case kDown: ++ state = esGroupsChannelList; ++ dcExt->SetViewType(dcGroupsChannelList); ++ CursorDown(dcExt); ++ keyHandeled = true; ++ break; ++ case kLeft|k_Repeat: case kLeft: { ++ keyHandeled = true; ++ if (keyRightOpensChannellist) ++ ShowChannellistInfo(dcExt, dcGroupsChannelListInfo); ++ else { ++ if (state == esGroupsChannelList) { ++ state = esGroupsList; ++ dcExt->SetViewType(dcGroupsList); ++ } else if (state == esGroupsChannelListInfo) { ++ state = esGroupsChannelList; ++ dcExt->SetViewType(dcGroupsChannelList); ++ } ++ } ++ break; ++ } ++ case kRight|k_Repeat: case kRight: { ++ keyHandeled = true; ++ if (keyRightOpensChannellist) { ++ if (state == esGroupsChannelList) { ++ state = esGroupsList; ++ dcExt->SetViewType(dcGroupsList); ++ } else if (state == esGroupsChannelListInfo) { ++ state = esGroupsChannelList; ++ dcExt->SetViewType(dcGroupsChannelList); ++ } ++ } else ++ ShowChannellistInfo(dcExt, dcGroupsChannelListInfo); ++ break; ++ } ++ default: ++ break; ++ } ++ return keyHandeled; ++} ++ ++void cDisplayChannelExtended::ShowChannellistInfo(cSkinDisplayChannelExtended *dcExt, eDisplaychannelView newViewType) { ++ if (newViewType == dcChannelListInfo && state != esChannelList) ++ return; ++ if (newViewType == dcGroupsChannelListInfo && state != esGroupsChannelList) ++ return; ++ ++ cChannelListItem *li = channellist.Get(currentChannel); ++ if (li) { ++ const cChannel *selected = li->Channel(); ++ if (selected) { ++ dcExt->SetViewType(newViewType); ++ dcExt->SetChannelInfo(selected); ++ state = (newViewType == dcChannelListInfo) ? esChannelListInfo : esGroupsChannelListInfo; ++ } ++ } ++} ++ ++void cDisplayChannelExtended::InitChannelList(cSkinDisplayChannelExtended *dcExt) ++{ ++ dcExt->SetViewType(dcChannelList); ++ numItemsChannel = dcExt->MaxItems(); ++ if (numItemsChannel < 1) ++ return; ++ SetChannelList(); ++ currentChannel = GetIndexChannel(channel); ++ if (currentChannel < 0) ++ currentChannel = 0; ++ startChannel = max(0, currentChannel - numItemsChannel/2 + 1); ++ DisplayChannelList(dcExt); ++} ++ ++void cDisplayChannelExtended::SetChannelList(void) ++{ ++ channellist.Clear(); ++ const cChannel *lastSep = NULL; ++ if (Setup.ZapcockpitHideLastGroup) ++ lastSep = LastChannelSep(); ++ LOCK_CHANNELS_READ; ++ for (const cChannel *c = Channels->First(); c; c = Channels->Next(c)) { ++ if (c->GroupSep()) { ++ if (Setup.ZapcockpitHideLastGroup && c == lastSep) ++ break; ++ else ++ continue; ++ } ++ channellist.Add(new cChannelListItem(c)); ++ } ++} ++ ++int cDisplayChannelExtended::GetIndexChannel(const cChannel *c) ++{ ++ int i=0; ++ for (cChannelListItem *li = channellist.First(); li; li = channellist.Next(li)) { ++ if (li->Channel() == c) ++ return i; ++ i++; ++ } ++ return -1; ++} ++ ++void cDisplayChannelExtended::InitGroupList(cSkinDisplayChannelExtended *dcExt) ++{ ++ dcExt->SetViewType(dcGroupsList); ++ numItemsGroup = dcExt->MaxItems(); ++ if (numItemsGroup < 1) ++ return; ++ SetGroupList(); ++ currentGroup = GetIndexGroup(channel); ++ if (currentGroup < 0) ++ currentGroup = 0; ++ startGroup = max(0, numItemsGroup >= grouplist.Count() ? 0 : currentGroup - numItemsGroup/2 + 1); ++ DisplayGroupList(dcExt); ++} ++ ++void cDisplayChannelExtended::SetGroupList(void) ++{ ++ grouplist.Clear(); ++ if (Setup.ZapcockpitShowAllChannels) { ++ cGroupListItem *allChannels = new cGroupListItem(NULL); ++ int totalNumChannels = 0; ++ const cChannel *lastSep = NULL; ++ if (Setup.ZapcockpitHideLastGroup) ++ lastSep = LastChannelSep(); ++ LOCK_CHANNELS_READ; ++ for (const cChannel *c = Channels->First(); c; c = Channels->Next(c)) { ++ if (c->GroupSep()) { ++ if (Setup.ZapcockpitHideLastGroup && c == lastSep) ++ break; ++ else ++ continue; ++ } ++ totalNumChannels++; ++ } ++ allChannels->SetNumChannels(totalNumChannels); ++ grouplist.Add(allChannels); ++ } ++ ++ const cChannel *lastSep = NULL; ++ if (Setup.ZapcockpitHideLastGroup) ++ lastSep = LastChannelSep(); ++ int numChannels = 0; ++ cGroupListItem *item = NULL; ++ LOCK_CHANNELS_READ; ++ for (const cChannel *c = Channels->First(); c; c = Channels->Next(c)) { ++ if (c->GroupSep()) { ++ if (item) { ++ item->SetNumChannels(numChannels); ++ numChannels = 0; ++ } ++ if (Setup.ZapcockpitHideLastGroup && c == lastSep) ++ break; ++ item = new cGroupListItem(c); ++ grouplist.Add(item); ++ } else ++ numChannels++; ++ } ++ if (grouplist.Count() > 0 && numChannels) ++ grouplist.Last()->SetNumChannels(numChannels); ++} ++ ++int cDisplayChannelExtended::GetIndexGroup(const cChannel *cur) ++{ ++ const cChannel *group = NULL; ++ LOCK_CHANNELS_READ; ++ for (const cChannel *c = cur; c; c = Channels->Prev(c)) { ++ if (c->GroupSep()) { ++ group = c; ++ break; ++ } ++ } ++ if (!group) ++ return -1; ++ int i=0; ++ for (cGroupListItem *li = grouplist.First(); li; li = grouplist.Next(li)) { ++ if (li->Channel() == group) ++ return i; ++ i++; ++ } ++ return -1; ++} ++ ++void cDisplayChannelExtended::InitGroupChannelList(cSkinDisplayChannelExtended *dcExt) ++{ ++ dcExt->SetViewType(dcGroupsChannelList); ++ numItemsChannel = dcExt->MaxItems(); ++ if (numItemsChannel < 1) ++ return; ++ SetGroupChannelList(dcExt); ++ currentChannel = 0; ++ startChannel = 0; ++ DisplayChannelList(dcExt); ++} ++ ++void cDisplayChannelExtended::SetGroupChannelList(cSkinDisplayChannelExtended *dcExt) ++{ ++ cGroupListItem *curGroup = grouplist.Get(currentGroup); ++ if (!curGroup) ++ return; ++ const cChannel *curChannel = curGroup->Channel(); ++ if (!curChannel) { ++ if (Setup.ZapcockpitShowAllChannels) ++ SetChannelList(); ++ return; ++ } ++ channellist.Clear(); ++ LOCK_CHANNELS_READ; ++ for (const cChannel *c = dynamic_cast(curChannel->Next()); c; c = Channels->Next(c)) { ++ if (c->GroupSep()) ++ break; ++ channellist.Add(new cChannelListItem(c)); ++ } ++} ++ ++void cDisplayChannelExtended::CursorUp(cSkinDisplayChannelExtended *dcExt) ++{ ++ int *start, *current, *numItems; ++ if (state == esChannelList || state == esGroupsChannelList) { ++ start = &startChannel; ++ current = ¤tChannel; ++ numItems = &numItemsChannel; ++ } else if (state == esGroupsList) { ++ start = &startGroup; ++ current = ¤tGroup; ++ numItems = &numItemsGroup; ++ } else ++ return; ++ ++ if (*current == 0) { ++ dcExt->ClearList(); ++ int itemsTotal = (state == esChannelList || state == esGroupsChannelList)?channellist.Count():((state == esGroupsList)?grouplist.Count():0); ++ *current = itemsTotal-1; ++ *start = max(0, itemsTotal - *numItems); ++ if (state == esChannelList || state == esGroupsChannelList) ++ DisplayChannelList(dcExt); ++ else if (state == esGroupsList) ++ DisplayGroupList(dcExt); ++ return; ++ } ++ int curRel = *current - *start; ++ if (curRel > 0) { ++ if (state == esChannelList || state == esGroupsChannelList) { ++ const cChannel *prev = channellist.Get(*current-1)->Channel(); ++ dcExt->SetChannelList(channellist.Get(*current)->Channel(), curRel, false); ++ dcExt->SetChannelList(prev, curRel-1, true); ++ (*current)--; ++ return; ++ } else if (state = esGroupsList) { ++ cGroupListItem *prev = grouplist.Get(*current-1); ++ cGroupListItem *old = grouplist.Get(*current); ++ dcExt->SetGroupList(old->GroupName(), old->NumChannels(), curRel, false); ++ dcExt->SetGroupList(prev->GroupName(), prev->NumChannels(), curRel-1, true); ++ (*current)--; ++ return; ++ } ++ } ++ dcExt->ClearList(); ++ (*current)--; ++ *start = max(0, *start-*numItems); ++ ++ if (state == esChannelList || state == esGroupsChannelList) ++ DisplayChannelList(dcExt); ++ else if (state == esGroupsList) ++ DisplayGroupList(dcExt); ++} ++ ++void cDisplayChannelExtended::CursorDown(cSkinDisplayChannelExtended *dcExt) ++{ ++ int *start, *current, *numItems; ++ if (state == esChannelList || state == esGroupsChannelList) { ++ start = &startChannel; ++ current = ¤tChannel; ++ numItems = &numItemsChannel; ++ } else if (state == esGroupsList) { ++ start = &startGroup; ++ current = ¤tGroup; ++ numItems = &numItemsGroup; ++ } else ++ return; ++ ++ int curRel = *current - *start; ++ if (curRel < *numItems - 1) { ++ if (state == esChannelList || state == esGroupsChannelList) { ++ cChannelListItem *next = channellist.Get(*current+1); ++ if (next) { ++ dcExt->SetChannelList(channellist.Get(*current)->Channel(), curRel, false); ++ dcExt->SetChannelList(next->Channel(), curRel+1, true); ++ (*current)++; ++ return; ++ } ++ } else if (state == esGroupsList) { ++ cGroupListItem *next = grouplist.Get(*current+1); ++ if (next) { ++ cGroupListItem *old = grouplist.Get(*current); ++ dcExt->SetGroupList(old->GroupName(), old->NumChannels(), curRel, false); ++ dcExt->SetGroupList(next->GroupName(), next->NumChannels(), curRel+1, true); ++ (*current)++; ++ return; ++ } ++ } ++ } ++ if (((state == esChannelList || state == esGroupsChannelList) && *current+1 == channellist.Count()) || ++ (state == esGroupsList && *current+1 == grouplist.Count())) ++ *start = *current = 0; ++ else ++ *start = *current = *current+1; ++ dcExt->ClearList(); ++ ++ if (state == esChannelList || state == esGroupsChannelList) ++ DisplayChannelList(dcExt); ++ else if (state == esGroupsList) ++ DisplayGroupList(dcExt); ++} ++ ++void cDisplayChannelExtended::DisplayChannelList(cSkinDisplayChannelExtended *dcExt) ++{ ++ int index = 0; ++ for (cChannelListItem *c = channellist.Get(startChannel); c; c = channellist.Next(c)) { ++ dcExt->SetChannelList(c->Channel(), index, (startChannel + index == currentChannel) ? true : false); ++ if (++index == numItemsChannel) ++ break; ++ } ++} ++ ++void cDisplayChannelExtended::DisplayGroupList(cSkinDisplayChannelExtended *dcExt) ++{ ++ int index = 0; ++ for (cGroupListItem *g = grouplist.Get(startGroup); g; g = grouplist.Next(g)) { ++ dcExt->SetGroupList(g->GroupName(), g->NumChannels(), index, (startGroup + index == currentGroup) ? true : false); ++ if (++index == numItemsGroup) ++ break; ++ } ++} ++ ++bool cDisplayChannelExtended::SwitchChannel(void) ++{ ++ const cChannel *newChannel = NULL; ++ if ( state == esChannelList || ++ state == esChannelListInfo || ++ state == esGroupsChannelList || ++ state == esGroupsChannelListInfo ) { ++ cChannelListItem *li = channellist.Get(currentChannel); ++ if (li) ++ newChannel = li->Channel(); ++ } else if (state == esGroupsList) { ++ cGroupListItem *item = grouplist.Get(currentGroup); ++ if (!item) ++ return false; ++ const cChannel *cGroup = item->Channel(); ++ LOCK_CHANNELS_READ; ++ for (const cChannel *c = cGroup; c; c = Channels->Next(c)) ++ if (!c->GroupSep()) { ++ newChannel = c; ++ break; ++ } ++ } ++ if (!newChannel || newChannel == channel) ++ return false; ++ SetTrackDescriptions(newChannel->Number()); // to make them immediately visible in the channel display ++ LOCK_CHANNELS_READ; ++ Channels->SwitchTo(newChannel->Number()); ++ SetTrackDescriptions(newChannel->Number()); // switching the channel has cleared them ++ channel = newChannel; ++ return true; ++} ++ ++const cChannel *cDisplayChannelExtended::LastChannelSep(void) ++{ ++ LOCK_CHANNELS_READ; ++ for (const cChannel *c = Channels->Last(); c; c = Channels->Prev(c)) ++ if (c->GroupSep()) ++ return c; ++ return NULL; ++} ++ + // --- cDisplayVolume -------------------------------------------------------- + + #define VOLUMETIMEOUT 1000 //ms +diff -Nur vdr-2.4.0/menu.h vdr-2.4.0.p/menu.h +--- vdr-2.4.0/menu.h 2018-04-14 12:24:41.000000000 +0200 ++++ vdr-2.4.0.p/menu.h 2019-04-04 15:27:27.648451092 +0200 +@@ -119,30 +119,102 @@ + + class cDisplayChannel : public cOsdObject { + private: +- cSkinDisplayChannel *displayChannel; + int group; + bool withInfo; +- cTimeMs lastTime; +- int number; + bool timeout; +- int osdState; + const cPositioner *positioner; +- const cChannel *channel; + const cEvent *lastPresent; + const cEvent *lastFollowing; + static cDisplayChannel *currentDisplayChannel; +- void DisplayChannel(void); +- void DisplayInfo(void); + void Refresh(void); + const cChannel *NextAvailableChannel(const cChannel *Channel, int Direction); ++protected: ++ cSkinDisplayChannel *displayChannel; ++ cTimeMs lastTime; ++ int number; ++ const cChannel *channel; ++ int osdState; ++ void DisplayChannel(void); ++ void DisplayInfo(void); + public: + cDisplayChannel(int Number, bool Switched); +- cDisplayChannel(eKeys FirstKey); ++ cDisplayChannel(eKeys FirstKey, bool processKey = true); + virtual ~cDisplayChannel(); + virtual eOSState ProcessKey(eKeys Key); + static bool IsOpen(void) { return currentDisplayChannel != NULL; } + }; + ++enum eExtendedState { ++ esInit = 0, ++ esDefault, ++ esChannelInfo, ++ esChannelList, ++ esChannelListInfo, ++ esGroupsList, ++ esGroupsChannelList, ++ esGroupsChannelListInfo, ++ esClose ++ }; ++ ++class cChannelListItem : public cListObject { ++private: ++ const cChannel *channel; ++public: ++ cChannelListItem(const cChannel *Channel) { channel = Channel; }; ++ virtual ~cChannelListItem(void) { }; ++ const cChannel *Channel(void) { return channel; } ++ }; ++ ++class cGroupListItem : public cListObject { ++private: ++ const cChannel *channel; ++ int numChannels; ++public: ++ cGroupListItem(const cChannel *Channel) { channel = Channel; numChannels = 0; }; ++ virtual ~cGroupListItem(void) { }; ++ const char *GroupName(void); ++ void SetNumChannels(int NumChannels) { numChannels = NumChannels; }; ++ int NumChannels(void) { return numChannels; }; ++ const cChannel *Channel(void) { return channel; } ++ }; ++ ++class cDisplayChannelExtended : public cDisplayChannel { ++private: ++ eExtendedState state; ++ int keyRightOpensChannellist; ++ int numItemsChannel, startChannel, currentChannel; ++ int numItemsGroup, startGroup, currentGroup; ++ cList channellist; ++ cList grouplist; ++ void StateNumberKey(int key, cSkinDisplayChannelExtended *dcExt); ++ bool StateInit(int key, cSkinDisplayChannelExtended *dcExt); ++ bool StateDefault(int key, cSkinDisplayChannelExtended *dcExt); ++ bool StateChannelInfo(int key, cSkinDisplayChannelExtended *dcExt); ++ bool StateChannelList(int key, cSkinDisplayChannelExtended *dcExt); ++ bool StateGroupList(int key, cSkinDisplayChannelExtended *dcExt); ++ bool StateGroupChannelList(int key, cSkinDisplayChannelExtended *dcExt); ++ void ShowChannellistInfo(cSkinDisplayChannelExtended *dcExt, eDisplaychannelView newViewType); ++ void InitChannelList(cSkinDisplayChannelExtended *dcExt); ++ void SetChannelList(void); ++ int GetIndexChannel(const cChannel *c); ++ void InitGroupList(cSkinDisplayChannelExtended *dcExt); ++ void SetGroupList(void); ++ int GetIndexGroup(const cChannel *c); ++ void InitGroupChannelList(cSkinDisplayChannelExtended *dcExt); ++ void SetGroupChannelList(cSkinDisplayChannelExtended *dcExt); ++ void CursorUp(cSkinDisplayChannelExtended *dcExt); ++ void CursorDown(cSkinDisplayChannelExtended *dcExt); ++ void DisplayChannelList(cSkinDisplayChannelExtended *dcExt); ++ void DisplayGroupList(cSkinDisplayChannelExtended *dcExt); ++ bool SwitchChannel(void); ++ const cChannel *LastChannelSep(void); ++public: ++ cDisplayChannelExtended(int Number, bool Switched); ++ cDisplayChannelExtended(eKeys FirstKey); ++ virtual ~cDisplayChannelExtended(); ++ virtual eOSState ProcessKey(eKeys Key); ++ }; ++ + class cDisplayVolume : public cOsdObject { + private: + cSkinDisplayVolume *displayVolume; +diff -Nur vdr-2.4.0/po/de_DE.po vdr-2.4.0.p/po/de_DE.po +--- vdr-2.4.0/po/de_DE.po 2019-04-04 15:47:31.199441881 +0200 ++++ vdr-2.4.0.p/po/de_DE.po 2019-04-04 15:29:21.903832616 +0200 +@@ -1347,6 +1347,21 @@ + msgid "Setup.Miscellaneous$Channel entry timeout (ms)" + msgstr "Zeitlimit für Kanaleingabe (ms)" + ++msgid "Setup.Miscellaneous$Zapcockpit: 2nd ok shows info" ++msgstr "Zapcockpit: zweites OK zeigt Info" ++ ++msgid "Setup.Miscellaneous$Zapcockpit: Use extended channel group display" ++msgstr "Zapcockpit: Erweiterte Kanalgruppen Anzeige benutzen" ++ ++msgid "Setup.Miscellaneous$Zapcockpit: Use channel hints" ++msgstr "Zapcockpit: Kanalhinweise benutzen" ++ ++msgid "Setup.Miscellaneous$Zapcockpit: Hide last channel group" ++msgstr "Zapcockpit: letzte Kanalgruppe ausblenden" ++ ++msgid "Setup.Miscellaneous$Zapcockpit: Show \"All Channels\" Item in Group List" ++msgstr "Zapcockpit: Zeige \"Alle Kanäle\" in Kanalgruppen Liste" ++ + msgid "Setup.Miscellaneous$Remote control repeat delay (ms)" + msgstr "Fernbedienung Wiederholverzögerung (ms)" + +@@ -1419,6 +1434,9 @@ + msgid "Cancel editing?" + msgstr "Bearbeitung abbrechen?" + ++msgid "Setup.Miscellaneous$All Channels" ++msgstr "Alle Kanäle" ++ + msgid "No audio available!" + msgstr "Kein Audio verfügbar!" + +diff -Nur vdr-2.4.0/skins.c vdr-2.4.0.p/skins.c +--- vdr-2.4.0/skins.c 2019-04-04 15:47:25.665519948 +0200 ++++ vdr-2.4.0.p/skins.c 2019-04-04 14:57:48.240702878 +0200 +@@ -79,6 +79,13 @@ + SetMessage(mtInfo, cString::sprintf(tr("Moving dish to %.1f..."), double(positioner->TargetLongitude()) / 10)); + } + ++cSkinDisplayChannelExtended::cSkinDisplayChannelExtended(void) ++: cSkinDisplayChannel() ++{ ++ ++} ++ ++ + // --- cSkinDisplayMenu ------------------------------------------------------ + + cSkinDisplayMenu::cSkinDisplayMenu(void) +diff -Nur vdr-2.4.0/skins.h vdr-2.4.0.p/skins.h +--- vdr-2.4.0/skins.h 2017-11-02 16:04:56.000000000 +0100 ++++ vdr-2.4.0.p/skins.h 2019-04-04 14:57:48.241702864 +0200 +@@ -101,6 +101,34 @@ + */ + }; + ++#define USE_ZAPCOCKPIT 1 ++ ++enum eDisplaychannelView { ++ dcDefault = 0, ++ dcChannelInfo, ++ dcChannelList, ++ dcChannelListInfo, ++ dcGroupsList, ++ dcGroupsChannelList, ++ dcGroupsChannelListInfo ++ }; ++ ++class cSkinDisplayChannelExtended : public cSkinDisplayChannel { ++private: ++public: ++ cSkinDisplayChannelExtended(void); ++ virtual void SetViewType(eDisplaychannelView ViewType) = 0; ++ virtual int MaxItems(void) = 0; ++ virtual bool KeyRightOpensChannellist(void) = 0; ++ virtual void SetChannelInfo(const cChannel *Channel) = 0; ++ virtual void SetChannelList(const cChannel *Channel, int Index, bool Current) = 0; ++ virtual void SetGroupList(const char *Group, int NumChannels, int Index, bool Current) = 0; ++ virtual void SetGroupChannelList(const cChannel *Channel, int Index, bool Current) = 0; ++ virtual void ClearList(void) = 0; ++ virtual void SetNumChannelHints(int Num) = 0; ++ virtual void SetChannelHint(const cChannel *Channel) = 0; ++}; ++ + enum eMenuCategory { + mcUndefined = -1, + mcUnknown = 0, +diff -Nur vdr-2.4.0/vdr.c vdr-2.4.0.p/vdr.c +--- vdr-2.4.0/vdr.c 2019-04-04 15:47:25.719519186 +0200 ++++ vdr-2.4.0.p/vdr.c 2019-04-04 14:57:48.241702864 +0200 +@@ -1088,7 +1088,7 @@ + // Channel display: + if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) { + if (!Menu) +- Menu = new cDisplayChannel(cDevice::CurrentChannel(), LastChannel >= 0); ++ Menu = new cDisplayChannelExtended(cDevice::CurrentChannel(), LastChannel >= 0); + LastChannel = cDevice::CurrentChannel(); + LastChannelChanged = Now; + } +@@ -1287,7 +1287,8 @@ + case kChanDn|k_Repeat: + case kChanDn: + if (!Interact) { +- Menu = new cDisplayChannel(NORMALKEY(key)); ++ Menu = new cDisplayChannelExtended(NORMALKEY(key)); ++ Menu->ProcessKey(NORMALKEY(key)); + continue; + } + else if (cDisplayChannel::IsOpen() || cControl::Control()) { +@@ -1480,7 +1481,8 @@ + case kUp: + case kDown|k_Repeat: + case kDown: +- Menu = new cDisplayChannel(NORMALKEY(key)); ++ Menu = new cDisplayChannelExtended(NORMALKEY(key)); ++ Menu->ProcessKey(NORMALKEY(key)); + break; + // Viewing Control: + case kOk: LastChannel = -1; break; // forces channel display