vdr/menu.c
Klaus Schmidinger 76c331181a Version 0.63
- The new "Setup" menu allows the user to configure several parameters to his/her
  personal taste (see MANUAL for details).
- 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 this is now configurable via the "Setup"
  menu.
- 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).
- Started a new file named FORMATS with a description of the various file
  formats used by VDR.
- The "Primary DVB interface" can now be chosen via the "Setup" menu.
- Display of the "current/next" information when switching channels can now
  be disabled via the "Setup" menu.
- The "current/next" display now only shows those lines that actually contain
  information.
- When directly selecting a channel by entering the channel number, the digits
  entered so far together with the name of that channel are displayed on the
  OSD (suggested by Martin Hammerschmid).
2000-09-10 18:00:00 +02:00

1400 lines
36 KiB
C

/*
* 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 1.26 2000/09/10 15:06:15 kls Exp $
*/
#include "menu.h"
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include "config.h"
#include "recording.h"
#define MENUTIMEOUT 120 // seconds
const char *FileNameChars = " aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789-.#^";
// --- cMenuEditItem ---------------------------------------------------------
class cMenuEditItem : public cOsdItem {
private:
const char *name;
const char *value;
public:
cMenuEditItem(const char *Name);
~cMenuEditItem();
void SetValue(const char *Value);
};
cMenuEditItem::cMenuEditItem(const char *Name)
{
name = strdup(Name);
value = NULL;
}
cMenuEditItem::~cMenuEditItem()
{
delete name;
delete value;
}
void cMenuEditItem::SetValue(const char *Value)
{
delete value;
value = strdup(Value);
char *buffer = NULL;
asprintf(&buffer, "%s:\t%s", name, value);
SetText(buffer, false);
Display();
}
// --- cMenuEditIntItem ------------------------------------------------------
class cMenuEditIntItem : public cMenuEditItem {
protected:
int *value;
int min, max;
virtual void Set(void);
public:
cMenuEditIntItem(const char *Name, int *Value, int Min = 0, int Max = INT_MAX);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuEditIntItem::cMenuEditIntItem(const char *Name, int *Value, int Min, int Max)
:cMenuEditItem(Name)
{
value = Value;
min = Min;
max = Max;
Set();
}
void cMenuEditIntItem::Set(void)
{
char buf[16];
snprintf(buf, sizeof(buf), "%d", *value);
SetValue(buf);
}
eOSState cMenuEditIntItem::ProcessKey(eKeys Key)
{
eOSState state = cMenuEditItem::ProcessKey(Key);
if (state == osUnknown) {
int newValue;
if (k0 <= Key && Key <= k9) {
if (fresh) {
*value = 0;
fresh = false;
}
newValue = *value * 10 + (Key - k0);
}
else if (Key == kLeft) { // TODO might want to increase the delta if repeated quickly?
newValue = *value - 1;
fresh = true;
}
else if (Key == kRight) {
newValue = *value + 1;
fresh = true;
}
else
return state;
if ((!fresh || min <= newValue) && newValue <= max) {
*value = newValue;
Set();
}
state = osContinue;
}
return state;
}
// --- cMenuEditBoolItem -----------------------------------------------------
class cMenuEditBoolItem : public cMenuEditIntItem {
protected:
virtual void Set(void);
public:
cMenuEditBoolItem(const char *Name, int *Value);
};
cMenuEditBoolItem::cMenuEditBoolItem(const char *Name, int *Value)
:cMenuEditIntItem(Name, Value, 0, 1)
{
Set();
}
void cMenuEditBoolItem::Set(void)
{
char buf[16];
snprintf(buf, sizeof(buf), "%s", *value ? "yes" : "no");
SetValue(buf);
}
// --- cMenuEditChanItem -----------------------------------------------------
class cMenuEditChanItem : public cMenuEditIntItem {
protected:
virtual void Set(void);
public:
cMenuEditChanItem(const char *Name, int *Value);
};
cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value)
:cMenuEditIntItem(Name, Value, 1, Channels.MaxNumber())
{
Set();
}
void cMenuEditChanItem::Set(void)
{
char buf[255];
cChannel *channel = Channels.GetByNumber(*value);
if (channel)
snprintf(buf, sizeof(buf), "%d %s", *value, channel->name);
else
*buf = 0;
SetValue(buf);
}
// --- cMenuEditDayItem ------------------------------------------------------
class cMenuEditDayItem : public cMenuEditIntItem {
protected:
static int days[];
int d;
virtual void Set(void);
public:
cMenuEditDayItem(const char *Name, int *Value);
virtual eOSState ProcessKey(eKeys Key);
};
int cMenuEditDayItem::days[] ={ cTimer::ParseDay("M------"),
cTimer::ParseDay("-T-----"),
cTimer::ParseDay("--W----"),
cTimer::ParseDay("---T---"),
cTimer::ParseDay("----F--"),
cTimer::ParseDay("-----S-"),
cTimer::ParseDay("------S"),
cTimer::ParseDay("MTWTF--"),
cTimer::ParseDay("MTWTFS-"),
cTimer::ParseDay("MTWTFSS"),
cTimer::ParseDay("-----SS"),
0 };
cMenuEditDayItem::cMenuEditDayItem(const char *Name, int *Value)
:cMenuEditIntItem(Name, Value, -INT_MAX, 31)
{
d = -1;
if (*value < 0) {
int n = 0;
while (days[n]) {
if (days[n] == *value) {
d = n;
break;
}
n++;
}
}
Set();
}
void cMenuEditDayItem::Set(void)
{
SetValue(cTimer::PrintDay(*value));
}
eOSState cMenuEditDayItem::ProcessKey(eKeys Key)
{
switch (Key) {
case kLeft: if (d > 0)
*value = days[--d];
else if (d == 0) {
*value = 31;
d = -1;
}
else if (*value == 1) {
d = sizeof(days) / sizeof(int) - 2;
*value = days[d];
}
else
return cMenuEditIntItem::ProcessKey(Key);
Set();
break;
case kRight: if (d >= 0) {
*value = days[++d];
if (*value == 0) {
*value = 1;
d = -1;
}
}
else if (*value == 31) {
d = 0;
*value = days[d];
}
else
return cMenuEditIntItem::ProcessKey(Key);
Set();
break;
default : return cMenuEditIntItem::ProcessKey(Key);
}
return osContinue;
}
// --- cMenuEditTimeItem -----------------------------------------------------
class cMenuEditTimeItem : public cMenuEditItem {
protected:
int *value;
int hh, mm;
int pos;
virtual void Set(void);
public:
cMenuEditTimeItem(const char *Name, int *Value);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuEditTimeItem::cMenuEditTimeItem(const char *Name, int *Value)
:cMenuEditItem(Name)
{
value = Value;
hh = *value / 100;
mm = *value % 100;
pos = 0;
Set();
}
void cMenuEditTimeItem::Set(void)
{
char buf[10];
snprintf(buf, sizeof(buf), "%02d:%02d", hh, mm);
SetValue(buf);
}
eOSState cMenuEditTimeItem::ProcessKey(eKeys Key)
{
eOSState state = cMenuEditItem::ProcessKey(Key);
if (state == osUnknown) {
if (k0 <= Key && Key <= k9) {
if (fresh || pos > 3) {
pos = 0;
fresh = false;
}
int n = Key - k0;
switch (pos) {
case 0: if (n <= 2) {
hh = n * 10;
mm = 0;
pos++;
}
break;
case 1: if (hh + n <= 23) {
hh += n;
pos++;
}
break;
case 2: if (n <= 5) {
mm += n * 10;
pos++;
}
break;
case 3: if (mm + n <= 59) {
mm += n;
pos++;
}
break;
}
}
else if (Key == kLeft) { // TODO might want to increase the delta if repeated quickly?
if (--mm < 0) {
mm = 59;
if (--hh < 0)
hh = 23;
}
fresh = true;
}
else if (Key == kRight) {
if (++mm > 59) {
mm = 0;
if (++hh > 23)
hh = 0;
}
fresh = true;
}
else
return state;
*value = hh * 100 + mm;
Set();
state = osContinue;
}
return state;
}
// --- cMenuEditChrItem ------------------------------------------------------
class cMenuEditChrItem : public cMenuEditItem {
private:
char *value;
const char *allowed;
const char *current;
virtual void Set(void);
public:
cMenuEditChrItem(const char *Name, char *Value, const char *Allowed);
~cMenuEditChrItem();
virtual eOSState ProcessKey(eKeys Key);
};
cMenuEditChrItem::cMenuEditChrItem(const char *Name, char *Value, const char *Allowed)
:cMenuEditItem(Name)
{
value = Value;
allowed = strdup(Allowed);
current = strchr(allowed, *Value);
if (!current)
current = allowed;
Set();
}
cMenuEditChrItem::~cMenuEditChrItem()
{
delete allowed;
}
void cMenuEditChrItem::Set(void)
{
char buf[2];
snprintf(buf, sizeof(buf), "%c", *value);
SetValue(buf);
}
eOSState cMenuEditChrItem::ProcessKey(eKeys Key)
{
eOSState state = cMenuEditItem::ProcessKey(Key);
if (state == osUnknown) {
if (Key == kLeft) {
if (current > allowed)
current--;
}
else if (Key == kRight) {
if (*(current + 1))
current++;
}
else
return state;
*value = *current;
Set();
state = osContinue;
}
return state;
}
// --- cMenuEditStrItem ------------------------------------------------------
class cMenuEditStrItem : public cMenuEditItem {
private:
char *value;
int length;
const char *allowed;
int pos;
virtual void Set(void);
char Inc(char c, bool Up);
public:
cMenuEditStrItem(const char *Name, char *Value, int Length, const char *Allowed);
~cMenuEditStrItem();
virtual eOSState ProcessKey(eKeys Key);
};
cMenuEditStrItem::cMenuEditStrItem(const char *Name, char *Value, int Length, const char *Allowed)
:cMenuEditItem(Name)
{
value = Value;
length = Length;
allowed = strdup(Allowed);
pos = -1;
Set();
}
cMenuEditStrItem::~cMenuEditStrItem()
{
delete allowed;
}
void cMenuEditStrItem::Set(void)
{
char buf[1000];
if (pos >= 0) {
strncpy(buf, value, pos);
const char *s = value[pos] != '^' ? value + pos + 1 : "";
snprintf(buf + pos, sizeof(buf) - pos - 2, "[%c]%s", *(value + pos), s);
SetValue(buf);
}
else
SetValue(value);
}
char cMenuEditStrItem::Inc(char c, bool Up)
{
const char *p = strchr(allowed, c);
if (!p)
p = allowed;
if (Up) {
if (!*++p)
p = allowed;
}
else if (--p < allowed)
p = allowed + strlen(allowed) - 1;
return *p;
}
eOSState cMenuEditStrItem::ProcessKey(eKeys Key)
{
switch (Key) {
case kLeft: if (pos > 0) {
if (value[pos] == '^')
value[pos] = 0;
pos--;
}
break;
case kRight: if (pos < length && value[pos] != '^' && (pos < int(strlen(value) - 1) || value[pos] != ' ')) {
if (++pos >= int(strlen(value))) {
value[pos] = ' ';
value[pos + 1] = 0;
}
}
break;
case kUp:
case kDown: if (pos >= 0)
value[pos] = Inc(value[pos], Key == kUp);
else
return cMenuEditItem::ProcessKey(Key);
break;
case kOk: if (pos >= 0) {
if (value[pos] == '^')
value[pos] = 0;
pos = -1;
break;
}
// run into default
default: return cMenuEditItem::ProcessKey(Key);
}
Set();
return osContinue;
}
// --- cMenuEditChannel ------------------------------------------------------
class cMenuEditChannel : public cOsdMenu {
private:
cChannel *channel;
cChannel data;
public:
cMenuEditChannel(int Index);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuEditChannel::cMenuEditChannel(int Index)
:cOsdMenu("Edit Channel", 14)
{
channel = Channels.Get(Index);
if (channel) {
data = *channel;
Add(new cMenuEditStrItem( "Name", data.name, sizeof(data.name), FileNameChars));
Add(new cMenuEditIntItem( "Frequency", &data.frequency, 10000, 13000)); //TODO exact limits???
Add(new cMenuEditChrItem( "Polarization", &data.polarization, "hv"));
Add(new cMenuEditIntItem( "Diseqc", &data.diseqc, 0, 10)); //TODO exact limits???
Add(new cMenuEditIntItem( "Srate", &data.srate, 22000, 27500)); //TODO exact limits - toggle???
Add(new cMenuEditIntItem( "Vpid", &data.vpid, 0, 10000)); //TODO exact limits???
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));
}
}
eOSState cMenuEditChannel::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
if (Key == kOk) {
if (channel)
*channel = data;
Channels.Save();
state = osBack;
}
}
return state;
}
// --- cMenuChannelItem ------------------------------------------------------
class cMenuChannelItem : public cOsdItem {
private:
int index;
cChannel *channel;
public:
cMenuChannelItem(int Index, cChannel *Channel);
virtual void Set(void);
void SetIndex(int Index);
};
cMenuChannelItem::cMenuChannelItem(int Index, cChannel *Channel)
{
index = Index;
channel = Channel;
if (channel->groupSep)
SetColor(clrWhite, clrBlue);
Set();
}
void cMenuChannelItem::Set(void)
{
char *buffer = NULL;
if (!channel->groupSep)
asprintf(&buffer, "%d\t%s", channel->number, channel->name );
else
asprintf(&buffer, "\t%s", channel->name);
SetText(buffer, false);
}
void cMenuChannelItem::SetIndex(int Index)
{
index = Index;
Set();
}
// --- cMenuChannels ---------------------------------------------------------
class cMenuChannels : public cOsdMenu {
protected:
eOSState Switch(void);
eOSState Edit(void);
eOSState New(void);
eOSState Del(void);
virtual void Move(int From, int To);
public:
cMenuChannels(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuChannels::cMenuChannels(void)
:cOsdMenu("Channels", 4)
{
//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 == curr);
i++;
}
SetHelp("Edit", "New", "Delete", "Mark");
}
eOSState cMenuChannels::Switch(void)
{
cChannel *ch = Channels.Get(Current());
if (ch)
ch->Switch();
return osEnd;
}
eOSState cMenuChannels::Edit(void)
{
if (HasSubMenu() || Count() == 0)
return osContinue;
isyslog(LOG_INFO, "editing channel %d", Current() + 1);
return AddSubMenu(new cMenuEditChannel(Current()));
}
eOSState cMenuChannels::New(void)
{
if (HasSubMenu())
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->number);
return AddSubMenu(new cMenuEditChannel(Current()));
}
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 == DeletedChannel) {
Interface.Error("Channel is being used by a timer!");
return osContinue;
}
}
if (Interface.Confirm("Delete Channel?")) {
// Move and renumber the channels:
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", DeletedChannel);
// Fix the timers:
bool TimersModified = false;
for (cTimer *ti = Timers.First(); ti; ti = (cTimer *)ti->Next()) {
int OldChannel = ti->channel;
if (ti->channel > DeletedChannel)
ti->channel--;
if (ti->channel != OldChannel) {
TimersModified = true;
isyslog(LOG_INFO, "timer %d: channel changed from %d to %d", ti->Index() + 1, OldChannel, ti->channel);
}
}
if (TimersModified)
Timers.Save();
Display();
}
}
return osContinue;
}
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", 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 == FromNumber)
ti->channel = ToNumber;
else if (ti->channel > FromNumber && ti->channel <= ToNumber)
ti->channel--;
else if (ti->channel < FromNumber && ti->channel >= ToNumber)
ti->channel++;
if (ti->channel != OldChannel) {
TimersModified = true;
isyslog(LOG_INFO, "timer %d: channel changed from %d to %d", ti->Index() + 1, OldChannel, ti->channel);
}
}
if (TimersModified)
Timers.Save();
Display();
}
eOSState cMenuChannels::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kOk: return Switch();
case kRed: return Edit();
case kGreen: return New();
case kYellow: return Del();
case kBlue: Mark(); break;
default: break;
}
}
return state;
}
// --- cMenuSummary --------------------------------------------------------
class cMenuSummary : public cOsdMenu {
public:
cMenuSummary(const char *Text);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuSummary::cMenuSummary(const char *Text)
:cOsdMenu("Summary")
{
while (*Text) {
char line[MenuColumns + 1];
char *p = line;
const char *b = NULL;
*p++ = ' ';
while (*Text && p - line < MenuColumns - 2) {
if (isspace(*Text))
b = Text; // remember the blank
if (*Text == '\n')
break;
*p++ = *Text++;
}
if (*Text) {
if (b && Text - b > 0) {
p -= Text - b;
Text = b + 1;
}
else
Text++;
}
*p = 0;
Add(new cOsdItem(line, osBack));
}
}
eOSState cMenuSummary::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown)
state = osContinue;
return state;
}
// --- cMenuEditTimer --------------------------------------------------------
class cMenuEditTimer : public cOsdMenu {
private:
cTimer *timer;
cTimer data;
public:
cMenuEditTimer(int Index, bool New = false);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuEditTimer::cMenuEditTimer(int Index, bool New)
:cOsdMenu("Edit Timer", 10)
{
timer = Timers.Get(Index);
if (timer) {
data = *timer;
if (New)
data.active = 1;
Add(new cMenuEditBoolItem("Active", &data.active));
Add(new cMenuEditChanItem("Channel", &data.channel));
Add(new cMenuEditDayItem( "Day", &data.day));
Add(new cMenuEditTimeItem("Start", &data.start));
Add(new cMenuEditTimeItem("Stop", &data.stop));
//TODO VPS???
Add(new cMenuEditIntItem( "Priority", &data.priority, 0, 99));
Add(new cMenuEditIntItem( "Lifetime", &data.lifetime, 0, 99));
Add(new cMenuEditStrItem( "File", data.file, sizeof(data.file), FileNameChars));
}
}
eOSState cMenuEditTimer::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
if (Key == kOk) {
if (!*data.file)
strcpy(data.file, Channels.GetChannelNameByNumber(data.channel));
if (timer && memcmp(timer, &data, sizeof(data)) != 0) {
*timer = data;
Timers.Save();
isyslog(LOG_INFO, "timer %d modified (%s)", timer->Index() + 1, timer->active ? "active" : "inactive");
}
state = osBack;
}
}
return state;
}
// --- cMenuTimerItem --------------------------------------------------------
class cMenuTimerItem : public cOsdItem {
private:
int index;
cTimer *timer;
public:
cMenuTimerItem(int Index, cTimer *Timer);
virtual void Set(void);
};
cMenuTimerItem::cMenuTimerItem(int Index, cTimer *Timer)
{
index = Index;
timer = Timer;
Set();
}
void cMenuTimerItem::Set(void)
{
char *buffer = NULL;
asprintf(&buffer, "%c\t%d\t%s\t%02d:%02d\t%02d:%02d\t%s",
timer->active ? '>' : ' ',
timer->channel,
timer->PrintDay(timer->day),
timer->start / 100,
timer->start % 100,
timer->stop / 100,
timer->stop % 100,
timer->file);
SetText(buffer, false);
}
// --- cMenuTimers -----------------------------------------------------------
class cMenuTimers : public cOsdMenu {
private:
eOSState Activate(bool On);
eOSState Edit(void);
eOSState New(void);
eOSState Del(void);
virtual void Move(int From, int To);
eOSState Summary(void);
public:
cMenuTimers(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuTimers::cMenuTimers(void)
:cOsdMenu("Timer", 2, 4, 10, 6, 6)
{
int i = 0;
cTimer *timer;
while ((timer = Timers.Get(i)) != NULL) {
Add(new cMenuTimerItem(i, timer));
i++;
}
SetHelp("Edit", "New", "Delete", "Mark");
}
eOSState cMenuTimers::Activate(bool On)
{
cTimer *timer = Timers.Get(Current());
if (timer && timer->active != On) {
timer->active = On;
RefreshCurrent();
DisplayCurrent(true);
isyslog(LOG_INFO, "timer %d %sactivated", timer->Index() + 1, timer->active ? "" : "de");
Timers.Save();
}
return osContinue;
}
eOSState cMenuTimers::Edit(void)
{
if (HasSubMenu() || Count() == 0)
return osContinue;
isyslog(LOG_INFO, "editing timer %d", Current() + 1);
return AddSubMenu(new cMenuEditTimer(Current()));
}
eOSState cMenuTimers::New(void)
{
if (HasSubMenu())
return osContinue;
cTimer *timer = new cTimer;
Timers.Add(timer);
Add(new cMenuTimerItem(timer->Index()/*XXX*/, timer), true);
Timers.Save();
isyslog(LOG_INFO, "timer %d added", timer->Index() + 1);
return AddSubMenu(new cMenuEditTimer(Current(), true));
}
eOSState cMenuTimers::Del(void)
{
// Check if this timer is active:
int Index = Current();
cTimer *ti = Timers.Get(Index);
if (ti) {
if (!ti->recording) {
if (Interface.Confirm("Delete Timer?")) {
Timers.Del(Timers.Get(Index));
cOsdMenu::Del(Index);
Timers.Save();
Display();
isyslog(LOG_INFO, "timer %d deleted", Index + 1);
}
}
else
Interface.Error("Timer is recording!");
}
return osContinue;
}
void cMenuTimers::Move(int From, int To)
{
Timers.Move(From, To);
cOsdMenu::Move(From, To);
Timers.Save();
Display();
isyslog(LOG_INFO, "timer %d moved to %d", From + 1, To + 1);
}
eOSState cMenuTimers::Summary(void)
{
if (HasSubMenu() || Count() == 0)
return osContinue;
cTimer *ti = Timers.Get(Current());
if (ti && ti->summary && *ti->summary)
return AddSubMenu(new cMenuSummary(ti->summary));
return Edit(); // convenience for people not using the Summary feature ;-)
}
eOSState cMenuTimers::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kLeft:
case kRight: return Activate(Key == kRight);
case kOk: return Summary();
case kRed: return Edit();
case kGreen: return New();
case kYellow: return Del();
case kBlue: Mark(); break;
default: break;
}
}
return state;
}
// --- cMenuRecordingItem ----------------------------------------------------
class cMenuRecordingItem : public cOsdItem {
public:
cRecording *recording;
cMenuRecordingItem(cRecording *Recording);
virtual void Set(void);
};
cMenuRecordingItem::cMenuRecordingItem(cRecording *Recording)
{
recording = Recording;
Set();
}
void cMenuRecordingItem::Set(void)
{
SetText(recording->Title('\t'));
}
// --- cMenuRecordings -------------------------------------------------------
class cMenuRecordings : public cOsdMenu {
private:
cRecordings Recordings;
eOSState Play(void);
eOSState Del(void);
eOSState Summary(void);
public:
cMenuRecordings(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuRecordings::cMenuRecordings(void)
:cOsdMenu("Recordings", 9, 6)
{
if (Recordings.Load()) {
cRecording *recording = Recordings.First();
while (recording) {
Add(new cMenuRecordingItem(recording));
recording = Recordings.Next(recording);
}
}
SetHelp("Play", NULL/*XXX"Resume"*/, "Delete", "Summary");
}
eOSState cMenuRecordings::Play(void)
{
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri) {
cReplayControl::SetRecording(ri->recording->FileName(), ri->recording->Title());
return osReplay;
}
return osContinue;
}
eOSState cMenuRecordings::Del(void)
{
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri) {
//XXX what if this recording's file is currently in use???
//XXX if (!ti->recording) {
if (Interface.Confirm("Delete Recording?")) {
if (ri->recording->Delete()) {
cOsdMenu::Del(Current());
Display();
}
else
Interface.Error("Error while deleting recording!");
}
//XXX }
//XXX else
//XXX Interface.Error("Timer is recording!");
}
return osContinue;
}
eOSState cMenuRecordings::Summary(void)
{
if (HasSubMenu() || Count() == 0)
return osContinue;
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri && ri->recording->Summary() && *ri->recording->Summary())
return AddSubMenu(new cMenuSummary(ri->recording->Summary()));
return osContinue;
}
eOSState cMenuRecordings::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kOk:
case kRed: return Play();
case kYellow: return Del();
case kBlue: return Summary();
default: break;
}
}
return state;
}
// --- cMenuSetup ------------------------------------------------------------
class cMenuSetup : public cOsdMenu {
private:
cSetup data;
public:
cMenuSetup(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuSetup::cMenuSetup(void)
:cOsdMenu("Setup", 20)
{
data = Setup;
Add(new cMenuEditIntItem( "PrimaryDVB", &data.PrimaryDVB, 1, cDvbApi::NumDvbApis));
Add(new cMenuEditBoolItem("ShowInfoOnChSwitch", &data.ShowInfoOnChSwitch));
Add(new cMenuEditBoolItem("MenuScrollPage", &data.MenuScrollPage));
}
eOSState cMenuSetup::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kOk: state = (Setup.PrimaryDVB != data.PrimaryDVB) ? osSwitchDvb : osBack;
Setup = data;
Setup.Save();
break;
default: break;
}
}
return state;
}
// --- cMenuMain -------------------------------------------------------------
#define STOP_RECORDING "Stop recording "
cMenuMain::cMenuMain(bool Replaying)
:cOsdMenu("Main")
{
Add(new cOsdItem("Channels", osChannels));
Add(new cOsdItem("Timer", osTimer));
Add(new cOsdItem("Recordings", osRecordings));
Add(new cOsdItem("Setup", osSetup));
if (Replaying)
Add(new cOsdItem("Stop replaying", osStopReplay));
const char *s = NULL;
while ((s = cRecordControls::GetInstantId(s)) != NULL) {
char *buffer = NULL;
asprintf(&buffer, "%s%s", STOP_RECORDING, s);
Add(new cOsdItem(buffer, osStopRecord));
delete buffer;
}
SetHelp("Record");
Display();
lastActivity = time(NULL);
}
eOSState cMenuMain::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
switch (state) {
case osChannels: return AddSubMenu(new cMenuChannels);
case osTimer: return AddSubMenu(new cMenuTimers);
case osRecordings: return AddSubMenu(new cMenuRecordings);
case osSetup: return AddSubMenu(new cMenuSetup);
case osStopRecord: if (Interface.Confirm("Stop Recording?")) {
cOsdItem *item = Get(Current());
if (item) {
cRecordControls::Stop(item->Text() + strlen(STOP_RECORDING));
return osEnd;
}
}
default: switch (Key) {
case kMenu: state = osEnd; break;
case kRed: if (!HasSubMenu())
state = osRecord;
break;
default: break;
}
}
if (Key != kNone)
lastActivity = time(NULL);
else if (time(NULL) - lastActivity > MENUTIMEOUT)
state = osEnd;
return state;
}
// --- cDirectChannelSelect --------------------------------------------------
#define DIRECTCHANNELTIMEOUT 500 //ms
cDirectChannelSelect::cDirectChannelSelect(eKeys FirstKey)
:cOsdBase(true)
{
oldNumber = CurrentChannel;
number = 0;
lastTime = time_ms();
Interface.Open(MenuColumns, 1);
ProcessKey(FirstKey);
}
cDirectChannelSelect::~cDirectChannelSelect()
{
if (number < 0)
Interface.DisplayChannel(oldNumber);
Interface.Close();
}
eOSState cDirectChannelSelect::ProcessKey(eKeys Key)
{
switch (Key) {
case k0: case k1: case k2: case k3: case k4: case k5: case k6: case k7: case k8: case k9:
if (number >= 0) {
number = number * 10 + Key - k0;
cChannel *channel = Channels.GetByNumber(number);
char *Name = channel ? channel->name : "*** Invalid Channel ***";
int BufSize = MenuColumns + 1;
char buffer[BufSize];
snprintf(buffer, BufSize, "%d %s", number, Name);
Interface.DisplayChannel(number);
Interface.Clear();
Interface.Write(0, 0, buffer);
lastTime = time_ms();
if (!channel) {
number = -1;
lastTime += 1000;
}
}
break;
case kNone:
if (time_ms() - lastTime > DIRECTCHANNELTIMEOUT) {
if (number > 0 && !Channels.SwitchTo(number))
number = -1;
}
else
break;
default: return osEnd;
};
return osContinue;
}
// --- cRecordControl --------------------------------------------------------
cRecordControl::cRecordControl(cDvbApi *DvbApi, cTimer *Timer)
{
instantId = NULL;
dvbApi = DvbApi;
if (!dvbApi) dvbApi = cDvbApi::PrimaryDvbApi;//XXX
timer = Timer;
if (!timer) {
timer = new cTimer(true);
Timers.Add(timer);
Timers.Save();
asprintf(&instantId, cDvbApi::NumDvbApis > 1 ? "%s on %d" : "%s", Channels.GetChannelNameByNumber(timer->channel), dvbApi->Index() + 1);
}
timer->SetRecording(true);
Channels.SwitchTo(timer->channel, dvbApi);
cRecording Recording(timer);
if (dvbApi->StartRecord(Recording.FileName()))
Recording.WriteSummary();
Interface.DisplayRecording(dvbApi->Index(), true);
}
cRecordControl::~cRecordControl()
{
Stop(true);
delete instantId;
Interface.DisplayRecording(dvbApi->Index(), false);
}
void cRecordControl::Stop(bool KeepInstant)
{
if (timer) {
dvbApi->StopRecord();
timer->SetRecording(false);
if ((IsInstant() && !KeepInstant) || (timer->IsSingleEvent() && !timer->Matches())) {
// checking timer->Matches() to make sure we don't delete the timer
// if the program was cancelled before the timer's stop time!
isyslog(LOG_INFO, "deleting timer %d", timer->Index() + 1);
Timers.Del(timer);
Timers.Save();
}
timer = NULL;
}
}
bool cRecordControl::Process(void)
{
if (!timer || !timer->Matches())
return false;
AssertFreeDiskSpace();
return true;
}
// --- cRecordControls -------------------------------------------------------
cRecordControl *cRecordControls::RecordControls[MAXDVBAPI] = { NULL };
bool cRecordControls::Start(cTimer *Timer)
{
int ch = Timer ? Timer->channel : CurrentChannel;
cChannel *channel = Channels.GetByNumber(ch);
if (channel) {
cDvbApi *dvbApi = cDvbApi::GetDvbApi(channel->ca);
if (dvbApi) {
for (int i = 0; i < MAXDVBAPI; i++) {
if (!RecordControls[i]) {
RecordControls[i] = new cRecordControl(dvbApi, Timer);
return true;
}
}
}
else
esyslog(LOG_ERR, "ERROR: no free DVB device to record channel %d!", ch);
}
else
esyslog(LOG_ERR, "ERROR: channel %d not defined!", ch);
return false;
}
void cRecordControls::Stop(const char *InstantId)
{
for (int i = 0; i < MAXDVBAPI; i++) {
if (RecordControls[i]) {
const char *id = RecordControls[i]->InstantId();
if (id && strcmp(id, InstantId) == 0)
RecordControls[i]->Stop();
}
}
}
const char *cRecordControls::GetInstantId(const char *LastInstantId)
{
for (int i = 0; i < MAXDVBAPI; i++) {
if (RecordControls[i]) {
if (!LastInstantId && RecordControls[i]->InstantId())
return RecordControls[i]->InstantId();
if (LastInstantId && LastInstantId == RecordControls[i]->InstantId())
LastInstantId = NULL;
}
}
return NULL;
}
void cRecordControls::Process(void)
{
for (int i = 0; i < MAXDVBAPI; i++) {
if (RecordControls[i]) {
if (!RecordControls[i]->Process())
DELETENULL(RecordControls[i]);
}
}
}
// --- cReplayControl --------------------------------------------------------
char *cReplayControl::fileName = NULL;
char *cReplayControl::title = NULL;
cReplayControl::cReplayControl(void)
{
dvbApi = cDvbApi::PrimaryDvbApi;//XXX
visible = shown = false;
if (fileName)
dvbApi->StartReplay(fileName, title);
}
cReplayControl::~cReplayControl()
{
Hide();
dvbApi->Stop();
}
void cReplayControl::SetRecording(const char *FileName, const char *Title)
{
delete fileName;
delete title;
fileName = FileName ? strdup(FileName) : NULL;
title = Title ? strdup(Title) : NULL;
}
void cReplayControl::Show(void)
{
if (!visible) {
Interface.Open(MenuColumns, -3);
needsFastResponse = visible = true;
shown = dvbApi->ShowProgress(true);
}
}
void cReplayControl::Hide(void)
{
if (visible) {
Interface.Close();
needsFastResponse = visible = false;
}
}
eOSState cReplayControl::ProcessKey(eKeys Key)
{
if (!dvbApi->Replaying())
return osEnd;
if (visible)
shown = dvbApi->ShowProgress(!shown) || shown;
switch (Key) {
case kUp: dvbApi->Play(); break;
case kDown: dvbApi->Pause(); break;
case kBlue: Hide();
dvbApi->Stop();
return osEnd;
case kLeft: dvbApi->Backward(); break;
case kRight: dvbApi->Forward(); break;
case kGreen: dvbApi->Skip(-60); break;
case kYellow: dvbApi->Skip(60); break;
case kMenu: Hide(); return osMenu; // allow direct switching to menu
case kOk: visible ? Hide() : Show(); break;
default: return osUnknown;
}
return osContinue;
}