From c00d4ea326e61d76d7ab5760a5c06646d6b88ab0 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sat, 9 Sep 2000 14:57:43 +0200 Subject: [PATCH] Implemented 'channel grouping' --- CONTRIBUTORS | 5 ++ FORMATS | 35 ++++++++++++ HISTORY | 20 +++++++ MANUAL | 16 +++++- Makefile | 8 ++- config.c | 152 ++++++++++++++++++++++++++++++++++++++++----------- config.h | 30 +++++++--- dvbapi.c | 6 +- interface.c | 50 +++++++++-------- interface.h | 6 +- menu.c | 59 ++++++++++++-------- osd.c | 100 ++++++++++++++++++++++----------- osd.h | 9 ++- svdrp.c | 57 +++++++++---------- tools.c | 12 +++- tools.h | 3 +- vdr.c | 37 ++++++++++--- 17 files changed, 437 insertions(+), 168 deletions(-) create mode 100644 FORMATS diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 07f9d16d..173afe23 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -10,6 +10,8 @@ Carsten Koch Plamen Ganev for fixing the frequency offset for Hotbird channels for adding the 'xtvrc2vdr' tool (see Tools/xtvrc2vdr) + for adding the 'dvbrc2vdr' tool (see Tools/dvbrc2vdr) + for implementing "channel grouping" Heino Goldenstein for modifying scrolling through lists to make it page up and down @@ -19,3 +21,6 @@ Guido Fiala Robert Schneider for implementing EIT support for displaying the current/next info + +Niels de Carpentier + for adding a workaround for a driver timing problem in cDvbApi::Cmd(). diff --git a/FORMATS b/FORMATS new file mode 100644 index 00000000..776fb4fc --- /dev/null +++ b/FORMATS @@ -0,0 +1,35 @@ +Video Disk Recorder File Formats +-------------------------------- + +* channels.conf + + This file contains the channel setup. + It consists of two types of lines: "group delimiters" and "channel + definitions". + + A "group delimiter" is a line starting with a ':' as the very first + character, followed by arbitrary text. + Example: ":First group" + + A "channel definition" is a line with channel data, where the fields + are separated by ':' characters: + Example: "RTL:12188:h:1:27500:163:104:0:12003" + + The fields in a channel definition have the following meaning (from left + to right): + + - Name: the channel's name (if the name originally contains a ':' character + it has to be replaced by '|') + - Frequency in MHz (as an integer) + - Polarization (one of 'h', 'H', 'v', 'V') + - Diseqc number + - Symbol rate + - Video PID + - Audio PID + - Conditional Access (0 = Free To Air, 1 = can be decrypted by the first + DVB card, 2 = can be decrypted by the second DVB card) + - Program Number + +* timers.conf + + TODO diff --git a/HISTORY b/HISTORY index f5c3cb93..859e783e 100644 --- a/HISTORY +++ b/HISTORY @@ -138,3 +138,23 @@ Video Disk Recorder Revision History done for some of the channels in the default 'channels.conf'. Some other parameters in the default 'channels.conf' have also been updated, so please make sure your timers still use the correct channels! + +2000-09-09: Version 0.63 + +- Workaround for a driver timing problem in cDvbApi::Cmd(), which sometimes caused + the OSD to no longer be displayed (thanks to Niels de Carpentier). +- Added the '-m486' option to the compiler call. +- If a channel name contains a colon (':') it is now replaced with a '|' in + channels.conf. +- Not everybody appears to like the "page scrolling" mechanism introduced by + Heino Goldenstein in version 0.61, so the Makefile now reacts on NO_PAGE_SCROLL=1 + to suppress that. +- The new 'dvbrc2vdr' tool (thanks to Plamen Ganev!) can be used to convert + 'dvbrc' channel files into 'vdr' format. +- Channels can now be "grouped" (thanks to Plamen Ganev!). See MANUAL for details. + There is currently no mechanism to define and maintain "Channel groups" via + the menu, so you'll have to insert "Channel group" control lines into your + 'channels.conf' file manually (for example with a text editor). + XXX additional fields for 'preferred' etc??? +- Started a new file named FORMATS with a description of the various file + formats used by VDR. diff --git a/MANUAL b/MANUAL index b05c3508..4291f76b 100644 --- a/MANUAL +++ b/MANUAL @@ -12,8 +12,8 @@ Video Disk Recorder User's Manual Up Ch up Crsr up Crsr up Crsr up Crsr up Crsr up Play Down Ch down Crsr down Crsr down Crsr down Crsr down Crsr down Pause - Left - - - Disable Decrement - Search back - Right - - - Enable Increment - Search forward + Left Prev group - - Disable Decrement - Search back + Right Next group - - Enable Increment - Search forward Ok Ch display Select Switch Edit Accept Play Progress disp. Menu Menu on Menu off Menu off Menu off Menu off Menu off Menu on Back - Menu off Main menu Main menu Discard Main menu - @@ -72,6 +72,18 @@ Video Disk Recorder User's Manual To bring up the channel display without switching channels you can press the "Ok" button. +* Switching through channel groups + + If the 'channels.conf' file contains "group separators" you can switch + through these groups by pressing the "Left" and "Right" key while no + menu is being displayed. The channel display will show the name of the + group, and if you press the "Ok" button while the group name is being + displayed, you will switch to the first channel of that group. + + Channel groups can be whatever you decide them to be. You can either + group your channels by "Bouquet", by language, genre or whatever your + preferences may be. + * Instant Recording You can start recording the current channel by pressing the "Red" button diff --git a/Makefile b/Makefile index 9868ddb8..6f3e7014 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: Makefile 1.7 2000/09/03 09:26:24 kls Exp $ +# $Id: Makefile 1.8 2000/09/03 15:38:18 kls Exp $ DVBDIR = ../DVB @@ -21,8 +21,12 @@ ifdef DEBUG_OSD DEFINES += -DDEBUG_OSD endif +ifdef NO_PAGE_SCROLL +DEFINES += -DNO_PAGE_SCROLL +endif + %.o: %.c - g++ -g -O2 -Wall -c $(DEFINES) $(INCLUDES) $< + g++ -g -O2 -Wall -m486 -c $(DEFINES) $(INCLUDES) $< all: vdr diff --git a/config.c b/config.c index af5be871..32f910ec 100644 --- a/config.c +++ b/config.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.c 1.18 2000/09/03 09:20:22 kls Exp $ + * $Id: config.c 1.19 2000/09/09 14:50:58 kls Exp $ */ #include "config.h" @@ -196,11 +196,23 @@ cChannel::cChannel(const cChannel *Channel) apid = Channel ? Channel->apid : 256; ca = Channel ? Channel->ca : 0; pnr = Channel ? Channel->pnr : 0; + preferred = Channel ? Channel->preferred : 0; + groupSep = Channel ? Channel->groupSep : false; } const char *cChannel::ToText(cChannel *Channel) { - asprintf(&buffer, "%s:%d:%c:%d:%d:%d:%d:%d:%d\n", Channel->name, Channel->frequency, Channel->polarization, Channel->diseqc, Channel->srate, Channel->vpid, Channel->apid, Channel->ca, Channel->pnr); + char buf[MaxChannelName * 2]; + char *s = Channel->name; + if (strchr(s, ':')) { + s = strcpy(buf, s); + strreplace(s, ':', '|'); + } + delete buffer; + if (Channel->groupSep) + asprintf(&buffer, ":%s\n", s); + else + asprintf(&buffer, "%s:%d:%c:%d:%d:%d:%d:%d:%d:%d\n", s, Channel->frequency, Channel->polarization, Channel->diseqc, Channel->srate, Channel->vpid, Channel->apid, Channel->ca, Channel->pnr, Channel->preferred); return buffer; } @@ -212,13 +224,32 @@ const char *cChannel::ToText(void) bool cChannel::Parse(const char *s) { char *buffer = NULL; - if (9 == sscanf(s, "%a[^:]:%d:%c:%d:%d:%d:%d:%d:%d", &buffer, &frequency, &polarization, &diseqc, &srate, &vpid, &apid, &ca, &pnr)) { - strncpy(name, buffer, MaxChannelName - 1); - name[strlen(buffer)] = 0; - delete buffer; - return true; + if (*s == ':') { + if (*++s) { + strn0cpy(name, s, MaxChannelName); + name[strlen(name) - 1] = 0; // strip the '\n' + groupSep = true; + } + else + return false; } - return false; + else { + groupSep = false; + int fields = sscanf(s, "%a[^:]:%d:%c:%d:%d:%d:%d:%d:%d:%d", &buffer, &frequency, &polarization, &diseqc, &srate, &vpid, &apid, &ca, &pnr, &preferred); +#define VER062_FIELDS 9 +#define VER063_FIELDS 10 + if (fields == VER062_FIELDS || fields == VER063_FIELDS) { + strn0cpy(name, buffer, MaxChannelName); + delete buffer; + if (fields == VER062_FIELDS) { + preferred = 0; + } + } + else + return false; + } + strreplace(name, '|', ':'); + return true; } bool cChannel::Save(FILE *f) @@ -230,9 +261,9 @@ bool cChannel::Switch(cDvbApi *DvbApi) { if (!DvbApi) DvbApi = cDvbApi::PrimaryDvbApi; - if (!DvbApi->Recording()) { - isyslog(LOG_INFO, "switching to channel %d", Index() + 1); - CurrentChannel = Index(); + if (!DvbApi->Recording() && !groupSep) { + isyslog(LOG_INFO, "switching to channel %d", number); + CurrentChannel = number; for (int i = 3; i--;) { if (DvbApi->SetChannel(frequency, polarization, diseqc, srate, vpid, apid, ca, pnr)) { EIT.SetProgramNumber(pnr); @@ -242,22 +273,10 @@ bool cChannel::Switch(cDvbApi *DvbApi) } return false; } - Interface.Info("Channel locked (recording)!"); + Interface.Info(DvbApi->Recording() ? "Channel locked (recording)!" : name); return false; } -bool cChannel::SwitchTo(int i, cDvbApi *DvbApi) -{ - cChannel *channel = Channels.Get(i); - return channel && channel->Switch(DvbApi); -} - -const char *cChannel::GetChannelName(int i) -{ - cChannel *channel = Channels.Get(i); - return channel ? channel->name : NULL; -} - // -- cTimer ----------------------------------------------------------------- char *cTimer::buffer = NULL; @@ -267,7 +286,8 @@ cTimer::cTimer(bool Instant) startTime = stopTime = 0; recording = false; active = Instant; - channel = CurrentChannel + 1; + cChannel *ch = Channels.GetByNumber(CurrentChannel); + channel = ch ? ch->number : 0; time_t t = time(NULL); struct tm *now = localtime(&t); day = now->tm_mday; @@ -280,8 +300,8 @@ cTimer::cTimer(bool Instant) lifetime = 99; *file = 0; summary = NULL; - if (Instant) - snprintf(file, sizeof(file), "@%s", cChannel::GetChannelName(CurrentChannel)); + if (Instant && ch) + snprintf(file, sizeof(file), "@%s", ch->name); } cTimer::~cTimer() @@ -299,6 +319,7 @@ cTimer& cTimer::operator= (const cTimer &Timer) const char *cTimer::ToText(cTimer *Timer) { + delete buffer; asprintf(&buffer, "%d:%d:%s:%04d:%04d:%d:%d:%s:%s\n", Timer->active, Timer->channel, PrintDay(Timer->day), Timer->start, Timer->stop, Timer->priority, Timer->lifetime, Timer->file, Timer->summary ? Timer->summary : ""); return buffer; } @@ -368,11 +389,7 @@ bool cTimer::Parse(const char *s) if (8 <= sscanf(s, "%d:%d:%a[^:]:%d:%d:%d:%d:%a[^:\n]:%a[^\n]", &active, &channel, &buffer1, &start, &stop, &priority, &lifetime, &buffer2, &summary)) { //TODO add more plausibility checks day = ParseDay(buffer1); - int l = strlen(buffer2); - if (l >= MaxFileName) - l = MaxFileName - 1; - strncpy(file, buffer2, l); - file[l] = 0; + strn0cpy(file, buffer2, MaxFileName); delete buffer1; delete buffer2; return day != 0; @@ -470,10 +487,79 @@ cKeys Keys; // -- cChannels -------------------------------------------------------------- -int CurrentChannel = 0; +int CurrentChannel = 1; +int CurrentGroup = -1; cChannels Channels; +bool cChannels::Load(const char *FileName) +{ + if (cConfig::Load(FileName)) { + ReNumber(); + return true; + } + return false; +} + +int cChannels::GetNextGroup(int Idx) +{ + cChannel *channel = Get(++Idx); + while (channel && !channel->groupSep) + channel = Get(++Idx); + return channel ? Idx : -1; +} + +int cChannels::GetPrevGroup(int Idx) +{ + cChannel *channel = Get(--Idx); + while (channel && !channel->groupSep) + channel = Get(--Idx); + return channel ? Idx : -1; +} + +int cChannels::GetNextNormal(int Idx) +{ + cChannel *channel = Get(++Idx); + while (channel && channel->groupSep) + channel = Get(++Idx); + return channel ? Idx : -1; +} + +void cChannels::ReNumber( void ) +{ + int Number = 0; + cChannel *ch = (cChannel *)First(); + while (ch) { + if (!ch->groupSep) + ch->number = ++Number; + ch = (cChannel *)ch->Next(); + } + maxNumber = Number; +} + +cChannel *cChannels::GetByNumber(int Number) +{ + cChannel *channel = (cChannel *)First(); + while (channel) { + if (channel->number == Number) + return channel; + channel = (cChannel *)channel->Next(); + } + return NULL; +} + +bool cChannels::SwitchTo(int Number, cDvbApi *DvbApi) +{ + cChannel *channel = GetByNumber(Number); + return channel && channel->Switch(DvbApi); +} + +const char *cChannels::GetChannelNameByNumber(int Number) +{ + cChannel *channel = GetByNumber(Number); + return channel ? channel->name : NULL; +} + // -- cTimers ---------------------------------------------------------------- cTimers Timers; diff --git a/config.h b/config.h index 15e90501..2a8954b7 100644 --- a/config.h +++ b/config.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.15 2000/09/03 09:37:30 kls Exp $ + * $Id: config.h 1.16 2000/09/09 14:21:35 kls Exp $ */ #ifndef __CONFIG_H @@ -17,7 +17,7 @@ #include "dvbapi.h" #include "tools.h" -#define VDRVERSION "0.62" +#define VDRVERSION "0.63" #define MaxBuffer 10000 @@ -75,14 +75,15 @@ public: int apid; int ca; int pnr; + int preferred; //TODO implement "preferred channel" mechanism + int number; // Sequence number assigned on load + bool groupSep; cChannel(void); cChannel(const cChannel *Channel); const char *ToText(void); bool Parse(const char *s); bool Save(FILE *f); bool Switch(cDvbApi *DvbApi = NULL); - static bool SwitchTo(int i, cDvbApi *DvbApi = NULL); - static const char *GetChannelName(int i); }; class cTimer : public cListObject { @@ -130,7 +131,7 @@ private: cList::Clear(); } public: - bool Load(const char *FileName) + virtual bool Load(const char *FileName) { isyslog(LOG_INFO, "loading %s", FileName); bool result = true; @@ -182,14 +183,29 @@ public: } }; -class cChannels : public cConfig {}; - +class cChannels : public cConfig { +protected: + int maxNumber; +public: + cChannels(void) { maxNumber = 0; } + virtual bool Load(const char *FileName); + int GetNextGroup(int Idx); // Get next channel group + int GetPrevGroup(int Idx); // Get previous channel group + int GetNextNormal(int Idx); // Get next normal channel (not group) + void ReNumber(void); // Recalculate 'number' based on channel type + cChannel *GetByNumber(int Number); + const char *GetChannelNameByNumber(int Number); + bool SwitchTo(int Number, cDvbApi *DvbApi = NULL); + int MaxNumber(void) { return maxNumber; } + }; + class cTimers : public cConfig { public: cTimer *GetTimer(cTimer *Timer); }; extern int CurrentChannel; +extern int CurrentGroup; extern cChannels Channels; extern cTimers Timers; diff --git a/dvbapi.c b/dvbapi.c index 0562a4f5..271d93b0 100644 --- a/dvbapi.c +++ b/dvbapi.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbapi.c 1.22 2000/08/06 14:06:14 kls Exp $ + * $Id: dvbapi.c 1.23 2000/09/09 12:13:55 kls Exp $ */ #include "dvbapi.h" @@ -1199,6 +1199,10 @@ void cDvbApi::Cmd(OSD_Command cmd, int color, int x0, int y0, int x1, int y1, co dc.y1 = y1; dc.data = (void *)data; ioctl(videoDev, VIDIOCSOSDCOMMAND, &dc); + usleep(10); // XXX Workaround for a driver bug (cInterface::DisplayChannel() displayed texts at wrong places + // XXX and sometimes the OSD was no longer displayed). + // XXX Increase the value if the problem still persists on your particular system. + // TODO Check if this is still necessary with driver versions after 0.6. } } #endif diff --git a/interface.c b/interface.c index 2ae49c33..52879b64 100644 --- a/interface.c +++ b/interface.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: interface.c 1.11 2000/09/03 10:17:21 kls Exp $ + * $Id: interface.c 1.12 2000/09/09 14:17:48 kls Exp $ */ #include "interface.h" @@ -72,10 +72,10 @@ eKeys cInterface::GetKey(bool Wait) eKeys cInterface::Wait(int Seconds, bool KeepChar) { - int t0 = time_ms(); + int t0 = time_ms() + Seconds * 1000; eKeys Key = kNone; - while (time_ms() - t0 < Seconds * 1000) { + while (time_ms() < t0) { Key = GetKey(); if (Key != kNone) break; @@ -112,11 +112,9 @@ void cInterface::Write(int x, int y, const char *s, eDvbColor FgColor, eDvbColor cDvbApi::PrimaryDvbApi->Text(x, y, s, FgColor, BgColor); } -void cInterface::WriteText(int x, int y, const char *s, bool Current) +void cInterface::WriteText(int x, int y, const char *s, eDvbColor FgColor, eDvbColor BgColor) { if (open) { - eDvbColor FgColor = Current ? clrBlack : clrWhite; - eDvbColor BgColor = Current ? clrCyan : clrBackground; ClearEol(x, y, BgColor); int col = 0; for (;;) { @@ -315,36 +313,42 @@ void cInterface::LearnKeys(void) } } -void cInterface::DisplayChannel(int Number, const char *Name) +eKeys cInterface::DisplayChannel(int Number, const char *Name) { - RcIo.Number(Number); + // Number = 0 is used for channel group display and no EIT + if (Number) + RcIo.Number(Number); if (Name && !Recording()) { - Open(MenuColumns, EIT.IsValid() ? 5 : 1); - char buffer[MenuColumns + 1]; - snprintf(buffer, sizeof(buffer), "%d %s", Number, Name ? Name : ""); + //XXX Maybe show only those lines that have actual information??? + Open(MenuColumns, Number && EIT.IsValid() ? 5 : 1); + int BufSize = MenuColumns + 1; + char buffer[BufSize]; + if (Number) + snprintf(buffer, BufSize, "%d %s", Number, Name ? Name : ""); + else + snprintf(buffer, BufSize, "%s", Name ? Name : ""); Write(0, 0, buffer); time_t t = time(NULL); struct tm *now = localtime(&t); - snprintf(buffer, sizeof(buffer), "%02d:%02d", now->tm_hour, now->tm_min); + snprintf(buffer, BufSize, "%02d:%02d", now->tm_hour, now->tm_min); Write(-5, 0, buffer); - if (EIT.IsValid()) { - const int t = 7; + if (Number && EIT.IsValid()) { + const int t = 6; int w = MenuColumns - t; Write(0, 1, EIT.GetRunningTime(), clrYellow, clrBackground); - snprintf(buffer, sizeof(buffer), "%.*s", w, EIT.GetRunningTitle()); - Write(t, 1, buffer, clrCyan, clrBackground); - snprintf(buffer, sizeof(buffer), "%.*s", w, EIT.GetRunningSubtitle()); - Write(t, 2, buffer, clrCyan, clrBackground); + snprintf(buffer, BufSize, "%.*s", w, EIT.GetRunningTitle()); Write(t, 1, buffer, clrCyan, clrBackground); + snprintf(buffer, BufSize, "%.*s", w, EIT.GetRunningSubtitle()); Write(t, 2, buffer, clrCyan, clrBackground); Write(0, 3, EIT.GetNextTime(), clrYellow, clrBackground); - snprintf(buffer, sizeof(buffer), "%.*s", w, EIT.GetNextTitle()); - Write(t, 3, buffer, clrCyan, clrBackground); - snprintf(buffer, sizeof(buffer), "%.*s", w, EIT.GetNextSubtitle()); - Write(t, 4, buffer, clrCyan, clrBackground); + snprintf(buffer, BufSize, "%.*s", w, EIT.GetNextTitle()); Write(t, 3, buffer, clrCyan, clrBackground); + snprintf(buffer, BufSize, "%.*s", w, EIT.GetNextSubtitle()); Write(t, 4, buffer, clrCyan, clrBackground); } - if (Wait(5, true) == kOk) + eKeys Key = Wait(5, true); + if (Key == kOk) GetKey(); Close(); + return Key; } + return kNone; } void cInterface::DisplayRecording(int Index, bool On) diff --git a/interface.h b/interface.h index 2a1c1560..ce4646bf 100644 --- a/interface.h +++ b/interface.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: interface.h 1.9 2000/05/06 15:39:23 kls Exp $ + * $Id: interface.h 1.10 2000/09/03 14:34:24 kls Exp $ */ #ifndef __INTERFACE_H @@ -34,7 +34,7 @@ public: void ClearEol(int x, int y, eDvbColor Color = clrBackground); void SetCols(int *c); void Write(int x, int y, const char *s, eDvbColor FgColor = clrWhite, eDvbColor BgColor = clrBackground); - void WriteText(int x, int y, const char *s, bool Current = false); + void WriteText(int x, int y, const char *s, eDvbColor FgColor = clrWhite, eDvbColor BgColor = clrBlack); void Title(const char *s); void Status(const char *s, eDvbColor FgColor = clrBlack, eDvbColor BgColor = clrCyan); void Info(const char *s); @@ -42,7 +42,7 @@ public: bool Confirm(const char *s); void Help(const char *Red, const char *Green = NULL, const char *Yellow = NULL, const char *Blue = NULL); void LearnKeys(void); - void DisplayChannel(int Number, const char *Name = NULL); + eKeys DisplayChannel(int Number, const char *Name = NULL); void DisplayRecording(int Index, bool On); bool Recording(void); }; diff --git a/menu.c b/menu.c index 15c8379a..e8a79966 100644 --- a/menu.c +++ b/menu.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 1.22 2000/08/06 07:02:52 kls Exp $ + * $Id: menu.c 1.23 2000/09/09 14:43:37 kls Exp $ */ #include "menu.h" @@ -145,7 +145,7 @@ public: }; cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value) -:cMenuEditIntItem(Name, Value, 1, Channels.Count()) +:cMenuEditIntItem(Name, Value, 1, Channels.MaxNumber()) { Set(); } @@ -153,7 +153,7 @@ cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value) void cMenuEditChanItem::Set(void) { char buf[255]; - cChannel *channel = Channels.Get(*value - 1); + cChannel *channel = Channels.GetByNumber(*value); if (channel) snprintf(buf, sizeof(buf), "%d %s", *value, channel->name); else @@ -513,6 +513,7 @@ cMenuEditChannel::cMenuEditChannel(int Index) Add(new cMenuEditIntItem( "Apid", &data.apid, 0, 10000)); //TODO exact limits??? Add(new cMenuEditIntItem( "CA", &data.ca, 0, cDvbApi::NumDvbApis)); Add(new cMenuEditIntItem( "Pnr", &data.pnr, 0)); + Add(new cMenuEditIntItem( "Preferred", &data.preferred, 0, 1)); //TODO implement "preferred channel" mechanism } } @@ -547,13 +548,18 @@ cMenuChannelItem::cMenuChannelItem(int Index, cChannel *Channel) { index = Index; channel = Channel; + if (channel->groupSep) + SetColor(clrWhite, clrBlue); Set(); } void cMenuChannelItem::Set(void) { char *buffer = NULL; - asprintf(&buffer, "%d\t%s", index + 1, channel->name); // user visible channel numbers start with '1' + if (!channel->groupSep) + asprintf(&buffer, "%d\t%s", channel->number, channel->name ); + else + asprintf(&buffer, "\t%s", channel->name); SetText(buffer, false); } @@ -583,9 +589,10 @@ cMenuChannels::cMenuChannels(void) //TODO int i = 0; cChannel *channel; + int curr = ((channel = Channels.GetByNumber(CurrentChannel)) != NULL) ? channel->Index() : -1; while ((channel = Channels.Get(i)) != NULL) { - Add(new cMenuChannelItem(i, channel), i == CurrentChannel); + Add(new cMenuChannelItem(i, channel), i == curr); i++; } SetHelp("Edit", "New", "Delete", "Mark"); @@ -613,9 +620,10 @@ eOSState cMenuChannels::New(void) return osContinue; cChannel *channel = new cChannel(Channels.Get(Current())); Channels.Add(channel); + Channels.ReNumber(); Add(new cMenuChannelItem(channel->Index()/*XXX*/, channel), true); Channels.Save(); - isyslog(LOG_INFO, "channel %d added", channel->Index() + 1); + isyslog(LOG_INFO, "channel %d added", channel->number); return AddSubMenu(new cMenuEditChannel(Current())); } @@ -623,28 +631,30 @@ eOSState cMenuChannels::Del(void) { if (Count() > 0) { int Index = Current(); + cChannel *channel = Channels.Get(Index); + int DeletedChannel = channel->number; // Check if there is a timer using this channel: for (cTimer *ti = Timers.First(); ti; ti = (cTimer *)ti->Next()) { - if (ti->channel == Index + 1) { + if (ti->channel == DeletedChannel) { Interface.Error("Channel is being used by a timer!"); return osContinue; } } if (Interface.Confirm("Delete Channel?")) { // Move and renumber the channels: - Channels.Del(Channels.Get(Index)); + Channels.Del(channel); + Channels.ReNumber(); cOsdMenu::Del(Index); int i = 0; for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) ci->SetIndex(i++); Channels.Save(); - isyslog(LOG_INFO, "channel %d deleted", Index + 1); + isyslog(LOG_INFO, "channel %d deleted", DeletedChannel); // Fix the timers: bool TimersModified = false; - Index++; // user visible channel numbers start with '1' for (cTimer *ti = Timers.First(); ti; ti = (cTimer *)ti->Next()) { int OldChannel = ti->channel; - if (ti->channel > Index) + if (ti->channel > DeletedChannel) ti->channel--; if (ti->channel != OldChannel) { TimersModified = true; @@ -661,25 +671,28 @@ eOSState cMenuChannels::Del(void) void cMenuChannels::Move(int From, int To) { + int FromNumber = Channels.Get(From)->number; + int ToNumber = Channels.Get(To)->number; // Move and renumber the channels: Channels.Move(From, To); + Channels.ReNumber(); cOsdMenu::Move(From, To); int i = 0; for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) ci->SetIndex(i++); Channels.Save(); - isyslog(LOG_INFO, "channel %d moved to %d", From + 1, To + 1); + isyslog(LOG_INFO, "channel %d moved to %d", FromNumber, ToNumber); // Fix the timers: bool TimersModified = false; From++; // user visible channel numbers start with '1' To++; for (cTimer *ti = Timers.First(); ti; ti = (cTimer *)ti->Next()) { int OldChannel = ti->channel; - if (ti->channel == From) - ti->channel = To; - else if (ti->channel > From && ti->channel <= To) + if (ti->channel == FromNumber) + ti->channel = ToNumber; + else if (ti->channel > FromNumber && ti->channel <= ToNumber) ti->channel--; - else if (ti->channel < From && ti->channel >= To) + else if (ti->channel < FromNumber && ti->channel >= ToNumber) ti->channel++; if (ti->channel != OldChannel) { TimersModified = true; @@ -791,7 +804,7 @@ eOSState cMenuEditTimer::ProcessKey(eKeys Key) if (state == osUnknown) { if (Key == kOk) { if (!*data.file) - strcpy(data.file, cChannel::GetChannelName(data.channel - 1)); + strcpy(data.file, Channels.GetChannelNameByNumber(data.channel)); if (timer && memcmp(timer, &data, sizeof(data)) != 0) { *timer = data; Timers.Save(); @@ -1125,10 +1138,10 @@ cRecordControl::cRecordControl(cDvbApi *DvbApi, cTimer *Timer) timer = new cTimer(true); Timers.Add(timer); Timers.Save(); - asprintf(&instantId, cDvbApi::NumDvbApis > 1 ? "%s on %d" : "%s", cChannel::GetChannelName(timer->channel - 1), dvbApi->Index() + 1); + asprintf(&instantId, cDvbApi::NumDvbApis > 1 ? "%s on %d" : "%s", Channels.GetChannelNameByNumber(timer->channel), dvbApi->Index() + 1); } timer->SetRecording(true); - cChannel::SwitchTo(timer->channel - 1, dvbApi); + Channels.SwitchTo(timer->channel, dvbApi); cRecording Recording(timer); if (dvbApi->StartRecord(Recording.FileName())) Recording.WriteSummary(); @@ -1172,8 +1185,8 @@ cRecordControl *cRecordControls::RecordControls[MAXDVBAPI] = { NULL }; bool cRecordControls::Start(cTimer *Timer) { - int ch = Timer ? Timer->channel - 1 : CurrentChannel; - cChannel *channel = Channels.Get(ch); + int ch = Timer ? Timer->channel : CurrentChannel; + cChannel *channel = Channels.GetByNumber(ch); if (channel) { cDvbApi *dvbApi = cDvbApi::GetDvbApi(channel->ca); @@ -1186,10 +1199,10 @@ bool cRecordControls::Start(cTimer *Timer) } } else - esyslog(LOG_ERR, "ERROR: no free DVB device to record channel %d!", ch + 1); + esyslog(LOG_ERR, "ERROR: no free DVB device to record channel %d!", ch); } else - esyslog(LOG_ERR, "ERROR: channel %d not defined!", ch + 1); + esyslog(LOG_ERR, "ERROR: channel %d not defined!", ch); return false; } diff --git a/osd.c b/osd.c index 3c323738..0efa837b 100644 --- a/osd.c +++ b/osd.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osd.c 1.5 2000/07/26 17:35:09 kls Exp $ + * $Id: osd.c 1.6 2000/09/09 14:28:57 kls Exp $ */ #include "osd.h" @@ -19,6 +19,9 @@ cOsdItem::cOsdItem(eOSState State) offset = -1; state = State; fresh = false; + userColor = false; + fgColor = clrWhite; + bgColor = clrBackground; } cOsdItem::cOsdItem(char *Text, eOSState State) @@ -27,6 +30,9 @@ cOsdItem::cOsdItem(char *Text, eOSState State) offset = -1; state = State; fresh = false; + userColor = false; + fgColor = clrWhite; + bgColor = clrBackground; SetText(Text); } @@ -41,15 +47,24 @@ void cOsdItem::SetText(const char *Text, bool Copy) text = Copy ? strdup(Text) : Text; } -void cOsdItem::Display(int Offset, bool Current) +void cOsdItem::SetColor(eDvbColor FgColor, eDvbColor BgColor) { + userColor = true; + fgColor = FgColor; + bgColor = BgColor; +} + +void cOsdItem::Display(int Offset, eDvbColor FgColor, eDvbColor BgColor) +{ + if (Offset < 0) { + FgColor = clrBlack; + BgColor = clrCyan; + } fresh |= Offset >= 0; - Current |= Offset < 0; if (Offset >= 0) offset = Offset; - //TODO current if Offset == -1 ??? if (offset >= 0) - Interface.WriteText(0, offset + 2, text, Current); + Interface.WriteText(0, offset + 2, text, userColor ? fgColor : FgColor, userColor ? bgColor : BgColor); } eOSState cOsdItem::ProcessKey(eKeys Key) @@ -100,6 +115,10 @@ void cOsdMenu::SetHelp(const char *Red, const char *Green, const char *Yellow, c helpGreen = Green; helpYellow = Yellow; helpBlue = Blue; + if (visible) + Display(); + //XXX Interface.Help(helpRed, helpGreen, helpYellow, helpBlue); + //XXX must clear unused button areas! } void cOsdMenu::Del(int Index) @@ -140,7 +159,7 @@ void cOsdMenu::Display(void) for (int i = first; i < count; i++) { cOsdItem *item = Get(i); if (item) - item->Display(i - first, i == current); + item->Display(i - first, i == current ? clrBlack : clrWhite, i == current ? clrCyan : clrBackground); if (++n == MAXOSDITEMS) //TODO get this from Interface!!! break; } @@ -159,49 +178,64 @@ void cOsdMenu::DisplayCurrent(bool Current) { cOsdItem *item = Get(current); if (item) - item->Display(current - first, Current); + item->Display(current - first, Current ? clrBlack : clrWhite, Current ? clrCyan : clrBackground); +} + +bool cOsdMenu::SpecialItem(int idx) +{ + cOsdItem *item = Get(idx); + return item && item->HasUserColor(); } void cOsdMenu::CursorUp(void) { if (current > 0) { - DisplayCurrent(false); - if (current == first) { - first -= MAXOSDITEMS; - if (first < 0) - first = 0; - if (current - MAXOSDITEMS > 0) - current -= MAXOSDITEMS; - else - current--; + int tmpCurrent = current; + while (--tmpCurrent >= 0 && SpecialItem(tmpCurrent)); + if (tmpCurrent < 0) + return; + if (tmpCurrent >= first) + DisplayCurrent(false); + current = tmpCurrent; + if (current < first) { + first = first > MAXOSDITEMS - 1 ? first - (MAXOSDITEMS - 1) : 0; +#ifndef NO_PAGE_SCROLL + current = SpecialItem(first) ? first + 1 : first; +#endif Display(); } - else { - current--; + else DisplayCurrent(true); - } } } void cOsdMenu::CursorDown(void) { - int count = Count(); - if (current < count - 1) { - DisplayCurrent(false); - if (current == first + MAXOSDITEMS - 1) { - first += MAXOSDITEMS; - if (first > count - MAXOSDITEMS) - first = count - MAXOSDITEMS; - if (current + MAXOSDITEMS < count) - current += MAXOSDITEMS; - else - current++; + int last = Count() - 1; + int lastOnScreen = first + MAXOSDITEMS - 1; + + if (current < last) { + int tmpCurrent = current; + while (++tmpCurrent <= last && SpecialItem(tmpCurrent)); + if (tmpCurrent > last) + return; + if (tmpCurrent <= lastOnScreen) + DisplayCurrent(false); + current = tmpCurrent; + if (current > lastOnScreen) { + first += MAXOSDITEMS - 1; + lastOnScreen = first + MAXOSDITEMS - 1; + if (lastOnScreen > last) { + first = last - (MAXOSDITEMS - 1); + lastOnScreen = last; + } +#ifndef NO_PAGE_SCROLL + current = SpecialItem(lastOnScreen) ? lastOnScreen - 1 : lastOnScreen; +#endif Display(); } - else { - current++; + else DisplayCurrent(true); - } } } diff --git a/osd.h b/osd.h index 1d4f4cf0..1fd4b209 100644 --- a/osd.h +++ b/osd.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osd.h 1.9 2000/05/27 15:35:41 kls Exp $ + * $Id: osd.h 1.10 2000/09/03 14:50:22 kls Exp $ */ #ifndef __OSD_H @@ -37,13 +37,17 @@ private: eOSState state; protected: bool fresh; + bool userColor; + eDvbColor fgColor, bgColor; public: cOsdItem(eOSState State = osUnknown); cOsdItem(char *Text, eOSState State = osUnknown); virtual ~cOsdItem(); + bool HasUserColor(void) { return userColor; } void SetText(const char *Text, bool Copy = true); + void SetColor(eDvbColor FgColor, eDvbColor BgColor = clrBackground); const char *Text(void) { return text; } - void Display(int Offset = -1, bool Current = false); + void Display(int Offset = -1, eDvbColor FgColor = clrWhite, eDvbColor BgColor = clrBackground); virtual void Set(void) {} virtual eOSState ProcessKey(eKeys Key); }; @@ -68,6 +72,7 @@ private: const char *status; protected: bool visible; + bool SpecialItem(int idx); void RefreshCurrent(void); void DisplayCurrent(bool Current); void CursorUp(void); diff --git a/svdrp.c b/svdrp.c index 01e9fb7b..2af3ee53 100644 --- a/svdrp.c +++ b/svdrp.c @@ -10,7 +10,7 @@ * and interact with the Video Disk Recorder - or write a full featured * graphical interface that sits on top of an SVDRP connection. * - * $Id: svdrp.c 1.5 2000/08/26 12:51:51 kls Exp $ + * $Id: svdrp.c 1.6 2000/09/09 10:51:21 kls Exp $ */ #define _GNU_SOURCE @@ -279,24 +279,24 @@ void cSVDRP::CmdChan(const char *Option) if (*Option) { int n = -1; if (isnumber(Option)) { - int o = strtol(Option, NULL, 10) - 1; - if (o >= 0 && o < Channels.Count()) + int o = strtol(Option, NULL, 10); + if (o >= 1 && o <= Channels.MaxNumber()) n = o; } else if (strcmp(Option, "-") == 0) { n = CurrentChannel; - if (CurrentChannel > 0) + if (CurrentChannel > 1) n--; } else if (strcmp(Option, "+") == 0) { n = CurrentChannel; - if (CurrentChannel < Channels.Count() - 1) + if (CurrentChannel < Channels.MaxNumber()) n++; } else { - int i = 0; + int i = 1; cChannel *channel; - while ((channel = Channels.Get(i)) != NULL) { + while ((channel = Channels.GetByNumber(i)) != NULL) { if (strcasecmp(channel->name, Option) == 0) { n = i; break; @@ -312,10 +312,10 @@ void cSVDRP::CmdChan(const char *Option) Reply(550, "Can't switch channel, interface is recording"); return; } - cChannel *channel = Channels.Get(n); + cChannel *channel = Channels.GetByNumber(n); if (channel) { if (!channel->Switch()) { - Reply(554, "Error switching to channel \"%d\"", channel->Index() + 1); + Reply(554, "Error switching to channel \"%d\"", channel->number); return; } } @@ -324,9 +324,9 @@ void cSVDRP::CmdChan(const char *Option) return; } } - cChannel *channel = Channels.Get(CurrentChannel); + cChannel *channel = Channels.GetByNumber(CurrentChannel); if (channel) - Reply(250, "%d %s", CurrentChannel + 1, channel->name); + Reply(250, "%d %s", CurrentChannel, channel->name); else Reply(550, "Unable to find channel \"%d\"", CurrentChannel); } @@ -394,41 +394,41 @@ void cSVDRP::CmdLstc(const char *Option) { if (*Option) { if (isnumber(Option)) { - cChannel *channel = Channels.Get(strtol(Option, NULL, 10) - 1); + cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10)); if (channel) - Reply(250, "%d %s", channel->Index() + 1, channel->ToText()); + Reply(250, "%d %s", channel->number, channel->ToText()); else Reply(501, "Channel \"%s\" not defined", Option); } else { - int i = 0; + int i = 1; cChannel *next = NULL; - while (i < Channels.Count()) { - cChannel *channel = Channels.Get(i); + while (i <= Channels.MaxNumber()) { + cChannel *channel = Channels.GetByNumber(i); if (channel) { if (strcasestr(channel->name, Option)) { if (next) - Reply(-250, "%d %s", next->Index() + 1, next->ToText()); + Reply(-250, "%d %s", next->number, next->ToText()); next = channel; } } else { - Reply(501, "Channel \"%d\" not found", i + 1); + Reply(501, "Channel \"%d\" not found", i); return; } i++; } if (next) - Reply(250, "%d %s", next->Index() + 1, next->ToText()); + Reply(250, "%d %s", next->number, next->ToText()); } } else { - for (int i = 0; i < Channels.Count(); i++) { - cChannel *channel = Channels.Get(i); + for (int i = 1; i <= Channels.MaxNumber(); i++) { + cChannel *channel = Channels.GetByNumber(i); if (channel) - Reply(i < Channels.Count() - 1 ? -250 : 250, "%d %s", channel->Index() + 1, channel->ToText()); + Reply(i < Channels.MaxNumber() ? -250 : 250, "%d %s", channel->number, channel->ToText()); else - Reply(501, "Channel \"%d\" not found", i + 1); + Reply(501, "Channel \"%d\" not found", i); } } } @@ -464,7 +464,7 @@ void cSVDRP::CmdModc(const char *Option) int n = strtol(Option, &tail, 10); if (tail && tail != Option) { tail = skipspace(tail); - cChannel *channel = Channels.Get(n - 1); + cChannel *channel = Channels.GetByNumber(n); if (channel) { cChannel c = *channel; if (!c.Parse(tail)) { @@ -473,8 +473,8 @@ void cSVDRP::CmdModc(const char *Option) } *channel = c; Channels.Save(); - isyslog(LOG_INFO, "channel %d modified", channel->Index() + 1); - Reply(250, "%d %s", channel->Index() + 1, channel->ToText()); + isyslog(LOG_INFO, "channel %d modified", channel->number); + Reply(250, "%d %s", channel->number, channel->ToText()); } else Reply(501, "Channel \"%d\" not defined", n); @@ -537,9 +537,10 @@ void cSVDRP::CmdNewc(const char *Option) cChannel *channel = new cChannel; if (channel->Parse(Option)) { Channels.Add(channel); + Channels.ReNumber(); Channels.Save(); - isyslog(LOG_INFO, "channel %d added", channel->Index() + 1); - Reply(250, "%d %s", channel->Index() + 1, channel->ToText()); + isyslog(LOG_INFO, "channel %d added", channel->number); + Reply(250, "%d %s", channel->number, channel->ToText()); } else Reply(501, "Error in channel settings"); diff --git a/tools.c b/tools.c index 0dca69ee..e081ee79 100644 --- a/tools.c +++ b/tools.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.c 1.13 2000/07/29 18:41:45 kls Exp $ + * $Id: tools.c 1.14 2000/09/09 12:53:34 kls Exp $ */ #define _GNU_SOURCE @@ -97,6 +97,14 @@ char *readline(FILE *f) return NULL; } +char *strn0cpy(char *dest, const char *src, size_t n) +{ + char *s = dest; + for ( ; --n && (*dest = *src) != 0; dest++, src++) ; + *dest = 0; + return s; +} + char *strreplace(char *s, char c1, char c2) { char *p = s; @@ -418,6 +426,8 @@ void cListBase::Clear(void) cListObject *cListBase::Get(int Index) { + if (Index < 0) + return NULL; cListObject *object = objects; while (object && Index-- > 0) object = object->Next(); diff --git a/tools.h b/tools.h index 563f3d06..1ef955c5 100644 --- a/tools.h +++ b/tools.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.h 1.12 2000/07/29 10:56:00 kls Exp $ + * $Id: tools.h 1.13 2000/09/09 12:53:10 kls Exp $ */ #ifndef __TOOLS_H @@ -38,6 +38,7 @@ bool readint(int filedes, int &n); int readstring(int filedes, char *buffer, int size, bool wait = false); void purge(int filedes); char *readline(FILE *f); +char *strn0cpy(char *dest, const char *src, size_t n); char *strreplace(char *s, char c1, char c2); char *skipspace(char *s); int time_ms(void); diff --git a/vdr.c b/vdr.c index 69eceedb..5e322390 100644 --- a/vdr.c +++ b/vdr.c @@ -22,7 +22,7 @@ * * The project's page is at http://www.cadsoft.de/people/kls/vdr * - * $Id: vdr.c 1.27 2000/07/29 19:01:57 kls Exp $ + * $Id: vdr.c 1.28 2000/09/09 14:18:25 kls Exp $ */ #include @@ -53,6 +53,14 @@ void SignalHandler(int signum) Interrupted = signum; } +static eKeys ShowChannel(int Number, bool Group = false) +{ + cChannel *channel = Group ? Channels.Get(Number) : Channels.GetByNumber(Number); + if (channel) + return Interface.DisplayChannel(channel->number, channel->name); + return kNone; +} + int main(int argc, char *argv[]) { // Command line options: @@ -166,7 +174,7 @@ int main(int argc, char *argv[]) #endif Interface.Init(); - cChannel::SwitchTo(CurrentChannel); + Channels.SwitchTo(CurrentChannel); // Signal handlers: @@ -185,16 +193,13 @@ int main(int argc, char *argv[]) while (!Interrupted) { // Channel display: if (CurrentChannel != LastChannel) { - if (!Menu) { - cChannel *channel = Channels.Get(CurrentChannel); - if (channel) - Interface.DisplayChannel(CurrentChannel + 1, channel->name); - } + if (!Menu) + ShowChannel(CurrentChannel); LastChannel = CurrentChannel; } // Direct Channel Select (action): if (dcNumber && time_ms() - dcTime > DIRECTCHANNELTIMEOUT) { - cChannel::SwitchTo(dcNumber - 1); + Channels.SwitchTo(dcNumber); dcNumber = 0; LastChannel = -1; // in case an invalid channel number was entered! } @@ -246,11 +251,25 @@ int main(int argc, char *argv[]) } } break; + // Left/Right rotates trough channel groups: + case kLeft: + case kRight: if (!Interface.Recording()) { + int SaveGroup = CurrentGroup; + if (key == kRight) + CurrentGroup = Channels.GetNextGroup(CurrentGroup) ; + else + CurrentGroup = Channels.GetPrevGroup(CurrentGroup < 1 ? 1 : CurrentGroup); + if (CurrentGroup < 0) + CurrentGroup = SaveGroup; + if (ShowChannel(CurrentGroup, true) == kOk) + Channels.SwitchTo(Channels.Get(Channels.GetNextNormal(CurrentGroup))->number); + } + break; // Up/Down Channel Select: case kUp: case kDown: if (!Interface.Recording()) { int n = CurrentChannel + (key == kUp ? 1 : -1); - cChannel *channel = Channels.Get(n); + cChannel *channel = Channels.GetByNumber(n); if (channel) channel->Switch(); }