vdr/menuitems.c
Klaus Schmidinger 5117f35938 Version 1.7.26
Original announce message:
VDR developer version 1.7.26 is now available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.26.tar.bz2

A 'diff' against the previous version is available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.25-1.7.26.diff

MD5 checksums:

eb57398e44935ecb0bfc532bdfead312  vdr-1.7.26.tar.bz2
62e0f39e2c53ec2b5abd3ebb0e00ce3d  vdr-1.7.25-1.7.26.diff

WARNING:
========

This is a developer version. Even though I use it in my productive
environment. I strongly recommend that you only use it under controlled
conditions and for testing and debugging.

From the HISTORY file:
- Now checking for NULL in cOsd::AddPixmap() (suggested by Christoph Haubrich).
- Fixed the German translation of "VDR will shut down in %s minutes" (thanks to
  Frank Neumann).
- The replay progress display is now turned on whenever a mark is toggled (not
  only when one is set).
- Toggling a mark now restarts the timeout of the replay progress display.
- Fixed a possible race condition with the SVDRP commands CLRE and PUTE, where
  EPG data from the transponder could be handled even though it shouldn't be
  handled for 10 seconds.
- Added some missing member initializations in cBitmap.
- Improved displaying the play mode in the ST:TNG skin.
- Made the ST:TNG skin the default in case the user selected skin is not available.
- Improved displaying signal strength and quality in the ST:TNG skin's channel
  display.
- Fixed switching devices to the transponders of VPS recordings in case there
  are only bonded devices. The cDevice's "avoid device" mechanism has been replaced
  by using "occupied".
- Fixed selecting devices for the EPG scan in case the primary device is bonded
  with an other one.
- Removed the calls to EITScanner.UsesDevice(this) from dvb[hs]ddevice.c, because
  the code following these calls is only executed if LiveView is true, which is
  never the case when the EITScanner switches to a channel.
- Removed the obsolete function cEITScanner::UsesDevice(). If a plugin has used this
  function to find out whether a call to its cStatus::ChannelSwitch() function was
  due to a live channel switch, it can use that function's new parameter LiveView.
  Any plugins that implement cStatus::ChannelSwitch() need to add the parameter
  'bool LiveView' to that function.
- Fixed parsing channel data in case the audio pid is 0. On channels that only
  broadcast Dolby Digital audio there were sometimes log entries like
  "changing pids of channel ... from ... to ..." with no apparent difference
  between the old and new set of pids.
- Fixed checking pids in case a channel has only Dolby Digital audio.
- The Green button in the "Edit timer" menu can now be used to toggle between single
  shot and repeating timers. This is the same as pressing '0' when the "Day" field
  is selected, but it works at any time (and is more obvious).
- Revoked "If the first event in a schedule has a table id of 0x00, any incoming EIT data for
  that schedule from the DVB stream will be completely ignored".
- Added a new plugin interface for implementing EPG handlers.
  + A plugin can implement an EPG handler by creating an object derived from
    cEpgHandler and implementing the necessary member functions.
  + The special handling of events with table id 0x00 has been dropped.
    For backwards compatibility EPG events with table ids lower than 0x4E will
    be treated as if they had a table id of 0x4E, and the new plugin 'epgtableid0'
    can be used to have them handled like in previous versions.
  + The default table id for a newly created cEvent has been changed to 0xFF,
    which is higher than any normal table id that is broadcast in the EIT data.
  See PLUGINS.html, section "Electronic Program Guide" for more information.
2012-03-11 18:00:38 +01:00

1166 lines
32 KiB
C

/*
* menuitems.c: General purpose menu items
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: menuitems.c 2.12 2012/03/08 13:22:22 kls Exp $
*/
#include "menuitems.h"
#include <ctype.h>
#include <math.h>
#include <wctype.h>
#include "i18n.h"
#include "plugin.h"
#include "remote.h"
#include "skins.h"
#include "status.h"
#define AUTO_ADVANCE_TIMEOUT 1500 // ms before auto advance when entering characters via numeric keys
const char *FileNameChars = trNOOP("FileNameChars$ abcdefghijklmnopqrstuvwxyz0123456789-.,#~\\^$[]|()*+?{}/:%@&");
// --- cMenuEditItem ---------------------------------------------------------
cMenuEditItem::cMenuEditItem(const char *Name)
{
name = strdup(Name ? Name : "???");
SetHelp(NULL);
}
cMenuEditItem::~cMenuEditItem()
{
free(name);
}
void cMenuEditItem::SetValue(const char *Value)
{
cString buffer = cString::sprintf("%s:\t%s", name, Value);
SetText(buffer);
cStatus::MsgOsdCurrentItem(buffer);
}
void cMenuEditItem::SetHelp(const char *Red, const char *Green, const char *Yellow, const char *Blue)
{
// strings are NOT copied - must be constants!!!
helpRed = Red;
helpGreen = Green;
helpYellow = Yellow;
helpBlue = Blue;
helpDisplayed = false;
}
bool cMenuEditItem::DisplayHelp(void)
{
bool HasHelp = helpRed || helpGreen || helpYellow || helpBlue;
if (HasHelp && !helpDisplayed) {
cSkinDisplay::Current()->SetButtons(helpRed, helpGreen, helpYellow, helpBlue);
cStatus::MsgOsdHelpKeys(helpRed, helpGreen, helpYellow, helpBlue);
helpDisplayed = true;
}
return HasHelp;
}
// --- cMenuEditIntItem ------------------------------------------------------
cMenuEditIntItem::cMenuEditIntItem(const char *Name, int *Value, int Min, int Max, const char *MinString, const char *MaxString)
:cMenuEditItem(Name)
{
value = Value;
min = Min;
max = Max;
minString = MinString;
maxString = MaxString;
if (*value < min)
*value = min;
else if (*value > max)
*value = max;
Set();
}
void cMenuEditIntItem::Set(void)
{
if (minString && *value == min)
SetValue(minString);
else if (maxString && *value == max)
SetValue(maxString);
else {
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 = *value;
bool IsRepeat = Key & k_Repeat;
Key = NORMALKEY(Key);
switch (Key) {
case kNone: break;
case k0 ... k9:
if (fresh) {
newValue = 0;
fresh = false;
}
newValue = newValue * 10 + (Key - k0);
break;
case kLeft: // TODO might want to increase the delta if repeated quickly?
newValue = *value - 1;
fresh = true;
if (!IsRepeat && newValue < min && max != INT_MAX)
newValue = max;
break;
case kRight:
newValue = *value + 1;
fresh = true;
if (!IsRepeat && newValue > max && min != INT_MIN)
newValue = min;
break;
default:
if (*value < min) { *value = min; Set(); }
if (*value > max) { *value = max; Set(); }
return state;
}
if (newValue != *value && (!fresh || min <= newValue) && newValue <= max) {
*value = newValue;
Set();
}
state = osContinue;
}
return state;
}
// --- cMenuEditBoolItem -----------------------------------------------------
cMenuEditBoolItem::cMenuEditBoolItem(const char *Name, int *Value, const char *FalseString, const char *TrueString)
:cMenuEditIntItem(Name, Value, 0, 1)
{
falseString = FalseString ? FalseString : tr("no");
trueString = TrueString ? TrueString : tr("yes");
Set();
}
void cMenuEditBoolItem::Set(void)
{
char buf[16];
snprintf(buf, sizeof(buf), "%s", *value ? trueString : falseString);
SetValue(buf);
}
// --- cMenuEditBitItem ------------------------------------------------------
cMenuEditBitItem::cMenuEditBitItem(const char *Name, uint *Value, uint Mask, const char *FalseString, const char *TrueString)
:cMenuEditBoolItem(Name, &bit, FalseString, TrueString)
{
value = Value;
bit = (*value & Mask) != 0;
mask = Mask;
Set();
}
void cMenuEditBitItem::Set(void)
{
*value = bit ? *value | mask : *value & ~mask;
cMenuEditBoolItem::Set();
}
// --- cMenuEditNumItem ------------------------------------------------------
cMenuEditNumItem::cMenuEditNumItem(const char *Name, char *Value, int Length, bool Blind)
:cMenuEditItem(Name)
{
value = Value;
length = Length;
blind = Blind;
Set();
}
void cMenuEditNumItem::Set(void)
{
if (blind) {
char buf[length + 1];
int i;
for (i = 0; i < length && value[i]; i++)
buf[i] = '*';
buf[i] = 0;
SetValue(buf);
}
else
SetValue(value);
}
eOSState cMenuEditNumItem::ProcessKey(eKeys Key)
{
eOSState state = cMenuEditItem::ProcessKey(Key);
if (state == osUnknown) {
Key = NORMALKEY(Key);
switch (Key) {
case kLeft: {
int l = strlen(value);
if (l > 0)
value[l - 1] = 0;
}
break;
case k0 ... k9: {
int l = strlen(value);
if (l < length) {
value[l] = Key - k0 + '0';
value[l + 1] = 0;
}
}
break;
default: return state;
}
Set();
state = osContinue;
}
return state;
}
// --- cMenuEditPrcItem ------------------------------------------------------
cMenuEditPrcItem::cMenuEditPrcItem(const char *Name, double *Value, double Min, double Max, int Decimals)
:cMenuEditItem(Name)
{
value = Value;
min = Min;
max = Max;
decimals = Decimals;
factor = 100;
while (Decimals-- > 0)
factor *= 10;
if (*value < min)
*value = min;
else if (*value > max)
*value = max;
Set();
}
void cMenuEditPrcItem::Set(void)
{
char buf[16];
snprintf(buf, sizeof(buf), "%.*f", decimals, *value * 100);
SetValue(buf);
}
eOSState cMenuEditPrcItem::ProcessKey(eKeys Key)
{
eOSState state = cMenuEditItem::ProcessKey(Key);
if (state == osUnknown) {
double newValue = round(*value * factor); // avoids precision problems
Key = NORMALKEY(Key);
switch (Key) {
case kNone: break;
case k0 ... k9:
if (fresh) {
newValue = 0;
fresh = false;
}
newValue = newValue * 10 + (Key - k0);
break;
case kLeft: // TODO might want to increase the delta if repeated quickly?
newValue--;
fresh = true;
break;
case kRight:
newValue++;
fresh = true;
break;
default:
if (*value < min) { *value = min; Set(); }
if (*value > max) { *value = max; Set(); }
return state;
}
newValue /= factor;
if (!DoubleEqual(newValue, *value) && (!fresh || min <= newValue) && newValue <= max) {
*value = newValue;
Set();
}
state = osContinue;
}
return state;
}
// --- cMenuEditChrItem ------------------------------------------------------
cMenuEditChrItem::cMenuEditChrItem(const char *Name, char *Value, const char *Allowed)
:cMenuEditItem(Name)
{
value = Value;
allowed = strdup(Allowed ? Allowed : "");
current = strchr(allowed, *Value);
if (!current)
current = allowed;
Set();
}
cMenuEditChrItem::~cMenuEditChrItem()
{
free(allowed);
}
void cMenuEditChrItem::Set(void)
{
char buf[2];
buf[0] = *value;
buf[1] = '\0';
SetValue(buf);
}
eOSState cMenuEditChrItem::ProcessKey(eKeys Key)
{
eOSState state = cMenuEditItem::ProcessKey(Key);
if (state == osUnknown) {
if (NORMALKEY(Key) == kLeft) {
if (current > allowed)
current--;
}
else if (NORMALKEY(Key) == kRight) {
if (*(current + 1))
current++;
}
else
return state;
*value = *current;
Set();
state = osContinue;
}
return state;
}
// --- cMenuEditStrItem ------------------------------------------------------
cMenuEditStrItem::cMenuEditStrItem(const char *Name, char *Value, int Length, const char *Allowed)
:cMenuEditItem(Name)
{
value = Value;
length = Length;
allowed = Allowed ? Allowed : tr(FileNameChars);
pos = -1;
offset = 0;
insert = uppercase = false;
newchar = true;
lengthUtf8 = 0;
valueUtf8 = NULL;
allowedUtf8 = NULL;
charMapUtf8 = NULL;
currentCharUtf8 = NULL;
lastKey = kNone;
Set();
}
cMenuEditStrItem::~cMenuEditStrItem()
{
delete[] valueUtf8;
delete[] allowedUtf8;
delete[] charMapUtf8;
}
void cMenuEditStrItem::EnterEditMode(void)
{
if (!valueUtf8) {
valueUtf8 = new uint[length];
lengthUtf8 = Utf8ToArray(value, valueUtf8, length);
int l = strlen(allowed) + 1;
allowedUtf8 = new uint[l];
Utf8ToArray(allowed, allowedUtf8, l);
const char *charMap = tr("CharMap$ 0\t-.,1#~\\^$[]|()*+?{}/:%@&\tabc2\tdef3\tghi4\tjkl5\tmno6\tpqrs7\ttuv8\twxyz9");
l = strlen(charMap) + 1;
charMapUtf8 = new uint[l];
Utf8ToArray(charMap, charMapUtf8, l);
currentCharUtf8 = charMapUtf8;
AdvancePos();
}
}
void cMenuEditStrItem::LeaveEditMode(bool SaveValue)
{
if (valueUtf8) {
if (SaveValue) {
Utf8FromArray(valueUtf8, value, length);
stripspace(value);
}
lengthUtf8 = 0;
delete[] valueUtf8;
valueUtf8 = NULL;
delete[] allowedUtf8;
allowedUtf8 = NULL;
delete[] charMapUtf8;
charMapUtf8 = NULL;
pos = -1;
offset = 0;
newchar = true;
}
}
void cMenuEditStrItem::SetHelpKeys(void)
{
if (InEditMode())
SetHelp(tr("Button$ABC/abc"), insert ? tr("Button$Overwrite") : tr("Button$Insert"), tr("Button$Delete"));
else
SetHelp(NULL);
}
uint *cMenuEditStrItem::IsAllowed(uint c)
{
if (allowedUtf8) {
for (uint *a = allowedUtf8; *a; a++) {
if (c == *a)
return a;
}
}
return NULL;
}
void cMenuEditStrItem::AdvancePos(void)
{
if (pos < length - 2 && pos < lengthUtf8) {
if (++pos >= lengthUtf8) {
if (pos >= 2 && valueUtf8[pos - 1] == ' ' && valueUtf8[pos - 2] == ' ')
pos--; // allow only two blanks at the end
else {
valueUtf8[pos] = ' ';
valueUtf8[pos + 1] = 0;
lengthUtf8++;
}
}
}
newchar = true;
if (!insert && Utf8is(alpha, valueUtf8[pos]))
uppercase = Utf8is(upper, valueUtf8[pos]);
}
void cMenuEditStrItem::Set(void)
{
if (InEditMode()) {
// This is an ugly hack to make editing strings work with the 'skincurses' plugin.
const cFont *font = dynamic_cast<cSkinDisplayMenu *>(cSkinDisplay::Current())->GetTextAreaFont(false);
if (!font || font->Width("W") != 1) // all characters have with == 1 in the font used by 'skincurses'
font = cFont::GetFont(fontOsd);
int width = cSkinDisplay::Current()->EditableWidth();
width -= font->Width("[]");
width -= font->Width("<>"); // reserving this anyway make the whole thing simpler
if (pos < offset)
offset = pos;
int WidthFromOffset = 0;
int EndPos = lengthUtf8;
for (int i = offset; i < lengthUtf8; i++) {
WidthFromOffset += font->Width(valueUtf8[i]);
if (WidthFromOffset > width) {
if (pos >= i) {
do {
WidthFromOffset -= font->Width(valueUtf8[offset]);
offset++;
} while (WidthFromOffset > width && offset < pos);
EndPos = pos + 1;
}
else {
EndPos = i;
break;
}
}
}
char buf[1000];
char *p = buf;
if (offset)
*p++ = '<';
p += Utf8FromArray(valueUtf8 + offset, p, sizeof(buf) - (p - buf), pos - offset);
*p++ = '[';
if (insert && newchar)
*p++ = ']';
p += Utf8FromArray(&valueUtf8[pos], p, sizeof(buf) - (p - buf), 1);
if (!(insert && newchar))
*p++ = ']';
p += Utf8FromArray(&valueUtf8[pos + 1], p, sizeof(buf) - (p - buf), EndPos - pos - 1);
if (EndPos != lengthUtf8)
*p++ = '>';
*p = 0;
SetValue(buf);
}
else
SetValue(value);
}
uint cMenuEditStrItem::Inc(uint c, bool Up)
{
uint *p = IsAllowed(c);
if (!p)
p = allowedUtf8;
if (Up) {
if (!*++p)
p = allowedUtf8;
}
else if (--p < allowedUtf8) {
p = allowedUtf8;
while (*p && *(p + 1))
p++;
}
return *p;
}
void cMenuEditStrItem::Type(uint c)
{
if (insert && lengthUtf8 < length - 1)
Insert();
valueUtf8[pos] = c;
if (pos < length - 2)
pos++;
if (pos >= lengthUtf8) {
valueUtf8[pos] = ' ';
valueUtf8[pos + 1] = 0;
lengthUtf8 = pos + 1;
}
}
void cMenuEditStrItem::Insert(void)
{
memmove(valueUtf8 + pos + 1, valueUtf8 + pos, (lengthUtf8 - pos + 1) * sizeof(*valueUtf8));
lengthUtf8++;
valueUtf8[pos] = ' ';
}
void cMenuEditStrItem::Delete(void)
{
memmove(valueUtf8 + pos, valueUtf8 + pos + 1, (lengthUtf8 - pos) * sizeof(*valueUtf8));
lengthUtf8--;
}
eOSState cMenuEditStrItem::ProcessKey(eKeys Key)
{
bool SameKey = NORMALKEY(Key) == lastKey;
if (Key != kNone)
lastKey = NORMALKEY(Key);
else if (!newchar && k0 <= lastKey && lastKey <= k9 && autoAdvanceTimeout.TimedOut()) {
AdvancePos();
newchar = true;
currentCharUtf8 = NULL;
Set();
return osContinue;
}
switch (int(Key)) {
case kRed: // Switch between upper- and lowercase characters
if (InEditMode()) {
if (!insert || !newchar) {
uppercase = !uppercase;
valueUtf8[pos] = uppercase ? Utf8to(upper, valueUtf8[pos]) : Utf8to(lower, valueUtf8[pos]);
}
}
else
return osUnknown;
break;
case kGreen: // Toggle insert/overwrite modes
if (InEditMode()) {
insert = !insert;
newchar = true;
SetHelpKeys();
}
else
return osUnknown;
break;
case kYellow|k_Repeat:
case kYellow: // Remove the character at the current position; in insert mode it is the character to the right of the cursor
if (InEditMode()) {
if (lengthUtf8 > 1) {
if (!insert || pos < lengthUtf8 - 1)
Delete();
else if (insert && pos == lengthUtf8 - 1)
valueUtf8[pos] = ' '; // in insert mode, deleting the last character replaces it with a blank to keep the cursor position
// reduce position, if we removed the last character
if (pos == lengthUtf8)
pos--;
}
else if (lengthUtf8 == 1)
valueUtf8[0] = ' '; // This is the last character in the string, replace it with a blank
if (Utf8is(alpha, valueUtf8[pos]))
uppercase = Utf8is(upper, valueUtf8[pos]);
newchar = true;
}
else
return osUnknown;
break;
case kBlue|k_Repeat:
case kBlue: // consume the key only if in edit-mode
if (!InEditMode())
return osUnknown;
break;
case kLeft|k_Repeat:
case kLeft: if (pos > 0) {
if (!insert || newchar)
pos--;
newchar = true;
if (!insert && Utf8is(alpha, valueUtf8[pos]))
uppercase = Utf8is(upper, valueUtf8[pos]);
}
break;
case kRight|k_Repeat:
case kRight: if (InEditMode())
AdvancePos();
else {
EnterEditMode();
SetHelpKeys();
}
break;
case kUp|k_Repeat:
case kUp:
case kDown|k_Repeat:
case kDown: if (InEditMode()) {
if (insert && newchar) {
// create a new character in insert mode
if (lengthUtf8 < length - 1)
Insert();
}
if (uppercase)
valueUtf8[pos] = Utf8to(upper, Inc(Utf8to(lower, valueUtf8[pos]), NORMALKEY(Key) == kUp));
else
valueUtf8[pos] = Inc( valueUtf8[pos], NORMALKEY(Key) == kUp);
newchar = false;
}
else
return cMenuEditItem::ProcessKey(Key);
break;
case k0|k_Repeat ... k9|k_Repeat:
case k0 ... k9: {
if (InEditMode()) {
if (Setup.NumberKeysForChars) {
if (!SameKey) {
if (!newchar)
AdvancePos();
currentCharUtf8 = NULL;
}
if (!currentCharUtf8 || !*currentCharUtf8 || *currentCharUtf8 == '\t') {
// find the beginning of the character map entry for Key
int n = NORMALKEY(Key) - k0;
currentCharUtf8 = charMapUtf8;
while (n > 0 && *currentCharUtf8) {
if (*currentCharUtf8++ == '\t')
n--;
}
// find first allowed character
while (*currentCharUtf8 && *currentCharUtf8 != '\t' && !IsAllowed(*currentCharUtf8))
currentCharUtf8++;
}
if (*currentCharUtf8 && *currentCharUtf8 != '\t') {
if (insert && newchar) {
// create a new character in insert mode
if (lengthUtf8 < length - 1)
Insert();
}
valueUtf8[pos] = *currentCharUtf8;
if (uppercase)
valueUtf8[pos] = Utf8to(upper, valueUtf8[pos]);
// find next allowed character
do {
currentCharUtf8++;
} while (*currentCharUtf8 && *currentCharUtf8 != '\t' && !IsAllowed(*currentCharUtf8));
newchar = false;
autoAdvanceTimeout.Set(AUTO_ADVANCE_TIMEOUT);
}
}
else
Type('0' + NORMALKEY(Key) - k0);
}
else
return cMenuEditItem::ProcessKey(Key);
}
break;
case kBack:
case kOk: if (InEditMode()) {
LeaveEditMode(Key == kOk);
SetHelpKeys();
break;
}
// run into default
default: if (InEditMode() && BASICKEY(Key) == kKbd) {
int c = KEYKBD(Key);
if (c <= 0xFF) { // FIXME what about other UTF-8 characters?
if (IsAllowed(Utf8to(lower, c)))
Type(c);
else {
switch (c) {
case 0x7F: // backspace
if (pos > 0) {
pos--;
return ProcessKey(kYellow);
}
break;
default: ;
}
}
}
else {
switch (c) {
case kfHome: pos = 0; break;
case kfEnd: pos = lengthUtf8 - 1; break;
case kfIns: return ProcessKey(kGreen);
case kfDel: return ProcessKey(kYellow);
default: ;
}
}
}
else
return cMenuEditItem::ProcessKey(Key);
}
Set();
return osContinue;
}
// --- cMenuEditStraItem -----------------------------------------------------
cMenuEditStraItem::cMenuEditStraItem(const char *Name, int *Value, int NumStrings, const char * const *Strings)
:cMenuEditIntItem(Name, Value, 0, NumStrings - 1)
{
strings = Strings;
Set();
}
void cMenuEditStraItem::Set(void)
{
SetValue(strings[*value]);
}
// --- cMenuEditChanItem -----------------------------------------------------
cMenuEditChanItem::cMenuEditChanItem(const char *Name, int *Value, const char *NoneString)
:cMenuEditIntItem(Name, Value, NoneString ? 0 : 1, Channels.MaxNumber())
{
channelID = NULL;
noneString = NoneString;
dummyValue = 0;
Set();
}
cMenuEditChanItem::cMenuEditChanItem(const char *Name, cString *ChannelID, const char *NoneString)
:cMenuEditIntItem(Name, &dummyValue, NoneString ? 0 : 1, Channels.MaxNumber())
{
channelID = ChannelID;
noneString = NoneString;
cChannel *channel = Channels.GetByChannelID(tChannelID::FromString(*ChannelID));
dummyValue = channel ? channel->Number() : 0;
Set();
}
void cMenuEditChanItem::Set(void)
{
if (*value > 0) {
char buf[255];
cChannel *channel = Channels.GetByNumber(*value);
snprintf(buf, sizeof(buf), "%d %s", *value, channel ? channel->Name() : "");
SetValue(buf);
if (channelID)
*channelID = channel->GetChannelID().ToString();
}
else if (noneString) {
SetValue(noneString);
if (channelID)
*channelID = "";
}
}
eOSState cMenuEditChanItem::ProcessKey(eKeys Key)
{
int delta = 1;
switch (int(Key)) {
case kLeft|k_Repeat:
case kLeft: delta = -1;
case kRight|k_Repeat:
case kRight:
{
cChannel *channel = Channels.GetByNumber(*value + delta, delta);
if (channel)
*value = channel->Number();
else if (delta < 0 && noneString)
*value = 0;
if (channelID)
*channelID = channel ? channel->GetChannelID().ToString() : "";
Set();
}
break;
default: return cMenuEditIntItem::ProcessKey(Key);
}
return osContinue;
}
// --- cMenuEditTranItem -----------------------------------------------------
cMenuEditTranItem::cMenuEditTranItem(const char *Name, int *Value, int *Source)
:cMenuEditChanItem(Name, &number, "-")
{
number = 0;
source = Source;
transponder = Value;
cChannel *channel = Channels.First();
while (channel) {
if (!channel->GroupSep() && *source == channel->Source() && ISTRANSPONDER(channel->Transponder(), *Value)) {
number = channel->Number();
break;
}
channel = (cChannel *)channel->Next();
}
Set();
}
eOSState cMenuEditTranItem::ProcessKey(eKeys Key)
{
eOSState state = cMenuEditChanItem::ProcessKey(Key);
cChannel *channel = Channels.GetByNumber(number);
if (channel) {
*source = channel->Source();
*transponder = channel->Transponder();
}
else {
*source = 0;
*transponder = 0;
}
return state;
}
// --- cMenuEditDateItem -----------------------------------------------------
static int ParseWeekDays(const char *s)
{
time_t day;
int weekdays;
return cTimer::ParseDay(s, day, weekdays) ? weekdays : 0;
}
int cMenuEditDateItem::days[] = { ParseWeekDays("M------"),
ParseWeekDays("-T-----"),
ParseWeekDays("--W----"),
ParseWeekDays("---T---"),
ParseWeekDays("----F--"),
ParseWeekDays("-----S-"),
ParseWeekDays("------S"),
ParseWeekDays("MTWTF--"),
ParseWeekDays("MTWTFS-"),
ParseWeekDays("MTWTFSS"),
ParseWeekDays("-----SS"),
0 };
cMenuEditDateItem::cMenuEditDateItem(const char *Name, time_t *Value, int *WeekDays)
:cMenuEditItem(Name)
{
value = Value;
weekdays = WeekDays;
oldvalue = 0;
dayindex = weekdays ? FindDayIndex(*weekdays) : 0;
Set();
}
int cMenuEditDateItem::FindDayIndex(int WeekDays)
{
for (unsigned int i = 0; i < sizeof(days) / sizeof(int); i++)
if (WeekDays == days[i])
return i;
return 0;
}
void cMenuEditDateItem::Set(void)
{
#define DATEBUFFERSIZE 32
char buf[DATEBUFFERSIZE];
if (weekdays && *weekdays) {
SetValue(cTimer::PrintDay(0, *weekdays, false));
return;
}
else if (*value) {
struct tm tm_r;
localtime_r(value, &tm_r);
strftime(buf, DATEBUFFERSIZE, "%Y-%m-%d ", &tm_r);
strcat(buf, WeekDayName(tm_r.tm_wday));
}
else
*buf = 0;
SetValue(buf);
}
void cMenuEditDateItem::ToggleRepeating(void)
{
if (weekdays) {
if (*weekdays) {
*value = cTimer::SetTime(oldvalue ? oldvalue : time(NULL), 0);
oldvalue = 0;
*weekdays = 0;
}
else {
*weekdays = days[cTimer::GetWDay(*value)];
dayindex = FindDayIndex(*weekdays);
oldvalue = *value;
*value = 0;
}
Set();
}
}
eOSState cMenuEditDateItem::ProcessKey(eKeys Key)
{
eOSState state = cMenuEditItem::ProcessKey(Key);
if (state == osUnknown) {
time_t now = time(NULL);
if (NORMALKEY(Key) == kLeft) { // TODO might want to increase the delta if repeated quickly?
if (!weekdays || !*weekdays) {
// Decrement single day:
time_t v = *value;
v -= SECSINDAY;
if (v < now) {
if (now <= v + SECSINDAY) { // switched from tomorrow to today
if (!weekdays)
v = 0;
}
else if (weekdays) { // switched from today to yesterday, so enter weekdays mode
v = 0;
dayindex = sizeof(days) / sizeof(int) - 2;
*weekdays = days[dayindex];
}
else // don't go before today
v = *value;
}
*value = v;
}
else {
// Decrement weekday index:
if (dayindex > 0)
*weekdays = days[--dayindex];
}
}
else if (NORMALKEY(Key) == kRight) {
if (!weekdays || !*weekdays) {
// Increment single day:
if (!*value)
*value = cTimer::SetTime(now, 0);
*value += SECSINDAY;
}
else {
// Increment weekday index:
*weekdays = days[++dayindex];
if (!*weekdays) { // was last weekday entry, so switch to today
*value = cTimer::SetTime(now, 0);
dayindex = 0;
}
}
}
else if (weekdays) {
if (Key == k0) {
// Toggle between weekdays and single day:
ToggleRepeating();
return osContinue; // ToggleRepeating) has already called Set()
}
else if (k1 <= Key && Key <= k7) {
// Toggle individual weekdays:
if (*weekdays) {
int v = *weekdays ^ (1 << (Key - k1));
if (v != 0)
*weekdays = v; // can't let this become all 0
}
}
else
return state;
}
else
return state;
Set();
state = osContinue;
}
return state;
}
// --- cMenuEditTimeItem -----------------------------------------------------
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];
switch (pos) {
case 1: snprintf(buf, sizeof(buf), "%01d-:--", hh / 10); break;
case 2: snprintf(buf, sizeof(buf), "%02d:--", hh); break;
case 3: snprintf(buf, sizeof(buf), "%02d:%01d-", hh, mm / 10); break;
default: 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;
default: ;
}
}
else if (NORMALKEY(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 (NORMALKEY(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;
}
// --- cMenuEditMapItem ------------------------------------------------------
cMenuEditMapItem::cMenuEditMapItem(const char *Name, int *Value, const tDvbParameterMap *Map, const char *ZeroString)
:cMenuEditItem(Name)
{
value = Value;
map = Map;
zeroString = ZeroString;
Set();
}
void cMenuEditMapItem::Set(void)
{
const char *s = NULL;
int n = MapToUser(*value, map, &s);
if (n == 0 && zeroString)
SetValue(zeroString);
else if (n >= 0) {
if (s)
SetValue(s);
else {
char buf[16];
snprintf(buf, sizeof(buf), "%d", n);
SetValue(buf);
}
}
else
SetValue("???");
}
eOSState cMenuEditMapItem::ProcessKey(eKeys Key)
{
eOSState state = cMenuEditItem::ProcessKey(Key);
if (state == osUnknown) {
int newValue = *value;
int n = DriverIndex(*value, map);
if (NORMALKEY(Key) == kLeft) { // TODO might want to increase the delta if repeated quickly?
if (n-- > 0)
newValue = map[n].driverValue;
}
else if (NORMALKEY(Key) == kRight) {
if (map[++n].userValue >= 0)
newValue = map[n].driverValue;
}
else
return state;
if (newValue != *value) {
*value = newValue;
Set();
}
state = osContinue;
}
return state;
}
// --- cMenuSetupPage --------------------------------------------------------
cMenuSetupPage::cMenuSetupPage(void)
:cOsdMenu("", 33)
{
plugin = NULL;
}
void cMenuSetupPage::SetSection(const char *Section)
{
SetTitle(cString::sprintf("%s - %s", tr("Setup"), Section));
}
eOSState cMenuSetupPage::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kOk: Store();
state = osBack;
break;
default: break;
}
}
return state;
}
void cMenuSetupPage::SetPlugin(cPlugin *Plugin)
{
plugin = Plugin;
SetSection(cString::sprintf("%s '%s'", tr("Plugin"), plugin->Name()));
}
void cMenuSetupPage::SetupStore(const char *Name, const char *Value)
{
if (plugin)
plugin->SetupStore(Name, Value);
}
void cMenuSetupPage::SetupStore(const char *Name, int Value)
{
if (plugin)
plugin->SetupStore(Name, Value);
}