vdr/menu.c
Klaus Schmidinger da948a50d2 Version 1.3.38
- Fixed handling second audio and Dolby Digital PIDs for encrypted channels
  (was broken in version 1.3.37).
- Improved TS/PES conversion to better handle lost TS packets (thanks to
  Reinhard Nissl).
- Limited the frequency of log messages from the cRepackers.
- Now using the gettid() syscall to get a thread's pid, so that we get a
  useful value on NPTL systems (suggested by Johannes Stezenbach).
- Fixed the RCU remote control handling to avoid problems with NPTL (thanks
  to Andreas Share for reporting a lockup with the RCU on NPTL systems).
- When displaying the amount of free disk space, the space consumed by
  recordings that have been "deleted" but not yet actually "removed" is now
  taken into account (suggested by Christian Vogt).
- Now avoiding unnecessary disk access when checking if there are deleted
  recordings that need to be removed (reported by Carsten Koch).
- Fixed handling the DELETEDLIFETIME when removing deleted recordings. Now
  a deleted recording is retained at least DELETEDLIFETIME seconds before
  actually removing it.
  The value of DELETEDLIFETIME has been changed to 300. So after (possibly
  inadvertently) deleting a recording, there will be at least 5 minutes
  in which it can be recovered (unless a new recording immediately requires
  the disk space). The count starts again at 0 every time VDR is started.
- Fixed a possible crash when displaying the "Low disk space!" message from
  a background thread (thanks to Christof Steininger).
- Fixed handling OSD areas that have invalid sizes (thanks to Marco Schlüßler).
- Added a mutex to AssertFreeDiskSpace() to make sure calls from foreground
  and background threads won't interfere.
- The main menu now dynamically updates its contents in case an instant
  recording or replay stops, etc.
- The version number of EPG events is now also stored in the epg.data file
  (thanks to Kendy Kutzner).
- EPG events that are no longer in the currently broadcasted data stream are
  now automatically deleted.
- Removed an invalid access to Event->schedule in cSchedule::DelEvent().
- Modified cSchedule::Cleanup() (events are always sorted by time).
- Schedules are now cleaned up once every hour (not only at 05:00).
- The "Schedule" and "What's on now/next?" menus are now updated if a timer
  is set or modified.
- cTimer no longer has its own 'schedule' member, it rather uses that of the
  event it has been set to.
- The "Red" button in the "Schedule", "What's on now/next?" and "Event" menus
  now immediately creates a timer for the selected event and marks it with 'T'.
  If the event is already marked with 'T', the "Red" button opens the "Edit
  timer" menu for that timer.
- Removing deleted recordings is now done in a separate thread.
- Dropped the unused "stop recording on primary interface" stuff.
- Converting a grabbed image to JPEG is now done with the new function
  RgbToJpeg() (see tools.h).
- The SVDRP command GRAB now determines the image type (JPEG or PNM) from the
  extension (".jpg", ".jpeg" or ".pnm") of the given file name. The explicit
  'jpeg' or 'pnm' parameter is still accepted for backward compatibility, but
  has no meaning any more.
- The function cDevice::GrabImage() no longer writes the grabbed image to a
  file, but rather returns a pointer to the image in memory. The wrapper
  function cDevice::GrabImageFile() can be used to write the grabbed image
  directly to a file. Plugins that used the old version of cDevice::GrabImage()
  need to be adapted to the new interface.
- The new class cBase64Encoder (see tools.h) can be used to encode data in
  base64 (thanks to Bob Withers for publishing his Base64 class).
- The SVDRP command GRAB now writes the image data to the SVDRP connection
  (encoded in base64) if the given file name consists of only the file
  extension (".jpg", ".jpeg" or ".pnm"), or if only "-" is given as file
  name (based on a suggestion from Darren Salt).
  A simple way of viewing a grabbed image on a remote host is:

  svdrpsend.pl -d <hostname> 'grab -' | sed -n -e 's/^216-//p' -e '1ibegin-base64 644 -' -e '$a====' | uudecode | display -

- The new command line option '-g' must be given if the SVDRP command GRAB
  shall be allowed to write image files to disk. The parameter to this option
  must be the full path name of an existing directory, without any "..", double
  '/' or symlinks. By default, or if "-g- is given, grabbing to files is
  not allowed any more because of potential security risks.
- Modified the way the SVDRP command GRAB writes the grabbed image to a file
  to avoid a security hole (CAN-2005-0071, reported by Javier Fernández-Sanguino
  Peña):
  + The file handle is now opened in a way that it won't follow symbolic links
    (suggested by Darren Salt).
  + The given file name is now canonicalized, so that it won't contain any
    ".." or symlinks (suggested by Darren Salt).
  + Grabbing to files is limited to the directory given in the the command
    line option '-g'. By default grabbing to files is not allowed any more.
- Updated the Greek OSD texts (thanks to Dimitrios Dimitrakos).
- Changed all "illegal" to "invalid" in error messages (there's nothing "illegal"
  in VDR ;-).
- When started as user 'root' VDR now switches to a lesser privileged user id,
  keeping the capability to set the system time (based on a patch from Ludwig
  Nussel). By default the user id 'vdr' is used, which can be changed through
  the new command line option '-u'. Note that for security reasons VDR will no
  longer run as user 'root' (unless you explicitly start it with '-u root',
  but this is not recommended!). The 'runvdr' script has been changed to
  use the '-u' option.
- Changed the API of the functions cStatus::Recording() and cStatus::Replaying(),
  so that they can provide the full file name of the recording. Plugins that use
  these (or the related cStatus::Msg...() functions) need to be adapted
  (suggested by Andreas Brugger).
- The DVB devices now retune (and, if applicable, resend the DiSEqC data) if
  the lock is lost (based on a patch from Reinhard Nissl).
- Fixed handling multi byte key sequences in cKbdRemote (based on a patch from
  Jürgen Schneider).
- Removed unused variables in skinclassic.c and skinsttng.c (thanks to Marco
  Schlüßler).
- Made the static cControl functions thread safe (thanks to Patrick Fischer).
- Fixed initializing pthread_mutexattr_t and pthread_rwlockattr_t to avoid
  warnings with g++ 4.1.0 (thanks to Ville Skyttä).
- Fixed incrementing the 'state' variables in the repacker classes in remux.c
  to avoid warnings with g++ 4.1.0 (reported by Ville Skyttä).
- The Makefile now reports a summary of failed plugins (thanks to Udo Richter).
- The cTimer constructor can now take an optional cChannel (suggested by
  Patrick Fischer).
- Fixed setting the main thread id if VDR is running as a daemon.
- Fixed handling TS packets in cTS2PES (thanks to Reinhard Nissl).
- Added cTimer::SetPriority() to set a timer's priority (suggested by Kendy Kutzner).
- Added cMenuEditStrItem::InEditMode() (suggested by Christian Wieninger).
- Now using FE_READ_STATUS to read the current frontend status (suggested by
  Holger Wächtler).
- The "Menu" key now behaves consistently. If there is anything on the OSD, it
  is closed when the "Menu" key is pressed, and if there is nothing on the OSD,
  the "Menu" key opens the main menu (suggested by Luca Olivetti).
- The new option "Setup/OSD/Timeout requested channel info" can be used to turn
  off the automatic timeout of the channel display in case it was invoked by
  a press of the "Ok" key (suggested by Thiemo Gehrke).
- A message is now given when an instant recording is started (suggested by
  Helmut Auer). Actually the code was already there, just commented out - don't
  remember why it wasn't active...
- Removed an obsolete "Summary" text from i18n.c and preceded all key definition
  texts with "Key$" to avoid duplicates (reported by Lucian Muresan).
- Preceded all button texts with "Button$".
- Removed obsolete "Eject", "Language" and "scanning recordings..." texts.
- Added missing #include "thread.h" to dvbspu.c (reported by Gavin Hamill).
- Disabled the use of "fadvise" in cUnbufferedFile because there have been
  several reports that it causes more problems than it solves (suggested by
  Petri Hintukainen). If you want to use "fadvise", you can activate the line
  //#define USE_FADVISE
  in tools.c.
- Removed unused 'offset' member from cOsdItem.
- In the "Channels" menu the numeric keys now position the cursor to the channel
  with the given number (see MANUAL, section "Remote Control Keys", note (3) for
  details).
- The "Mark/Move" function in the "Channels" menu now also works in the non-numeric
  sort modes.
- The default cOsdObject::Show() now automatically calls cOsdMenu::Display() if
  this is a menu.
- The new "Info" key brings up information on the currently viewed programme
  or recording. For a live programme this is the same as "Schedule/Ok", i.e. the
  description of the current EPG event. For a recording this is the same as shown
  by the "Info" button in the "Recordings" menu. Plugins that implement players
  can overwrite their cControl::GetInfo() function to show their own info (see
  PLUGINS.html for details). Pressing the "Info" key again while the info is
  displayed will close the OSD. In order to assign this new key to an existing
  remote control setup, the remote.conf file needs to be deleted and VDR has
  to be restarted to go through the process of learning the remote control keys.
- Any cReceivers still attached to a cDevice when that device switches to a
  different transponder are now automatically detached (suggested by Patrick
  Fischer).
- The "flags" of a timer are now handled as an unsigned integer value. In order
  to do this, the interface of cMenuEditBitItem also had to be changed.
- In string entry fields (like, e.g., the file name of a recording) the characters
  can now be entered by pressing the numeric keys, the same way as on a
  telephone keypad (based on the "Easy Input" patch from Marcel Schaeben).
- Fixed the "Day" field of the "Edit timer" menu when pressing '0' to switch
  from "single shot" to "weekly", followed by the "Right" key (reported by
  Andreas Böttger).
- The file 'ca.conf' is obsolete and has been removed.
- Revised all descriptions regarding CICAM.
- Adapted c(Dvb)Device::ProvidesCa() to the dynamic CA handling.
- Added a mutex to synchronize cDevice::PlayPesPacket() and SetCurrentAudioTrack()
  (thanks to Reinhard Nissl).
- Added a SleepMs() in cRecorder::Action() to avoid a busy loop (thanks to Ingo
  Schneider).
- Cleaned up some trailing white space.
2006-01-08 18:00:00 +01:00

3959 lines
117 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.390 2006/01/08 11:39:57 kls Exp $
*/
#include "menu.h"
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "channels.h"
#include "config.h"
#include "cutter.h"
#include "eitscan.h"
#include "i18n.h"
#include "interface.h"
#include "plugin.h"
#include "recording.h"
#include "remote.h"
#include "sources.h"
#include "status.h"
#include "themes.h"
#include "timers.h"
#include "transfer.h"
#include "videodir.h"
#define MAXWAIT4EPGINFO 3 // seconds
#define MODETIMEOUT 3 // seconds
#define DISKSPACECHEK 5 // seconds between disk space checks in the main menu
#define MAXRECORDCONTROLS (MAXDEVICES * MAXRECEIVERS)
#define MAXINSTANTRECTIME (24 * 60 - 1) // 23:59 hours
#define MAXWAITFORCAMMENU 4 // seconds to wait for the CAM menu to open
#define CHNUMWIDTH (numdigits(Channels.MaxNumber()) + 1)
// --- cMenuEditCaItem -------------------------------------------------------
class cMenuEditCaItem : public cMenuEditIntItem {
protected:
virtual void Set(void);
public:
cMenuEditCaItem(const char *Name, int *Value);
eOSState ProcessKey(eKeys Key);
};
cMenuEditCaItem::cMenuEditCaItem(const char *Name, int *Value)
:cMenuEditIntItem(Name, Value, 0)
{
Set();
}
void cMenuEditCaItem::Set(void)
{
if (*value == CA_FTA)
SetValue(tr("Free To Air"));
else if (*value >= CA_ENCRYPTED_MIN)
SetValue(tr("encrypted"));
else
cMenuEditIntItem::Set();
}
eOSState cMenuEditCaItem::ProcessKey(eKeys Key)
{
eOSState state = cMenuEditItem::ProcessKey(Key);
if (state == osUnknown) {
if (NORMALKEY(Key) == kLeft && *value >= CA_ENCRYPTED_MIN)
*value = CA_FTA;
else
return cMenuEditIntItem::ProcessKey(Key);
Set();
state = osContinue;
}
return state;
}
// --- cMenuEditSrcItem ------------------------------------------------------
class cMenuEditSrcItem : public cMenuEditIntItem {
private:
const cSource *source;
protected:
virtual void Set(void);
public:
cMenuEditSrcItem(const char *Name, int *Value);
eOSState ProcessKey(eKeys Key);
};
cMenuEditSrcItem::cMenuEditSrcItem(const char *Name, int *Value)
:cMenuEditIntItem(Name, Value, 0)
{
source = Sources.Get(*Value);
Set();
}
void cMenuEditSrcItem::Set(void)
{
if (source) {
char *buffer = NULL;
asprintf(&buffer, "%s - %s", *cSource::ToString(source->Code()), source->Description());
SetValue(buffer);
free(buffer);
}
else
cMenuEditIntItem::Set();
}
eOSState cMenuEditSrcItem::ProcessKey(eKeys Key)
{
eOSState state = cMenuEditItem::ProcessKey(Key);
if (state == osUnknown) {
if (NORMALKEY(Key) == kLeft) { // TODO might want to increase the delta if repeated quickly?
if (source && source->Prev()) {
source = (cSource *)source->Prev();
*value = source->Code();
}
}
else if (NORMALKEY(Key) == kRight) {
if (source) {
if (source->Next())
source = (cSource *)source->Next();
}
else
source = Sources.First();
if (source)
*value = source->Code();
}
else
return state; // we don't call cMenuEditIntItem::ProcessKey(Key) here since we don't accept numerical input
Set();
state = osContinue;
}
return state;
}
// --- cMenuEditMapItem ------------------------------------------------------
class cMenuEditMapItem : public cMenuEditItem {
protected:
int *value;
const tChannelParameterMap *map;
const char *zeroString;
virtual void Set(void);
public:
cMenuEditMapItem(const char *Name, int *Value, const tChannelParameterMap *Map, const char *ZeroString = NULL);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuEditMapItem::cMenuEditMapItem(const char *Name, int *Value, const tChannelParameterMap *Map, const char *ZeroString)
:cMenuEditItem(Name)
{
value = Value;
map = Map;
zeroString = ZeroString;
Set();
}
void cMenuEditMapItem::Set(void)
{
int n = MapToUser(*value, map);
if (n == 999)
SetValue(tr("auto"));
else if (n == 0 && zeroString)
SetValue(zeroString);
else if (n >= 0) {
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;
}
// --- cMenuEditChannel ------------------------------------------------------
class cMenuEditChannel : public cOsdMenu {
private:
cChannel *channel;
cChannel data;
char name[256];
void Setup(void);
public:
cMenuEditChannel(cChannel *Channel, bool New = false);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuEditChannel::cMenuEditChannel(cChannel *Channel, bool New)
:cOsdMenu(tr("Edit channel"), 16)
{
channel = Channel;
if (channel) {
data = *channel;
if (New) {
channel = NULL;
data.nid = 0;
data.tid = 0;
data.rid = 0;
}
Setup();
}
}
void cMenuEditChannel::Setup(void)
{
int current = Current();
char type = **cSource::ToString(data.source);
#define ST(s) if (strchr(s, type))
Clear();
// Parameters for all types of sources:
strn0cpy(name, data.name, sizeof(name));
Add(new cMenuEditStrItem( tr("Name"), name, sizeof(name), tr(FileNameChars)));
Add(new cMenuEditSrcItem( tr("Source"), &data.source));
Add(new cMenuEditIntItem( tr("Frequency"), &data.frequency));
Add(new cMenuEditIntItem( tr("Vpid"), &data.vpid, 0, 0x1FFF));
Add(new cMenuEditIntItem( tr("Ppid"), &data.ppid, 0, 0x1FFF));
Add(new cMenuEditIntItem( tr("Apid1"), &data.apids[0], 0, 0x1FFF));
Add(new cMenuEditIntItem( tr("Apid2"), &data.apids[1], 0, 0x1FFF));
Add(new cMenuEditIntItem( tr("Dpid1"), &data.dpids[0], 0, 0x1FFF));
Add(new cMenuEditIntItem( tr("Dpid2"), &data.dpids[1], 0, 0x1FFF));
Add(new cMenuEditIntItem( tr("Tpid"), &data.tpid, 0, 0x1FFF));
Add(new cMenuEditCaItem( tr("CA"), &data.caids[0]));
Add(new cMenuEditIntItem( tr("Sid"), &data.sid, 1, 0xFFFF));
/* XXX not yet used
Add(new cMenuEditIntItem( tr("Nid"), &data.nid, 0));
Add(new cMenuEditIntItem( tr("Tid"), &data.tid, 0));
Add(new cMenuEditIntItem( tr("Rid"), &data.rid, 0));
XXX*/
// Parameters for specific types of sources:
ST(" S ") Add(new cMenuEditChrItem( tr("Polarization"), &data.polarization, "hvlr"));
ST("CS ") Add(new cMenuEditIntItem( tr("Srate"), &data.srate));
ST("CST") Add(new cMenuEditMapItem( tr("Inversion"), &data.inversion, InversionValues, tr("off")));
ST("CST") Add(new cMenuEditMapItem( tr("CoderateH"), &data.coderateH, CoderateValues, tr("none")));
ST(" T") Add(new cMenuEditMapItem( tr("CoderateL"), &data.coderateL, CoderateValues, tr("none")));
ST("C T") Add(new cMenuEditMapItem( tr("Modulation"), &data.modulation, ModulationValues, "QPSK"));
ST(" T") Add(new cMenuEditMapItem( tr("Bandwidth"), &data.bandwidth, BandwidthValues));
ST(" T") Add(new cMenuEditMapItem( tr("Transmission"), &data.transmission, TransmissionValues));
ST(" T") Add(new cMenuEditMapItem( tr("Guard"), &data.guard, GuardValues));
ST(" T") Add(new cMenuEditMapItem( tr("Hierarchy"), &data.hierarchy, HierarchyValues, tr("none")));
SetCurrent(Get(current));
Display();
}
eOSState cMenuEditChannel::ProcessKey(eKeys Key)
{
int oldSource = data.source;
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
if (Key == kOk) {
if (Channels.HasUniqueChannelID(&data, channel)) {
data.name = strcpyrealloc(data.name, name);
if (channel) {
*channel = data;
isyslog("edited channel %d %s", channel->Number(), *data.ToText());
state = osBack;
}
else {
channel = new cChannel;
*channel = data;
Channels.Add(channel);
Channels.ReNumber();
isyslog("added channel %d %s", channel->Number(), *data.ToText());
state = osUser1;
}
Channels.SetModified(true);
}
else {
Skins.Message(mtError, tr("Channel settings are not unique!"));
state = osContinue;
}
}
}
if (Key != kNone && (data.source & cSource::st_Mask) != (oldSource & cSource::st_Mask))
Setup();
return state;
}
// --- cMenuChannelItem ------------------------------------------------------
class cMenuChannelItem : public cOsdItem {
public:
enum eChannelSortMode { csmNumber, csmName, csmProvider };
private:
static eChannelSortMode sortMode;
cChannel *channel;
public:
cMenuChannelItem(cChannel *Channel);
static void SetSortMode(eChannelSortMode SortMode) { sortMode = SortMode; }
static void IncSortMode(void) { sortMode = eChannelSortMode((sortMode == csmProvider) ? csmNumber : sortMode + 1); }
virtual int Compare(const cListObject &ListObject) const;
virtual void Set(void);
cChannel *Channel(void) { return channel; }
static eChannelSortMode SortMode(void) { return sortMode; }
};
cMenuChannelItem::eChannelSortMode cMenuChannelItem::sortMode = csmNumber;
cMenuChannelItem::cMenuChannelItem(cChannel *Channel)
{
channel = Channel;
if (channel->GroupSep())
SetSelectable(false);
Set();
}
int cMenuChannelItem::Compare(const cListObject &ListObject) const
{
cMenuChannelItem *p = (cMenuChannelItem *)&ListObject;
int r = -1;
if (sortMode == csmProvider)
r = strcoll(channel->Provider(), p->channel->Provider());
if (sortMode == csmName || r == 0)
r = strcoll(channel->Name(), p->channel->Name());
if (sortMode == csmNumber || r == 0)
r = channel->Number() - p->channel->Number();
return r;
}
void cMenuChannelItem::Set(void)
{
char *buffer = NULL;
if (!channel->GroupSep()) {
if (sortMode == csmProvider)
asprintf(&buffer, "%d\t%s - %s", channel->Number(), channel->Provider(), channel->Name());
else
asprintf(&buffer, "%d\t%s", channel->Number(), channel->Name());
}
else
asprintf(&buffer, "---\t%s ----------------------------------------------------------------", channel->Name());
SetText(buffer, false);
}
// --- cMenuChannels ---------------------------------------------------------
#define CHANNELNUMBERTIMEOUT 1000 //ms
class cMenuChannels : public cOsdMenu {
private:
int number;
cTimeMs numberTimer;
void Setup(void);
cChannel *GetChannel(int Index);
void Propagate(void);
protected:
eOSState Number(eKeys Key);
eOSState Switch(void);
eOSState Edit(void);
eOSState New(void);
eOSState Delete(void);
virtual void Move(int From, int To);
public:
cMenuChannels(void);
~cMenuChannels();
virtual eOSState ProcessKey(eKeys Key);
};
cMenuChannels::cMenuChannels(void)
:cOsdMenu(tr("Channels"), CHNUMWIDTH)
{
number = 0;
Setup();
Channels.IncBeingEdited();
}
cMenuChannels::~cMenuChannels()
{
Channels.DecBeingEdited();
}
void cMenuChannels::Setup(void)
{
cChannel *currentChannel = GetChannel(Current());
if (!currentChannel)
currentChannel = Channels.GetByNumber(cDevice::CurrentChannel());
cMenuChannelItem *currentItem = NULL;
Clear();
for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) {
if (!channel->GroupSep() || cMenuChannelItem::SortMode() == cMenuChannelItem::csmNumber && *channel->Name()) {
cMenuChannelItem *item = new cMenuChannelItem(channel);
Add(item);
if (channel == currentChannel)
currentItem = item;
}
}
if (cMenuChannelItem::SortMode() != cMenuChannelItem::csmNumber)
Sort();
SetCurrent(currentItem);
SetHelp(tr("Button$Edit"), tr("Button$New"), tr("Button$Delete"), tr("Button$Mark"));
Display();
}
cChannel *cMenuChannels::GetChannel(int Index)
{
cMenuChannelItem *p = (cMenuChannelItem *)Get(Index);
return p ? (cChannel *)p->Channel() : NULL;
}
void cMenuChannels::Propagate(void)
{
Channels.ReNumber();
for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next())
ci->Set();
Display();
Channels.SetModified(true);
}
eOSState cMenuChannels::Number(eKeys Key)
{
if (HasSubMenu())
return osContinue;
if (numberTimer.TimedOut())
number = 0;
if (!number && Key == k0) {
cMenuChannelItem::IncSortMode();
Setup();
}
else {
number = number * 10 + Key - k0;
for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) {
if (!ci->Channel()->GroupSep() && ci->Channel()->Number() == number) {
SetCurrent(ci);
Display();
break;
}
}
numberTimer.Set(CHANNELNUMBERTIMEOUT);
}
return osContinue;
}
eOSState cMenuChannels::Switch(void)
{
if (HasSubMenu())
return osContinue;
cChannel *ch = GetChannel(Current());
if (ch)
return cDevice::PrimaryDevice()->SwitchChannel(ch, true) ? osEnd : osContinue;
return osEnd;
}
eOSState cMenuChannels::Edit(void)
{
if (HasSubMenu() || Count() == 0)
return osContinue;
cChannel *ch = GetChannel(Current());
if (ch)
return AddSubMenu(new cMenuEditChannel(ch));
return osContinue;
}
eOSState cMenuChannels::New(void)
{
if (HasSubMenu())
return osContinue;
return AddSubMenu(new cMenuEditChannel(GetChannel(Current()), true));
}
eOSState cMenuChannels::Delete(void)
{
if (!HasSubMenu() && Count() > 0) {
int Index = Current();
cChannel *channel = GetChannel(Current());
int DeletedChannel = channel->Number();
// Check if there is a timer using this channel:
for (cTimer *ti = Timers.First(); ti; ti = Timers.Next(ti)) {
if (ti->Channel() == channel) {
Skins.Message(mtError, tr("Channel is being used by a timer!"));
return osContinue;
}
}
if (Interface->Confirm(tr("Delete channel?"))) {
Channels.Del(channel);
cOsdMenu::Del(Index);
Propagate();
isyslog("channel %d deleted", DeletedChannel);
}
}
return osContinue;
}
void cMenuChannels::Move(int From, int To)
{
int CurrentChannelNr = cDevice::CurrentChannel();
cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
cChannel *FromChannel = GetChannel(From);
cChannel *ToChannel = GetChannel(To);
if (FromChannel && ToChannel) {
int FromNumber = FromChannel->Number();
int ToNumber = ToChannel->Number();
Channels.Move(FromChannel, ToChannel);
cOsdMenu::Move(From, To);
Propagate();
isyslog("channel %d moved to %d", FromNumber, ToNumber);
if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr)
Channels.SwitchTo(CurrentChannel->Number());
}
}
eOSState cMenuChannels::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
switch (state) {
case osUser1: {
cChannel *channel = Channels.Last();
if (channel) {
Add(new cMenuChannelItem(channel), true);
return CloseSubMenu();
}
}
break;
default:
if (state == osUnknown) {
switch (Key) {
case k0 ... k9:
return Number(Key);
case kOk: return Switch();
case kRed: return Edit();
case kGreen: return New();
case kYellow: return Delete();
case kBlue: if (!HasSubMenu())
Mark();
break;
default: break;
}
}
}
return state;
}
// --- cMenuText -------------------------------------------------------------
cMenuText::cMenuText(const char *Title, const char *Text, eDvbFont Font)
:cOsdMenu(Title)
{
text = NULL;
SetText(Text);
}
cMenuText::~cMenuText()
{
free(text);
}
void cMenuText::SetText(const char *Text)
{
free(text);
text = Text ? strdup(Text) : NULL;
}
void cMenuText::Display(void)
{
cOsdMenu::Display();
DisplayMenu()->SetText(text, true);//XXX define control character in text to choose the font???
cStatus::MsgOsdTextItem(text);
}
eOSState cMenuText::ProcessKey(eKeys Key)
{
switch (Key) {
case kUp|k_Repeat:
case kUp:
case kDown|k_Repeat:
case kDown:
case kLeft|k_Repeat:
case kLeft:
case kRight|k_Repeat:
case kRight:
DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight);
cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp);
return osContinue;
default: break;
}
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kOk: return osBack;
default: state = osContinue;
}
}
return state;
}
// --- cMenuEditTimer --------------------------------------------------------
cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New)
:cOsdMenu(tr("Edit timer"), 12)
{
firstday = NULL;
timer = Timer;
addIfConfirmed = New;
if (timer) {
data = *timer;
if (New)
data.SetFlags(tfActive);
channel = data.Channel()->Number();
Add(new cMenuEditBitItem( tr("Active"), &data.flags, tfActive));
Add(new cMenuEditChanItem(tr("Channel"), &channel));
Add(new cMenuEditDateItem(tr("Day"), &data.day, &data.weekdays));
Add(new cMenuEditTimeItem(tr("Start"), &data.start));
Add(new cMenuEditTimeItem(tr("Stop"), &data.stop));
Add(new cMenuEditBitItem( tr("VPS"), &data.flags, tfVps));
Add(new cMenuEditIntItem( tr("Priority"), &data.priority, 0, MAXPRIORITY));
Add(new cMenuEditIntItem( tr("Lifetime"), &data.lifetime, 0, MAXLIFETIME));
Add(new cMenuEditStrItem( tr("File"), data.file, sizeof(data.file), tr(FileNameChars)));
SetFirstDayItem();
}
Timers.IncBeingEdited();
}
cMenuEditTimer::~cMenuEditTimer()
{
if (timer && addIfConfirmed)
delete timer; // apparently it wasn't confirmed
Timers.DecBeingEdited();
}
void cMenuEditTimer::SetFirstDayItem(void)
{
if (!firstday && !data.IsSingleEvent()) {
Add(firstday = new cMenuEditDateItem(tr("First day"), &data.day));
Display();
}
else if (firstday && data.IsSingleEvent()) {
Del(firstday->Index());
firstday = NULL;
Display();
}
}
eOSState cMenuEditTimer::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kOk: {
cChannel *ch = Channels.GetByNumber(channel);
if (ch)
data.channel = ch;
else {
Skins.Message(mtError, tr("*** Invalid Channel ***"));
break;
}
if (!*data.file)
strcpy(data.file, data.Channel()->ShortName(true));
if (timer) {
if (memcmp(timer, &data, sizeof(data)) != 0) {
*timer = data;
if (timer->HasFlags(tfActive))
timer->ClrFlags(~tfAll); // allows external programs to mark active timers with values > 0xFFFF and recognize if the user has modified them
}
if (addIfConfirmed)
Timers.Add(timer);
timer->Matches();
Timers.SetModified();
isyslog("timer %s %s (%s)", *timer->ToDescr(), addIfConfirmed ? "added" : "modified", timer->HasFlags(tfActive) ? "active" : "inactive");
addIfConfirmed = false;
}
}
return osBack;
case kRed:
case kGreen:
case kYellow:
case kBlue: return osContinue;
default: break;
}
}
if (Key != kNone)
SetFirstDayItem();
return state;
}
// --- cMenuTimerItem --------------------------------------------------------
class cMenuTimerItem : public cOsdItem {
private:
cTimer *timer;
public:
cMenuTimerItem(cTimer *Timer);
virtual int Compare(const cListObject &ListObject) const;
virtual void Set(void);
cTimer *Timer(void) { return timer; }
};
cMenuTimerItem::cMenuTimerItem(cTimer *Timer)
{
timer = Timer;
Set();
}
int cMenuTimerItem::Compare(const cListObject &ListObject) const
{
return timer->Compare(*((cMenuTimerItem *)&ListObject)->timer);
}
void cMenuTimerItem::Set(void)
{
cString day, name("");
if (timer->WeekDays())
day = timer->PrintDay(0, timer->WeekDays());
else if (timer->Day() - time(NULL) < 28 * SECSINDAY) {
day = itoa(timer->GetMDay(timer->Day()));
name = WeekDayName(timer->Day());
}
else {
struct tm tm_r;
time_t Day = timer->Day();
localtime_r(&Day, &tm_r);
char buffer[16];
strftime(buffer, sizeof(buffer), "%Y%m%d", &tm_r);
day = buffer;
}
char *buffer = NULL;
asprintf(&buffer, "%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s",
!(timer->HasFlags(tfActive)) ? ' ' : timer->FirstDay() ? '!' : timer->Recording() ? '#' : '>',
timer->Channel()->Number(),
*name,
*name && **name ? " " : "",
*day,
timer->Start() / 100,
timer->Start() % 100,
timer->Stop() / 100,
timer->Stop() % 100,
timer->File());
SetText(buffer, false);
}
// --- cMenuTimers -----------------------------------------------------------
class cMenuTimers : public cOsdMenu {
private:
eOSState Edit(void);
eOSState New(void);
eOSState Delete(void);
eOSState OnOff(void);
virtual void Move(int From, int To);
eOSState Summary(void);
cTimer *CurrentTimer(void);
public:
cMenuTimers(void);
virtual ~cMenuTimers();
virtual eOSState ProcessKey(eKeys Key);
};
cMenuTimers::cMenuTimers(void)
:cOsdMenu(tr("Timers"), 2, CHNUMWIDTH, 10, 6, 6)
{
for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer))
Add(new cMenuTimerItem(timer));
if (Setup.SortTimers)
Sort();
SetHelp(tr("Button$Edit"), tr("Button$New"), tr("Button$Delete"), Setup.SortTimers ? tr("Button$On/Off") : tr("Button$Mark"));
Timers.IncBeingEdited();
}
cMenuTimers::~cMenuTimers()
{
Timers.DecBeingEdited();
}
cTimer *cMenuTimers::CurrentTimer(void)
{
cMenuTimerItem *item = (cMenuTimerItem *)Get(Current());
return item ? item->Timer() : NULL;
}
eOSState cMenuTimers::OnOff(void)
{
cTimer *timer = CurrentTimer();
if (timer) {
timer->OnOff();
RefreshCurrent();
DisplayCurrent(true);
if (timer->FirstDay())
isyslog("timer %s first day set to %s", *timer->ToDescr(), *timer->PrintFirstDay());
else
isyslog("timer %s %sactivated", *timer->ToDescr(), timer->HasFlags(tfActive) ? "" : "de");
Timers.SetModified();
}
return osContinue;
}
eOSState cMenuTimers::Edit(void)
{
if (HasSubMenu() || Count() == 0)
return osContinue;
isyslog("editing timer %s", *CurrentTimer()->ToDescr());
return AddSubMenu(new cMenuEditTimer(CurrentTimer()));
}
eOSState cMenuTimers::New(void)
{
if (HasSubMenu())
return osContinue;
return AddSubMenu(new cMenuEditTimer(new cTimer, true));
}
eOSState cMenuTimers::Delete(void)
{
// Check if this timer is active:
cTimer *ti = CurrentTimer();
if (ti) {
if (Interface->Confirm(tr("Delete timer?"))) {
if (ti->Recording()) {
if (Interface->Confirm(tr("Timer still recording - really delete?"))) {
ti->Skip();
cRecordControls::Process(time(NULL));
}
else
return osContinue;
}
isyslog("deleting timer %s", *ti->ToDescr());
Timers.Del(ti);
cOsdMenu::Del(Current());
Timers.SetModified();
Display();
}
}
return osContinue;
}
void cMenuTimers::Move(int From, int To)
{
Timers.Move(From, To);
cOsdMenu::Move(From, To);
Timers.SetModified();
Display();
isyslog("timer %d moved to %d", From + 1, To + 1);
}
eOSState cMenuTimers::Summary(void)
{
if (HasSubMenu() || Count() == 0)
return osContinue;
cTimer *ti = CurrentTimer();
if (ti && !isempty(ti->Summary()))
return AddSubMenu(new cMenuText(tr("Summary"), ti->Summary()));
return Edit(); // convenience for people not using the Summary feature ;-)
}
eOSState cMenuTimers::ProcessKey(eKeys Key)
{
int TimerNumber = HasSubMenu() ? Count() : -1;
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kOk: return Summary();
case kRed: return Edit();
case kGreen: return New();
case kYellow: return Delete();
case kBlue: if (Setup.SortTimers)
OnOff();
else
Mark();
break;
default: break;
}
}
if (TimerNumber >= 0 && !HasSubMenu() && Timers.Get(TimerNumber)) {
// a newly created timer was confirmed with Ok
Add(new cMenuTimerItem(Timers.Get(TimerNumber)), true);
Display();
}
return state;
}
// --- cMenuEvent ------------------------------------------------------------
cMenuEvent::cMenuEvent(const cEvent *Event, bool CanSwitch)
:cOsdMenu(tr("Event"))
{
event = Event;
if (event) {
cChannel *channel = Channels.GetByChannelID(event->ChannelID(), true);
if (channel) {
SetTitle(channel->Name());
int TimerMatch = tmNone;
Timers.GetMatch(event, &TimerMatch);
SetHelp(TimerMatch == tmFull ? tr("Timer") : tr("Button$Record"), NULL, NULL, CanSwitch ? tr("Button$Switch") : NULL);
}
}
}
void cMenuEvent::Display(void)
{
cOsdMenu::Display();
DisplayMenu()->SetEvent(event);
cStatus::MsgOsdTextItem(event->Description());
}
eOSState cMenuEvent::ProcessKey(eKeys Key)
{
switch (Key) {
case kUp|k_Repeat:
case kUp:
case kDown|k_Repeat:
case kDown:
case kLeft|k_Repeat:
case kLeft:
case kRight|k_Repeat:
case kRight:
DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight);
cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp);
return osContinue;
default: break;
}
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kGreen:
case kYellow: return osContinue;
case kOk: return osBack;
default: break;
}
}
return state;
}
// --- cMenuScheduleItem -----------------------------------------------------
class cMenuScheduleItem : public cOsdItem {
public:
const cEvent *event;
const cChannel *channel;
int timerMatch;
cMenuScheduleItem(const cEvent *Event, cChannel *Channel = NULL);
bool Update(bool Force = false);
};
cMenuScheduleItem::cMenuScheduleItem(const cEvent *Event, cChannel *Channel)
{
event = Event;
channel = Channel;
timerMatch = tmNone;
Update(true);
}
static char *TimerMatchChars = " tT";
bool cMenuScheduleItem::Update(bool Force)
{
bool result = false;
int OldTimerMatch = timerMatch;
Timers.GetMatch(event, &timerMatch);
if (Force || timerMatch != OldTimerMatch) {
char *buffer = NULL;
char t = TimerMatchChars[timerMatch];
char v = event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' ';
char r = event->IsRunning() ? '*' : ' ';
if (channel)
asprintf(&buffer, "%d\t%.*s\t%s\t%c%c%c\t%s", channel->Number(), 6, channel->ShortName(true), *event->GetTimeString(), t, v, r, event->Title());
else
asprintf(&buffer, "%.*s\t%s\t%c%c%c\t%s", 6, *event->GetDateString(), *event->GetTimeString(), t, v, r, event->Title());
SetText(buffer, false);
result = true;
}
return result;
}
// --- cMenuWhatsOn ----------------------------------------------------------
class cMenuWhatsOn : public cOsdMenu {
private:
bool now;
int helpKeys;
eOSState Record(void);
eOSState Switch(void);
static int currentChannel;
static const cEvent *scheduleEvent;
bool Update(void);
void SetHelpKeys(void);
public:
cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr);
static int CurrentChannel(void) { return currentChannel; }
static void SetCurrentChannel(int ChannelNr) { currentChannel = ChannelNr; }
static const cEvent *ScheduleEvent(void);
virtual eOSState ProcessKey(eKeys Key);
};
int cMenuWhatsOn::currentChannel = 0;
const cEvent *cMenuWhatsOn::scheduleEvent = NULL;
cMenuWhatsOn::cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr)
:cOsdMenu(Now ? tr("What's on now?") : tr("What's on next?"), CHNUMWIDTH, 7, 6, 4)
{
now = Now;
helpKeys = -1;
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
if (!Channel->GroupSep()) {
const cSchedule *Schedule = Schedules->GetSchedule(Channel->GetChannelID());
if (Schedule) {
const cEvent *Event = Now ? Schedule->GetPresentEvent() : Schedule->GetFollowingEvent();
if (Event)
Add(new cMenuScheduleItem(Event, Channel), Channel->Number() == CurrentChannelNr);
}
}
}
currentChannel = CurrentChannelNr;
SetHelpKeys();
}
bool cMenuWhatsOn::Update(void)
{
bool result = false;
for (cOsdItem *item = First(); item; item = Next(item)) {
if (((cMenuScheduleItem *)item)->Update())
result = true;
}
return result;
}
void cMenuWhatsOn::SetHelpKeys(void)
{
cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
int NewHelpKeys = 0;
if (item) {
if (item->timerMatch == tmFull)
NewHelpKeys = 2;
else
NewHelpKeys = 1;
}
if (NewHelpKeys != helpKeys) {
const char *Red[] = { NULL, tr("Button$Record"), tr("Timer") };
SetHelp(Red[NewHelpKeys], now ? tr("Button$Next") : tr("Button$Now"), tr("Button$Schedule"), tr("Button$Switch"));
helpKeys = NewHelpKeys;
}
}
const cEvent *cMenuWhatsOn::ScheduleEvent(void)
{
const cEvent *ei = scheduleEvent;
scheduleEvent = NULL;
return ei;
}
eOSState cMenuWhatsOn::Switch(void)
{
cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
if (item) {
cChannel *channel = Channels.GetByChannelID(item->event->ChannelID(), true);
if (channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true))
return osEnd;
}
Skins.Message(mtError, tr("Can't switch channel!"));
return osContinue;
}
eOSState cMenuWhatsOn::Record(void)
{
cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
if (item) {
if (item->timerMatch == tmFull) {
int tm = tmNone;
cTimer *timer = Timers.GetMatch(item->event, &tm);
if (timer)
return AddSubMenu(new cMenuEditTimer(timer));
}
cTimer *timer = new cTimer(item->event);
cTimer *t = Timers.GetTimer(timer);
if (t) {
delete timer;
timer = t;
return AddSubMenu(new cMenuEditTimer(timer));
}
else {
Timers.Add(timer);
timer->Matches();
Timers.SetModified();
isyslog("timer %s added (active)", *timer->ToDescr());
if (HasSubMenu())
CloseSubMenu();
if (Update())
Display();
SetHelpKeys();
}
}
return osContinue;
}
eOSState cMenuWhatsOn::ProcessKey(eKeys Key)
{
bool HadSubMenu = HasSubMenu();
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kRecord:
case kRed: return Record();
case kYellow: state = osBack;
// continue with kGreen
case kGreen: {
cMenuScheduleItem *mi = (cMenuScheduleItem *)Get(Current());
if (mi) {
scheduleEvent = mi->event;
currentChannel = mi->channel->Number();
}
}
break;
case kBlue: return Switch();
case kOk: if (Count())
return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->event, true));
break;
default: break;
}
}
else if (!HasSubMenu()) {
if (HadSubMenu && Update())
Display();
if (Key != kNone)
SetHelpKeys();
}
return state;
}
// --- cMenuSchedule ---------------------------------------------------------
class cMenuSchedule : public cOsdMenu {
private:
cSchedulesLock schedulesLock;
const cSchedules *schedules;
bool now, next;
int otherChannel;
int helpKeys;
eOSState Record(void);
eOSState Switch(void);
void PrepareSchedule(cChannel *Channel);
bool Update(void);
void SetHelpKeys(void);
public:
cMenuSchedule(void);
virtual ~cMenuSchedule();
virtual eOSState ProcessKey(eKeys Key);
};
cMenuSchedule::cMenuSchedule(void)
:cOsdMenu("", 7, 6, 4)
{
now = next = false;
otherChannel = 0;
helpKeys = -1;
cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
if (channel) {
cMenuWhatsOn::SetCurrentChannel(channel->Number());
schedules = cSchedules::Schedules(schedulesLock);
PrepareSchedule(channel);
SetHelpKeys();
}
}
cMenuSchedule::~cMenuSchedule()
{
cMenuWhatsOn::ScheduleEvent(); // makes sure any posted data is cleared
}
void cMenuSchedule::PrepareSchedule(cChannel *Channel)
{
Clear();
char *buffer = NULL;
asprintf(&buffer, tr("Schedule - %s"), Channel->Name());
SetTitle(buffer);
free(buffer);
if (schedules) {
const cSchedule *Schedule = schedules->GetSchedule(Channel->GetChannelID());
if (Schedule) {
const cEvent *PresentEvent = Schedule->GetPresentEvent(Channel->Number() == cDevice::CurrentChannel());
time_t now = time(NULL) - Setup.EPGLinger * 60;
for (const cEvent *Event = Schedule->Events()->First(); Event; Event = Schedule->Events()->Next(Event)) {
if (Event->EndTime() > now || Event == PresentEvent)
Add(new cMenuScheduleItem(Event), Event == PresentEvent);
}
}
}
}
bool cMenuSchedule::Update(void)
{
bool result = false;
for (cOsdItem *item = First(); item; item = Next(item)) {
if (((cMenuScheduleItem *)item)->Update())
result = true;
}
return result;
}
void cMenuSchedule::SetHelpKeys(void)
{
cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
int NewHelpKeys = 0;
if (item) {
if (item->timerMatch == tmFull)
NewHelpKeys = 2;
else
NewHelpKeys = 1;
}
if (NewHelpKeys != helpKeys) {
const char *Red[] = { NULL, tr("Button$Record"), tr("Timer") };
SetHelp(Red[NewHelpKeys], tr("Button$Now"), tr("Button$Next"));
helpKeys = NewHelpKeys;
}
}
eOSState cMenuSchedule::Record(void)
{
cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current());
if (item) {
if (item->timerMatch == tmFull) {
int tm = tmNone;
cTimer *timer = Timers.GetMatch(item->event, &tm);
if (timer)
return AddSubMenu(new cMenuEditTimer(timer));
}
cTimer *timer = new cTimer(item->event);
cTimer *t = Timers.GetTimer(timer);
if (t) {
delete timer;
timer = t;
return AddSubMenu(new cMenuEditTimer(timer));
}
else {
Timers.Add(timer);
timer->Matches();
Timers.SetModified();
isyslog("timer %s added (active)", *timer->ToDescr());
if (HasSubMenu())
CloseSubMenu();
if (Update())
Display();
SetHelpKeys();
}
}
return osContinue;
}
eOSState cMenuSchedule::Switch(void)
{
if (otherChannel) {
if (Channels.SwitchTo(otherChannel))
return osEnd;
}
Skins.Message(mtError, tr("Can't switch channel!"));
return osContinue;
}
eOSState cMenuSchedule::ProcessKey(eKeys Key)
{
bool HadSubMenu = HasSubMenu();
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kRecord:
case kRed: return Record();
case kGreen: if (schedules) {
if (!now && !next) {
int ChannelNr = 0;
if (Count()) {
cChannel *channel = Channels.GetByChannelID(((cMenuScheduleItem *)Get(Current()))->event->ChannelID(), true);
if (channel)
ChannelNr = channel->Number();
}
now = true;
return AddSubMenu(new cMenuWhatsOn(schedules, true, ChannelNr));
}
now = !now;
next = !next;
return AddSubMenu(new cMenuWhatsOn(schedules, now, cMenuWhatsOn::CurrentChannel()));
}
case kYellow: if (schedules)
return AddSubMenu(new cMenuWhatsOn(schedules, false, cMenuWhatsOn::CurrentChannel()));
break;
case kBlue: if (Count())
return Switch();
break;
case kOk: if (Count())
return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->event, otherChannel));
break;
default: break;
}
}
else if (!HasSubMenu()) {
now = next = false;
const cEvent *ei = cMenuWhatsOn::ScheduleEvent();
if (ei) {
cChannel *channel = Channels.GetByChannelID(ei->ChannelID(), true);
if (channel) {
PrepareSchedule(channel);
if (channel->Number() != cDevice::CurrentChannel()) {
otherChannel = channel->Number();
SetHelp(Count() ? tr("Button$Record") : NULL, tr("Button$Now"), tr("Button$Next"), tr("Button$Switch"));
}
Display();
}
}
else if (HadSubMenu && Update())
Display();
if (Key != kNone)
SetHelpKeys();
}
return state;
}
// --- cMenuCommands ---------------------------------------------------------
class cMenuCommands : public cOsdMenu {
private:
cCommands *commands;
char *parameters;
eOSState Execute(void);
public:
cMenuCommands(const char *Title, cCommands *Commands, const char *Parameters = NULL);
virtual ~cMenuCommands();
virtual eOSState ProcessKey(eKeys Key);
};
cMenuCommands::cMenuCommands(const char *Title, cCommands *Commands, const char *Parameters)
:cOsdMenu(Title)
{
SetHasHotkeys();
commands = Commands;
parameters = Parameters ? strdup(Parameters) : NULL;
for (cCommand *command = commands->First(); command; command = commands->Next(command))
Add(new cOsdItem(hk(command->Title())));
}
cMenuCommands::~cMenuCommands()
{
free(parameters);
}
eOSState cMenuCommands::Execute(void)
{
cCommand *command = commands->Get(Current());
if (command) {
char *buffer = NULL;
bool confirmed = true;
if (command->Confirm()) {
asprintf(&buffer, "%s?", command->Title());
confirmed = Interface->Confirm(buffer);
free(buffer);
}
if (confirmed) {
asprintf(&buffer, "%s...", command->Title());
Skins.Message(mtStatus, buffer);
free(buffer);
const char *Result = command->Execute(parameters);
Skins.Message(mtStatus, NULL);
if (Result)
return AddSubMenu(new cMenuText(command->Title(), Result, fontFix));
return osEnd;
}
}
return osContinue;
}
eOSState cMenuCommands::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kRed:
case kGreen:
case kYellow:
case kBlue: return osContinue;
case kOk: return Execute();
default: break;
}
}
return state;
}
// --- cMenuCam --------------------------------------------------------------
cMenuCam::cMenuCam(cCiMenu *CiMenu)
:cOsdMenu("")
{
dsyslog("CAM: Menu ------------------");
ciMenu = CiMenu;
selected = false;
offset = 0;
if (ciMenu->Selectable())
SetHasHotkeys();
SetTitle(*ciMenu->TitleText() ? ciMenu->TitleText() : "CAM");
dsyslog("CAM: '%s'", ciMenu->TitleText());
if (*ciMenu->SubTitleText()) {
dsyslog("CAM: '%s'", ciMenu->SubTitleText());
AddMultiLineItem(ciMenu->SubTitleText());
offset = Count();
}
for (int i = 0; i < ciMenu->NumEntries(); i++) {
Add(new cOsdItem(hk(ciMenu->Entry(i)), osUnknown, ciMenu->Selectable()));
dsyslog("CAM: '%s'", ciMenu->Entry(i));
}
if (*ciMenu->BottomText()) {
AddMultiLineItem(ciMenu->BottomText());
dsyslog("CAM: '%s'", ciMenu->BottomText());
}
Display();
}
cMenuCam::~cMenuCam()
{
if (!selected)
ciMenu->Abort();
delete ciMenu;
}
void cMenuCam::AddMultiLineItem(const char *s)
{
while (s && *s) {
const char *p = strchr(s, '\n');
int l = p ? p - s : strlen(s);
cOsdItem *item = new cOsdItem;
item->SetSelectable(false);
item->SetText(strndup(s, l), false);
Add(item);
s = p ? p + 1 : p;
}
}
eOSState cMenuCam::Select(void)
{
if (ciMenu->Selectable()) {
ciMenu->Select(Current() - offset);
dsyslog("CAM: select %d", Current() - offset);
}
else
ciMenu->Cancel();
selected = true;
return osEnd;
}
eOSState cMenuCam::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kOk: return Select();
default: break;
}
}
else if (state == osBack) {
ciMenu->Cancel();
selected = true;
return osEnd;
}
if (ciMenu->HasUpdate()) {
selected = true;
return osEnd;
}
return state;
}
// --- cMenuCamEnquiry -------------------------------------------------------
cMenuCamEnquiry::cMenuCamEnquiry(cCiEnquiry *CiEnquiry)
:cOsdMenu("", 1)
{
ciEnquiry = CiEnquiry;
int Length = ciEnquiry->ExpectedLength();
input = MALLOC(char, Length + 1);
*input = 0;
replied = false;
SetTitle("CAM");
Add(new cOsdItem(ciEnquiry->Text(), osUnknown, false));
Add(new cOsdItem("", osUnknown, false));
Add(new cMenuEditNumItem("", input, Length, ciEnquiry->Blind()));
Display();
}
cMenuCamEnquiry::~cMenuCamEnquiry()
{
if (!replied)
ciEnquiry->Abort();
free(input);
delete ciEnquiry;
}
eOSState cMenuCamEnquiry::Reply(void)
{
if (ciEnquiry->ExpectedLength() < 0xFF && int(strlen(input)) != ciEnquiry->ExpectedLength()) {
char buffer[64];
snprintf(buffer, sizeof(buffer), tr("Please enter %d digits!"), ciEnquiry->ExpectedLength());
Skins.Message(mtError, buffer);
return osContinue;
}
ciEnquiry->Reply(input);
replied = true;
return osEnd;
}
eOSState cMenuCamEnquiry::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kOk: return Reply();
default: break;
}
}
else if (state == osBack) {
ciEnquiry->Cancel();
replied = true;
return osEnd;
}
return state;
}
// --- CamControl ------------------------------------------------------------
cOsdObject *CamControl(void)
{
for (int d = 0; d < cDevice::NumDevices(); d++) {
cDevice *Device = cDevice::GetDevice(d);
if (Device) {
cCiHandler *CiHandler = Device->CiHandler();
if (CiHandler && CiHandler->HasUserIO()) {
cCiMenu *CiMenu = CiHandler->GetMenu();
if (CiMenu)
return new cMenuCam(CiMenu);
else {
cCiEnquiry *CiEnquiry = CiHandler->GetEnquiry();
if (CiEnquiry)
return new cMenuCamEnquiry(CiEnquiry);
}
}
}
}
return NULL;
}
// --- cMenuRecording --------------------------------------------------------
class cMenuRecording : public cOsdMenu {
private:
const cRecording *recording;
public:
cMenuRecording(const cRecording *Recording);
virtual void Display(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuRecording::cMenuRecording(const cRecording *Recording)
:cOsdMenu(tr("Recording info"))
{
recording = Recording;
if (recording)
SetHelp(tr("Button$Play"), tr("Button$Rewind"));
}
void cMenuRecording::Display(void)
{
cOsdMenu::Display();
DisplayMenu()->SetRecording(recording);
cStatus::MsgOsdTextItem(recording->Info()->Description());
}
eOSState cMenuRecording::ProcessKey(eKeys Key)
{
switch (Key) {
case kUp|k_Repeat:
case kUp:
case kDown|k_Repeat:
case kDown:
case kLeft|k_Repeat:
case kLeft:
case kRight|k_Repeat:
case kRight:
DisplayMenu()->Scroll(NORMALKEY(Key) == kUp || NORMALKEY(Key) == kLeft, NORMALKEY(Key) == kLeft || NORMALKEY(Key) == kRight);
cStatus::MsgOsdTextItem(NULL, NORMALKEY(Key) == kUp);
return osContinue;
default: break;
}
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kRed: Key = kOk; // will play the recording, even if recording commands are defined
case kGreen: cRemote::Put(Key, true);
// continue with osBack to close the info menu and process the key
case kOk: return osBack;
default: break;
}
}
return state;
}
// --- cMenuRecordingItem ----------------------------------------------------
class cMenuRecordingItem : public cOsdItem {
private:
char *fileName;
char *name;
int totalEntries, newEntries;
public:
cMenuRecordingItem(cRecording *Recording, int Level);
~cMenuRecordingItem();
void IncrementCounter(bool New);
const char *Name(void) { return name; }
const char *FileName(void) { return fileName; }
bool IsDirectory(void) { return name != NULL; }
};
cMenuRecordingItem::cMenuRecordingItem(cRecording *Recording, int Level)
{
fileName = strdup(Recording->FileName());
name = NULL;
totalEntries = newEntries = 0;
SetText(Recording->Title('\t', true, Level));
if (*Text() == '\t')
name = strdup(Text() + 2); // 'Text() + 2' to skip the two '\t'
}
cMenuRecordingItem::~cMenuRecordingItem()
{
free(fileName);
free(name);
}
void cMenuRecordingItem::IncrementCounter(bool New)
{
totalEntries++;
if (New)
newEntries++;
char *buffer = NULL;
asprintf(&buffer, "%d\t%d\t%s", totalEntries, newEntries, name);
SetText(buffer, false);
}
// --- cMenuRecordings -------------------------------------------------------
cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus)
:cOsdMenu(Base ? Base : tr("Recordings"), 8, 6)
{
base = Base ? strdup(Base) : NULL;
level = Setup.RecordingDirs ? Level : -1;
Recordings.StateChanged(recordingsState); // just to get the current state
helpKeys = -1;
Display(); // this keeps the higher level menus from showing up briefly when pressing 'Back' during replay
Set();
if (Current() < 0)
SetCurrent(First());
else if (OpenSubMenus && cReplayControl::LastReplayed() && Open(true))
return;
Display();
SetHelpKeys();
}
cMenuRecordings::~cMenuRecordings()
{
helpKeys = -1;
free(base);
}
void cMenuRecordings::SetHelpKeys(void)
{
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
int NewHelpKeys = 0;
if (ri) {
if (ri->IsDirectory())
NewHelpKeys = 1;
else {
NewHelpKeys = 2;
cRecording *recording = GetRecording(ri);
if (recording && recording->Info()->Title())
NewHelpKeys = 3;
}
}
if (NewHelpKeys != helpKeys) {
switch (NewHelpKeys) {
case 0: SetHelp(NULL); break;
case 1: SetHelp(tr("Button$Open")); break;
case 2:
case 3: SetHelp(RecordingCommands.Count() ? tr("Commands") : tr("Button$Play"), tr("Button$Rewind"), tr("Button$Delete"), NewHelpKeys == 3 ? tr("Info") : NULL);
}
helpKeys = NewHelpKeys;
}
}
void cMenuRecordings::Set(bool Refresh)
{
const char *CurrentRecording = cReplayControl::LastReplayed();
cMenuRecordingItem *LastItem = NULL;
char *LastItemText = NULL;
cThreadLock RecordingsLock(&Recordings);
if (Refresh) {
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri) {
cRecording *Recording = Recordings.GetByName(ri->FileName());
if (Recording)
CurrentRecording = Recording->FileName();
}
}
Clear();
Recordings.Sort();
for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) {
if (!base || (strstr(recording->Name(), base) == recording->Name() && recording->Name()[strlen(base)] == '~')) {
cMenuRecordingItem *Item = new cMenuRecordingItem(recording, level);
if (*Item->Text() && (!LastItem || strcmp(Item->Text(), LastItemText) != 0)) {
Add(Item);
LastItem = Item;
free(LastItemText);
LastItemText = strdup(LastItem->Text()); // must use a copy because of the counters!
}
else
delete Item;
if (LastItem) {
if (CurrentRecording && strcmp(CurrentRecording, recording->FileName()) == 0)
SetCurrent(LastItem);
if (LastItem->IsDirectory())
LastItem->IncrementCounter(recording->IsNew());
}
}
}
free(LastItemText);
if (Refresh)
Display();
}
cRecording *cMenuRecordings::GetRecording(cMenuRecordingItem *Item)
{
cRecording *recording = Recordings.GetByName(Item->FileName());
if (!recording)
Skins.Message(mtError, tr("Error while accessing recording!"));
return recording;
}
bool cMenuRecordings::Open(bool OpenSubMenus)
{
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri && ri->IsDirectory()) {
const char *t = ri->Name();
char *buffer = NULL;
if (base) {
asprintf(&buffer, "%s~%s", base, t);
t = buffer;
}
AddSubMenu(new cMenuRecordings(t, level + 1, OpenSubMenus));
free(buffer);
return true;
}
return false;
}
eOSState cMenuRecordings::Play(void)
{
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri) {
if (ri->IsDirectory())
Open();
else {
cRecording *recording = GetRecording(ri);
if (recording) {
cReplayControl::SetRecording(recording->FileName(), recording->Title());
return osReplay;
}
}
}
return osContinue;
}
eOSState cMenuRecordings::Rewind(void)
{
if (HasSubMenu() || Count() == 0)
return osContinue;
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri && !ri->IsDirectory()) {
cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording
cResumeFile ResumeFile(ri->FileName());
ResumeFile.Delete();
return Play();
}
return osContinue;
}
eOSState cMenuRecordings::Delete(void)
{
if (HasSubMenu() || Count() == 0)
return osContinue;
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri && !ri->IsDirectory()) {
if (Interface->Confirm(tr("Delete recording?"))) {
cRecordControl *rc = cRecordControls::GetRecordControl(ri->FileName());
if (rc) {
if (Interface->Confirm(tr("Timer still recording - really delete?"))) {
cTimer *timer = rc->Timer();
if (timer) {
timer->Skip();
cRecordControls::Process(time(NULL));
if (timer->IsSingleEvent()) {
isyslog("deleting timer %s", *timer->ToDescr());
Timers.Del(timer);
}
Timers.SetModified();
}
}
else
return osContinue;
}
cRecording *recording = GetRecording(ri);
if (recording) {
if (recording->Delete()) {
cReplayControl::ClearLastReplayed(ri->FileName());
Recordings.DelByName(ri->FileName());
cOsdMenu::Del(Current());
SetHelpKeys();
Display();
if (!Count())
return osBack;
}
else
Skins.Message(mtError, tr("Error while deleting recording!"));
}
}
}
return osContinue;
}
eOSState cMenuRecordings::Info(void)
{
if (HasSubMenu() || Count() == 0)
return osContinue;
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri && !ri->IsDirectory()) {
cRecording *recording = GetRecording(ri);
if (recording && recording->Info()->Title())
return AddSubMenu(new cMenuRecording(recording));
}
return osContinue;
}
eOSState cMenuRecordings::Commands(eKeys Key)
{
if (HasSubMenu() || Count() == 0)
return osContinue;
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri && !ri->IsDirectory()) {
cRecording *recording = GetRecording(ri);
if (recording) {
char *parameter = NULL;
asprintf(&parameter, "'%s'", recording->FileName());
cMenuCommands *menu;
eOSState state = AddSubMenu(menu = new cMenuCommands(tr("Recording commands"), &RecordingCommands, parameter));
free(parameter);
if (Key != kNone)
state = menu->ProcessKey(Key);
return state;
}
}
return osContinue;
}
eOSState cMenuRecordings::ProcessKey(eKeys Key)
{
bool HadSubMenu = HasSubMenu();
eOSState state = cOsdMenu::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kOk: return Play();
case kRed: return (helpKeys > 1 && RecordingCommands.Count()) ? Commands() : Play();
case kGreen: return Rewind();
case kYellow: return Delete();
case kBlue: return Info();
case k1...k9: return Commands(Key);
case kNone: if (Recordings.StateChanged(recordingsState))
Set(true);
break;
default: break;
}
}
if (Key == kYellow && HadSubMenu && !HasSubMenu()) {
// the last recording in a subdirectory was deleted, so let's go back up
cOsdMenu::Del(Current());
if (!Count())
return osBack;
Display();
}
if (!HasSubMenu() && Key != kNone)
SetHelpKeys();
return state;
}
// --- cMenuSetupBase --------------------------------------------------------
class cMenuSetupBase : public cMenuSetupPage {
protected:
cSetup data;
virtual void Store(void);
public:
cMenuSetupBase(void);
};
cMenuSetupBase::cMenuSetupBase(void)
{
data = Setup;
}
void cMenuSetupBase::Store(void)
{
Setup = data;
Setup.Save();
}
// --- cMenuSetupOSD ---------------------------------------------------------
class cMenuSetupOSD : public cMenuSetupBase {
private:
const char *useSmallFontTexts[3];
int numSkins;
int originalSkinIndex;
int skinIndex;
const char **skinDescriptions;
cThemes themes;
int themeIndex;
virtual void Set(void);
public:
cMenuSetupOSD(void);
virtual ~cMenuSetupOSD();
virtual eOSState ProcessKey(eKeys Key);
};
cMenuSetupOSD::cMenuSetupOSD(void)
{
numSkins = Skins.Count();
skinIndex = originalSkinIndex = Skins.Current()->Index();
skinDescriptions = new const char*[numSkins];
themes.Load(Skins.Current()->Name());
themeIndex = Skins.Current()->Theme() ? themes.GetThemeIndex(Skins.Current()->Theme()->Description()) : 0;
Set();
}
cMenuSetupOSD::~cMenuSetupOSD()
{
cFont::SetCode(I18nCharSets()[Setup.OSDLanguage]);
delete skinDescriptions;
}
void cMenuSetupOSD::Set(void)
{
int current = Current();
for (cSkin *Skin = Skins.First(); Skin; Skin = Skins.Next(Skin))
skinDescriptions[Skin->Index()] = Skin->Description();
useSmallFontTexts[0] = tr("never");
useSmallFontTexts[1] = tr("skin dependent");
useSmallFontTexts[2] = tr("always");
Clear();
SetSection(tr("OSD"));
Add(new cMenuEditStraItem(tr("Setup.OSD$Language"), &data.OSDLanguage, I18nNumLanguages, I18nLanguages()));
Add(new cMenuEditStraItem(tr("Setup.OSD$Skin"), &skinIndex, numSkins, skinDescriptions));
if (themes.NumThemes())
Add(new cMenuEditStraItem(tr("Setup.OSD$Theme"), &themeIndex, themes.NumThemes(), themes.Descriptions()));
Add(new cMenuEditIntItem( tr("Setup.OSD$Left"), &data.OSDLeft, 0, MAXOSDWIDTH));
Add(new cMenuEditIntItem( tr("Setup.OSD$Top"), &data.OSDTop, 0, MAXOSDHEIGHT));
Add(new cMenuEditIntItem( tr("Setup.OSD$Width"), &data.OSDWidth, MINOSDWIDTH, MAXOSDWIDTH));
Add(new cMenuEditIntItem( tr("Setup.OSD$Height"), &data.OSDHeight, MINOSDHEIGHT, MAXOSDHEIGHT));
Add(new cMenuEditIntItem( tr("Setup.OSD$Message time (s)"), &data.OSDMessageTime, 1, 60));
Add(new cMenuEditStraItem(tr("Setup.OSD$Use small font"), &data.UseSmallFont, 3, useSmallFontTexts));
Add(new cMenuEditBoolItem(tr("Setup.OSD$Channel info position"), &data.ChannelInfoPos, tr("bottom"), tr("top")));
Add(new cMenuEditIntItem( tr("Setup.OSD$Channel info time (s)"), &data.ChannelInfoTime, 1, 60));
Add(new cMenuEditBoolItem(tr("Setup.OSD$Info on channel switch"), &data.ShowInfoOnChSwitch));
Add(new cMenuEditBoolItem(tr("Setup.OSD$Timeout requested channel info"), &data.TimeoutRequChInfo));
Add(new cMenuEditBoolItem(tr("Setup.OSD$Scroll pages"), &data.MenuScrollPage));
Add(new cMenuEditBoolItem(tr("Setup.OSD$Scroll wraps"), &data.MenuScrollWrap));
Add(new cMenuEditBoolItem(tr("Setup.OSD$Sort timers"), &data.SortTimers));
Add(new cMenuEditBoolItem(tr("Setup.OSD$Recording directories"), &data.RecordingDirs));
SetCurrent(Get(current));
Display();
}
eOSState cMenuSetupOSD::ProcessKey(eKeys Key)
{
if (Key == kOk) {
if (skinIndex != originalSkinIndex) {
cSkin *Skin = Skins.Get(skinIndex);
if (Skin) {
strn0cpy(data.OSDSkin, Skin->Name(), sizeof(data.OSDSkin));
Skins.SetCurrent(Skin->Name());
}
}
if (themes.NumThemes() && Skins.Current()->Theme()) {
Skins.Current()->Theme()->Load(themes.FileName(themeIndex));
strn0cpy(data.OSDTheme, themes.Name(themeIndex), sizeof(data.OSDTheme));
}
data.OSDWidth &= ~0x07; // OSD width must be a multiple of 8
}
int osdLanguage = data.OSDLanguage;
int oldSkinIndex = skinIndex;
eOSState state = cMenuSetupBase::ProcessKey(Key);
if (data.OSDLanguage != osdLanguage || skinIndex != oldSkinIndex) {
int OriginalOSDLanguage = Setup.OSDLanguage;
Setup.OSDLanguage = data.OSDLanguage;
cFont::SetCode(I18nCharSets()[Setup.OSDLanguage]);
cSkin *Skin = Skins.Get(skinIndex);
if (Skin) {
char *d = themes.NumThemes() ? strdup(themes.Descriptions()[themeIndex]) : NULL;
themes.Load(Skin->Name());
if (skinIndex != oldSkinIndex)
themeIndex = d ? themes.GetThemeIndex(d) : 0;
free(d);
}
Set();
Setup.OSDLanguage = OriginalOSDLanguage;
}
return state;
}
// --- cMenuSetupEPG ---------------------------------------------------------
class cMenuSetupEPG : public cMenuSetupBase {
private:
int originalNumLanguages;
int numLanguages;
void Setup(void);
public:
cMenuSetupEPG(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuSetupEPG::cMenuSetupEPG(void)
{
for (numLanguages = 0; numLanguages < I18nNumLanguages && data.EPGLanguages[numLanguages] >= 0; numLanguages++)
;
originalNumLanguages = numLanguages;
SetSection(tr("EPG"));
SetHelp(tr("Button$Scan"));
Setup();
}
void cMenuSetupEPG::Setup(void)
{
int current = Current();
Clear();
Add(new cMenuEditIntItem( tr("Setup.EPG$EPG scan timeout (h)"), &data.EPGScanTimeout));
Add(new cMenuEditIntItem( tr("Setup.EPG$EPG bugfix level"), &data.EPGBugfixLevel, 0, MAXEPGBUGFIXLEVEL));
Add(new cMenuEditIntItem( tr("Setup.EPG$EPG linger time (min)"), &data.EPGLinger, 0));
Add(new cMenuEditBoolItem(tr("Setup.EPG$Set system time"), &data.SetSystemTime));
if (data.SetSystemTime)
Add(new cMenuEditTranItem(tr("Setup.EPG$Use time from transponder"), &data.TimeTransponder, &data.TimeSource));
Add(new cMenuEditIntItem( tr("Setup.EPG$Preferred languages"), &numLanguages, 0, I18nNumLanguages));
for (int i = 0; i < numLanguages; i++)
Add(new cMenuEditStraItem(tr("Setup.EPG$Preferred language"), &data.EPGLanguages[i], I18nNumLanguages, I18nLanguages()));
SetCurrent(Get(current));
Display();
}
eOSState cMenuSetupEPG::ProcessKey(eKeys Key)
{
if (Key == kOk) {
bool Modified = numLanguages != originalNumLanguages;
if (!Modified) {
for (int i = 0; i < numLanguages; i++) {
if (data.EPGLanguages[i] != ::Setup.EPGLanguages[i]) {
Modified = true;
break;
}
}
}
if (Modified)
cSchedules::ResetVersions();
}
int oldnumLanguages = numLanguages;
int oldSetSystemTime = data.SetSystemTime;
eOSState state = cMenuSetupBase::ProcessKey(Key);
if (Key != kNone) {
if (numLanguages != oldnumLanguages || data.SetSystemTime != oldSetSystemTime) {
for (int i = oldnumLanguages; i < numLanguages; i++) {
data.EPGLanguages[i] = 0;
for (int l = 0; l < I18nNumLanguages; l++) {
int k;
for (k = 0; k < oldnumLanguages; k++) {
if (data.EPGLanguages[k] == l)
break;
}
if (k >= oldnumLanguages) {
data.EPGLanguages[i] = l;
break;
}
}
}
data.EPGLanguages[numLanguages] = -1;
Setup();
}
if (Key == kRed) {
EITScanner.ForceScan();
return osEnd;
}
}
return state;
}
// --- cMenuSetupDVB ---------------------------------------------------------
class cMenuSetupDVB : public cMenuSetupBase {
private:
int originalNumAudioLanguages;
int numAudioLanguages;
void Setup(void);
const char *videoDisplayFormatTexts[3];
const char *updateChannelsTexts[5];
public:
cMenuSetupDVB(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuSetupDVB::cMenuSetupDVB(void)
{
for (numAudioLanguages = 0; numAudioLanguages < I18nNumLanguages && data.AudioLanguages[numAudioLanguages] >= 0; numAudioLanguages++)
;
originalNumAudioLanguages = numAudioLanguages;
videoDisplayFormatTexts[0] = tr("pan&scan");
videoDisplayFormatTexts[1] = tr("letterbox");
videoDisplayFormatTexts[2] = tr("center cut out");
updateChannelsTexts[0] = tr("no");
updateChannelsTexts[1] = tr("names only");
updateChannelsTexts[2] = tr("names and PIDs");
updateChannelsTexts[3] = tr("add new channels");
updateChannelsTexts[4] = tr("add new transponders");
SetSection(tr("DVB"));
Setup();
}
void cMenuSetupDVB::Setup(void)
{
int current = Current();
Clear();
Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDevice::NumDevices()));
Add(new cMenuEditBoolItem(tr("Setup.DVB$Video format"), &data.VideoFormat, "4:3", "16:9"));
if (data.VideoFormat == 0)
Add(new cMenuEditStraItem(tr("Setup.DVB$Video display format"), &data.VideoDisplayFormat, 3, videoDisplayFormatTexts));
Add(new cMenuEditBoolItem(tr("Setup.DVB$Use Dolby Digital"), &data.UseDolbyDigital));
Add(new cMenuEditStraItem(tr("Setup.DVB$Update channels"), &data.UpdateChannels, 5, updateChannelsTexts));
Add(new cMenuEditIntItem( tr("Setup.DVB$Audio languages"), &numAudioLanguages, 0, I18nNumLanguages));
for (int i = 0; i < numAudioLanguages; i++)
Add(new cMenuEditStraItem(tr("Setup.DVB$Audio language"), &data.AudioLanguages[i], I18nNumLanguages, I18nLanguages()));
SetCurrent(Get(current));
Display();
}
eOSState cMenuSetupDVB::ProcessKey(eKeys Key)
{
int oldPrimaryDVB = ::Setup.PrimaryDVB;
int oldVideoDisplayFormat = ::Setup.VideoDisplayFormat;
bool oldVideoFormat = ::Setup.VideoFormat;
bool newVideoFormat = data.VideoFormat;
int oldnumAudioLanguages = numAudioLanguages;
eOSState state = cMenuSetupBase::ProcessKey(Key);
if (Key != kNone) {
bool DoSetup = data.VideoFormat != newVideoFormat;
if (numAudioLanguages != oldnumAudioLanguages) {
for (int i = oldnumAudioLanguages; i < numAudioLanguages; i++) {
data.AudioLanguages[i] = 0;
for (int l = 0; l < I18nNumLanguages; l++) {
int k;
for (k = 0; k < oldnumAudioLanguages; k++) {
if (data.AudioLanguages[k] == l)
break;
}
if (k >= oldnumAudioLanguages) {
data.AudioLanguages[i] = l;
break;
}
}
}
data.AudioLanguages[numAudioLanguages] = -1;
DoSetup = true;
}
if (DoSetup)
Setup();
}
if (state == osBack && Key == kOk) {
if (::Setup.PrimaryDVB != oldPrimaryDVB)
state = osSwitchDvb;
if (::Setup.VideoDisplayFormat != oldVideoDisplayFormat)
cDevice::PrimaryDevice()->SetVideoDisplayFormat(eVideoDisplayFormat(::Setup.VideoDisplayFormat));
if (::Setup.VideoFormat != oldVideoFormat)
cDevice::PrimaryDevice()->SetVideoFormat(::Setup.VideoFormat);
}
return state;
}
// --- cMenuSetupLNB ---------------------------------------------------------
class cMenuSetupLNB : public cMenuSetupBase {
private:
void Setup(void);
public:
cMenuSetupLNB(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuSetupLNB::cMenuSetupLNB(void)
{
SetSection(tr("LNB"));
Setup();
}
void cMenuSetupLNB::Setup(void)
{
int current = Current();
Clear();
Add(new cMenuEditBoolItem(tr("Setup.LNB$Use DiSEqC"), &data.DiSEqC));
if (!data.DiSEqC) {
Add(new cMenuEditIntItem( tr("Setup.LNB$SLOF (MHz)"), &data.LnbSLOF));
Add(new cMenuEditIntItem( tr("Setup.LNB$Low LNB frequency (MHz)"), &data.LnbFrequLo));
Add(new cMenuEditIntItem( tr("Setup.LNB$High LNB frequency (MHz)"), &data.LnbFrequHi));
}
SetCurrent(Get(current));
Display();
}
eOSState cMenuSetupLNB::ProcessKey(eKeys Key)
{
int oldDiSEqC = data.DiSEqC;
eOSState state = cMenuSetupBase::ProcessKey(Key);
if (Key != kNone && data.DiSEqC != oldDiSEqC)
Setup();
return state;
}
// --- cMenuSetupCICAM -------------------------------------------------------
class cMenuSetupCICAMItem : public cOsdItem {
private:
cCiHandler *ciHandler;
int slot;
public:
cMenuSetupCICAMItem(int Device, cCiHandler *CiHandler, int Slot);
cCiHandler *CiHandler(void) { return ciHandler; }
int Slot(void) { return slot; }
};
cMenuSetupCICAMItem::cMenuSetupCICAMItem(int Device, cCiHandler *CiHandler, int Slot)
{
ciHandler = CiHandler;
slot = Slot;
char buffer[32];
const char *CamName = CiHandler->GetCamName(slot);
snprintf(buffer, sizeof(buffer), "%s%d %d\t%s", tr("Setup.CICAM$CICAM DVB"), Device + 1, slot + 1, CamName ? CamName : "-");
SetText(buffer);
}
class cMenuSetupCICAM : public cMenuSetupBase {
private:
eOSState Menu(void);
eOSState Reset(void);
public:
cMenuSetupCICAM(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuSetupCICAM::cMenuSetupCICAM(void)
{
SetSection(tr("CICAM"));
for (int d = 0; d < cDevice::NumDevices(); d++) {
cDevice *Device = cDevice::GetDevice(d);
if (Device) {
cCiHandler *CiHandler = Device->CiHandler();
if (CiHandler) {
for (int Slot = 0; Slot < CiHandler->NumSlots(); Slot++)
Add(new cMenuSetupCICAMItem(Device->CardIndex(), CiHandler, Slot));
}
}
}
SetHelp(tr("Button$Menu"), tr("Button$Reset"));
}
eOSState cMenuSetupCICAM::Menu(void)
{
cMenuSetupCICAMItem *item = (cMenuSetupCICAMItem *)Get(Current());
if (item) {
if (item->CiHandler()->EnterMenu(item->Slot())) {
Skins.Message(mtWarning, tr("Opening CAM menu..."));
time_t t = time(NULL);
while (time(NULL) - t < MAXWAITFORCAMMENU && !item->CiHandler()->HasUserIO())
item->CiHandler()->Process();
return osEnd; // the CAM menu will be executed explicitly from the main loop
}
else
Skins.Message(mtError, tr("Can't open CAM menu!"));
}
return osContinue;
}
eOSState cMenuSetupCICAM::Reset(void)
{
cMenuSetupCICAMItem *item = (cMenuSetupCICAMItem *)Get(Current());
if (item) {
Skins.Message(mtWarning, tr("Resetting CAM..."));
if (item->CiHandler()->Reset(item->Slot())) {
Skins.Message(mtInfo, tr("CAM has been reset"));
return osEnd;
}
else
Skins.Message(mtError, tr("Can't reset CAM!"));
}
return osContinue;
}
eOSState cMenuSetupCICAM::ProcessKey(eKeys Key)
{
eOSState state = cMenuSetupBase::ProcessKey(Key);
if (state == osUnknown) {
switch (Key) {
case kRed: return Menu();
case kGreen: return Reset();
default: break;
}
}
return state;
}
// --- cMenuSetupRecord ------------------------------------------------------
class cMenuSetupRecord : public cMenuSetupBase {
public:
cMenuSetupRecord(void);
};
cMenuSetupRecord::cMenuSetupRecord(void)
{
SetSection(tr("Recording"));
Add(new cMenuEditIntItem( tr("Setup.Recording$Margin at start (min)"), &data.MarginStart));
Add(new cMenuEditIntItem( tr("Setup.Recording$Margin at stop (min)"), &data.MarginStop));
Add(new cMenuEditIntItem( tr("Setup.Recording$Primary limit"), &data.PrimaryLimit, 0, MAXPRIORITY));
Add(new cMenuEditIntItem( tr("Setup.Recording$Default priority"), &data.DefaultPriority, 0, MAXPRIORITY));
Add(new cMenuEditIntItem( tr("Setup.Recording$Default lifetime (d)"), &data.DefaultLifetime, 0, MAXLIFETIME));
Add(new cMenuEditIntItem( tr("Setup.Recording$Pause priority"), &data.PausePriority, 0, MAXPRIORITY));
Add(new cMenuEditIntItem( tr("Setup.Recording$Pause lifetime (d)"), &data.PauseLifetime, 0, MAXLIFETIME));
Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle));
Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps));
Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0));
Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord));
Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord), tr(FileNameChars)));
Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 1, MAXINSTANTRECTIME));
Add(new cMenuEditIntItem( tr("Setup.Recording$Max. video file size (MB)"), &data.MaxVideoFileSize, MINVIDEOFILESIZE, MAXVIDEOFILESIZE));
Add(new cMenuEditBoolItem(tr("Setup.Recording$Split edited files"), &data.SplitEditedFiles));
}
// --- cMenuSetupReplay ------------------------------------------------------
class cMenuSetupReplay : public cMenuSetupBase {
protected:
virtual void Store(void);
public:
cMenuSetupReplay(void);
};
cMenuSetupReplay::cMenuSetupReplay(void)
{
SetSection(tr("Replay"));
Add(new cMenuEditBoolItem(tr("Setup.Replay$Multi speed mode"), &data.MultiSpeedMode));
Add(new cMenuEditBoolItem(tr("Setup.Replay$Show replay mode"), &data.ShowReplayMode));
Add(new cMenuEditIntItem(tr("Setup.Replay$Resume ID"), &data.ResumeID, 0, 99));
}
void cMenuSetupReplay::Store(void)
{
if (Setup.ResumeID != data.ResumeID)
Recordings.ResetResume();
cMenuSetupBase::Store();
}
// --- cMenuSetupMisc --------------------------------------------------------
class cMenuSetupMisc : public cMenuSetupBase {
public:
cMenuSetupMisc(void);
};
cMenuSetupMisc::cMenuSetupMisc(void)
{
SetSection(tr("Miscellaneous"));
Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. event timeout (min)"), &data.MinEventTimeout));
Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. user inactivity (min)"), &data.MinUserInactivity));
Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$SVDRP timeout (s)"), &data.SVDRPTimeout));
Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Zap timeout (s)"), &data.ZapTimeout));
}
// --- cMenuSetupPluginItem --------------------------------------------------
class cMenuSetupPluginItem : public cOsdItem {
private:
int pluginIndex;
public:
cMenuSetupPluginItem(const char *Name, int Index);
int PluginIndex(void) { return pluginIndex; }
};
cMenuSetupPluginItem::cMenuSetupPluginItem(const char *Name, int Index)
:cOsdItem(Name)
{
pluginIndex = Index;
}
// --- cMenuSetupPlugins -----------------------------------------------------
class cMenuSetupPlugins : public cMenuSetupBase {
public:
cMenuSetupPlugins(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuSetupPlugins::cMenuSetupPlugins(void)
{
SetSection(tr("Plugins"));
SetHasHotkeys();
for (int i = 0; ; i++) {
cPlugin *p = cPluginManager::GetPlugin(i);
if (p) {
char *buffer = NULL;
asprintf(&buffer, "%s (%s) - %s", p->Name(), p->Version(), p->Description());
Add(new cMenuSetupPluginItem(hk(buffer), i));
free(buffer);
}
else
break;
}
}
eOSState cMenuSetupPlugins::ProcessKey(eKeys Key)
{
eOSState state = HasSubMenu() ? cMenuSetupBase::ProcessKey(Key) : cOsdMenu::ProcessKey(Key);
if (Key == kOk) {
if (state == osUnknown) {
cMenuSetupPluginItem *item = (cMenuSetupPluginItem *)Get(Current());
if (item) {
cPlugin *p = cPluginManager::GetPlugin(item->PluginIndex());
if (p) {
cMenuSetupPage *menu = p->SetupMenu();
if (menu) {
menu->SetPlugin(p);
return AddSubMenu(menu);
}
Skins.Message(mtInfo, tr("This plugin has no setup parameters!"));
}
}
}
else if (state == osContinue)
Store();
}
return state;
}
// --- cMenuSetup ------------------------------------------------------------
class cMenuSetup : public cOsdMenu {
private:
virtual void Set(void);
eOSState Restart(void);
public:
cMenuSetup(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuSetup::cMenuSetup(void)
:cOsdMenu("")
{
Set();
}
void cMenuSetup::Set(void)
{
Clear();
char buffer[64];
snprintf(buffer, sizeof(buffer), "%s - VDR %s", tr("Setup"), VDRVERSION);
SetTitle(buffer);
SetHasHotkeys();
Add(new cOsdItem(hk(tr("OSD")), osUser1));
Add(new cOsdItem(hk(tr("EPG")), osUser2));
Add(new cOsdItem(hk(tr("DVB")), osUser3));
Add(new cOsdItem(hk(tr("LNB")), osUser4));
Add(new cOsdItem(hk(tr("CICAM")), osUser5));
Add(new cOsdItem(hk(tr("Recording")), osUser6));
Add(new cOsdItem(hk(tr("Replay")), osUser7));
Add(new cOsdItem(hk(tr("Miscellaneous")), osUser8));
if (cPluginManager::HasPlugins())
Add(new cOsdItem(hk(tr("Plugins")), osUser9));
Add(new cOsdItem(hk(tr("Restart")), osUser10));
}
eOSState cMenuSetup::Restart(void)
{
if (Interface->Confirm(cRecordControls::Active() ? tr("Recording - restart anyway?") : tr("Really restart?"))) {
cThread::EmergencyExit(true);
return osEnd;
}
return osContinue;
}
eOSState cMenuSetup::ProcessKey(eKeys Key)
{
int osdLanguage = Setup.OSDLanguage;
eOSState state = cOsdMenu::ProcessKey(Key);
switch (state) {
case osUser1: return AddSubMenu(new cMenuSetupOSD);
case osUser2: return AddSubMenu(new cMenuSetupEPG);
case osUser3: return AddSubMenu(new cMenuSetupDVB);
case osUser4: return AddSubMenu(new cMenuSetupLNB);
case osUser5: return AddSubMenu(new cMenuSetupCICAM);
case osUser6: return AddSubMenu(new cMenuSetupRecord);
case osUser7: return AddSubMenu(new cMenuSetupReplay);
case osUser8: return AddSubMenu(new cMenuSetupMisc);
case osUser9: return AddSubMenu(new cMenuSetupPlugins);
case osUser10: return Restart();
default: ;
}
if (Setup.OSDLanguage != osdLanguage) {
Set();
if (!HasSubMenu())
Display();
}
return state;
}
// --- cMenuPluginItem -------------------------------------------------------
class cMenuPluginItem : public cOsdItem {
private:
int pluginIndex;
public:
cMenuPluginItem(const char *Name, int Index);
int PluginIndex(void) { return pluginIndex; }
};
cMenuPluginItem::cMenuPluginItem(const char *Name, int Index)
:cOsdItem(Name, osPlugin)
{
pluginIndex = Index;
}
// --- cMenuMain -------------------------------------------------------------
#define STOP_RECORDING tr(" Stop recording ")
cOsdObject *cMenuMain::pluginOsdObject = NULL;
cMenuMain::cMenuMain(eOSState State)
:cOsdMenu("")
{
lastDiskSpaceCheck = 0;
lastFreeMB = 0;
replaying = false;
stopReplayItem = NULL;
cancelEditingItem = NULL;
stopRecordingItem = NULL;
recordControlsState = 0;
Set();
// Initial submenus:
switch (State) {
case osSchedule: AddSubMenu(new cMenuSchedule); break;
case osChannels: AddSubMenu(new cMenuChannels); break;
case osTimers: AddSubMenu(new cMenuTimers); break;
case osRecordings: AddSubMenu(new cMenuRecordings(NULL, 0, true)); break;
case osSetup: AddSubMenu(new cMenuSetup); break;
case osCommands: AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); break;
default: break;
}
}
cOsdObject *cMenuMain::PluginOsdObject(void)
{
cOsdObject *o = pluginOsdObject;
pluginOsdObject = NULL;
return o;
}
void cMenuMain::Set(void)
{
Clear();
SetTitle("VDR");
SetHasHotkeys();
// Basic menu items:
Add(new cOsdItem(hk(tr("Schedule")), osSchedule));
Add(new cOsdItem(hk(tr("Channels")), osChannels));
Add(new cOsdItem(hk(tr("Timers")), osTimers));
Add(new cOsdItem(hk(tr("Recordings")), osRecordings));
// Plugins:
for (int i = 0; ; i++) {
cPlugin *p = cPluginManager::GetPlugin(i);
if (p) {
const char *item = p->MainMenuEntry();
if (item)
Add(new cMenuPluginItem(hk(item), i));
}
else
break;
}
// More basic menu items:
Add(new cOsdItem(hk(tr("Setup")), osSetup));
if (Commands.Count())
Add(new cOsdItem(hk(tr("Commands")), osCommands));
Update(true);
Display();
}
#define MB_PER_MINUTE 25.75 // this is just an estimate!
bool cMenuMain::Update(bool Force)
{
bool result = false;
// Title with disk usage:
if (Force || time(NULL) - lastDiskSpaceCheck > DISKSPACECHEK) {
int FreeMB;
int Percent = VideoDiskSpace(&FreeMB);
if (Force || FreeMB != lastFreeMB) {
int Minutes = int(double(FreeMB) / MB_PER_MINUTE);
int Hours = Minutes / 60;
Minutes %= 60;
char buffer[40];
snprintf(buffer, sizeof(buffer), "%s - %s %d%% - %2d:%02d %s", tr("VDR"), tr("Disk"), Percent, Hours, Minutes, tr("free"));
//XXX -> skin function!!!
SetTitle(buffer);
result = true;
}
lastDiskSpaceCheck = time(NULL);
}
bool NewReplaying = cControl::Control() != NULL;
if (Force || NewReplaying != replaying) {
replaying = NewReplaying;
// Replay control:
if (replaying && !stopReplayItem)
Add(stopReplayItem = new cOsdItem(tr(" Stop replaying"), osStopReplay));
else if (stopReplayItem && !replaying) {
Del(stopReplayItem->Index());
stopReplayItem = NULL;
}
// Color buttons:
SetHelp(!replaying ? tr("Button$Record") : NULL, tr("Button$Audio"), replaying ? NULL : tr("Button$Pause"), replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Button$Resume") : NULL);
result = true;
}
// Editing control:
bool CutterActive = cCutter::Active();
if (CutterActive && !cancelEditingItem) {
Add(cancelEditingItem = new cOsdItem(tr(" Cancel editing"), osCancelEdit));
result = true;
}
else if (cancelEditingItem && !CutterActive) {
Del(cancelEditingItem->Index());
cancelEditingItem = NULL;
result = true;
}
// Record control:
if (cRecordControls::StateChanged(recordControlsState)) {
while (stopRecordingItem) {
cOsdItem *it = Next(stopRecordingItem);
Del(stopRecordingItem->Index());
stopRecordingItem = it;
}
const char *s = NULL;
while ((s = cRecordControls::GetInstantId(s)) != NULL) {
char *buffer = NULL;
asprintf(&buffer, "%s%s", STOP_RECORDING, s);
cOsdItem *item = new cOsdItem(osStopRecord);
item->SetText(buffer, false);
Add(item);
if (!stopRecordingItem)
stopRecordingItem = item;
}
result = true;
}
return result;
}
eOSState cMenuMain::ProcessKey(eKeys Key)
{
bool HadSubMenu = HasSubMenu();
int osdLanguage = Setup.OSDLanguage;
eOSState state = cOsdMenu::ProcessKey(Key);
HadSubMenu |= HasSubMenu();
switch (state) {
case osSchedule: return AddSubMenu(new cMenuSchedule);
case osChannels: return AddSubMenu(new cMenuChannels);
case osTimers: return AddSubMenu(new cMenuTimers);
case osRecordings: return AddSubMenu(new cMenuRecordings);
case osSetup: return AddSubMenu(new cMenuSetup);
case osCommands: return AddSubMenu(new cMenuCommands(tr("Commands"), &Commands));
case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) {
cOsdItem *item = Get(Current());
if (item) {
cRecordControls::Stop(item->Text() + strlen(STOP_RECORDING));
return osEnd;
}
}
break;
case osCancelEdit: if (Interface->Confirm(tr("Cancel editing?"))) {
cCutter::Stop();
return osEnd;
}
break;
case osPlugin: {
cMenuPluginItem *item = (cMenuPluginItem *)Get(Current());
if (item) {
cPlugin *p = cPluginManager::GetPlugin(item->PluginIndex());
if (p) {
cOsdObject *menu = p->MainMenuAction();
if (menu) {
if (menu->IsMenu())
return AddSubMenu((cOsdMenu *)menu);
else {
pluginOsdObject = menu;
return osPlugin;
}
}
}
}
state = osEnd;
}
break;
default: switch (Key) {
case kRecord:
case kRed: if (!HadSubMenu)
state = replaying ? osContinue : osRecord;
break;
case kGreen: if (!HadSubMenu) {
cRemote::Put(kAudio, true);
state = osEnd;
}
break;
case kYellow: if (!HadSubMenu)
state = replaying ? osContinue : osPause;
break;
case kBlue: if (!HadSubMenu)
state = replaying ? osStopReplay : cReplayControl::LastReplayed() ? osReplay : osContinue;
break;
default: break;
}
}
if (!HasSubMenu() && Update(HadSubMenu))
Display();
if (Key != kNone) {
if (Setup.OSDLanguage != osdLanguage) {
Set();
if (!HasSubMenu())
Display();
}
}
return state;
}
// --- SetTrackDescriptions --------------------------------------------------
static void SetTrackDescriptions(bool Live)
{
cDevice::PrimaryDevice()->ClrAvailableTracks(true);
const cComponents *Components = NULL;
cSchedulesLock SchedulesLock;
if (Live) {
cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel());
if (Channel) {
const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
if (Schedules) {
const cSchedule *Schedule = Schedules->GetSchedule(Channel->GetChannelID());
if (Schedule) {
const cEvent *Present = Schedule->GetPresentEvent(true);
if (Present)
Components = Present->Components();
}
}
}
}
else if (cReplayControl::LastReplayed()) {
cThreadLock RecordingsLock(&Recordings);
cRecording *Recording = Recordings.GetByName(cReplayControl::LastReplayed());
if (Recording)
Components = Recording->Info()->Components();
}
if (Components) {
int indexAudio = 0;
int indexDolby = 0;
for (int i = 0; i < Components->NumComponents(); i++) {
const tComponent *p = Components->Component(i);
if (p->stream == 2) {
if (p->type == 0x05)
cDevice::PrimaryDevice()->SetAvailableTrack(ttDolby, indexDolby++, 0, NULL, p->description);
else
cDevice::PrimaryDevice()->SetAvailableTrack(ttAudio, indexAudio++, 0, NULL, p->description);
}
}
}
}
// --- cDisplayChannel -------------------------------------------------------
#define DIRECTCHANNELTIMEOUT 1000 //ms
cDisplayChannel::cDisplayChannel(int Number, bool Switched)
:cOsdObject(true)
{
group = -1;
withInfo = !Switched || Setup.ShowInfoOnChSwitch;
displayChannel = Skins.Current()->DisplayChannel(withInfo);
number = 0;
timeout = Switched || Setup.TimeoutRequChInfo;
channel = Channels.GetByNumber(Number);
lastPresent = lastFollowing = NULL;
if (channel) {
DisplayChannel();
DisplayInfo();
displayChannel->Flush();
}
lastTime.Set();
}
cDisplayChannel::cDisplayChannel(eKeys FirstKey)
:cOsdObject(true)
{
group = -1;
number = 0;
lastPresent = lastFollowing = NULL;
lastTime.Set();
withInfo = Setup.ShowInfoOnChSwitch;
displayChannel = Skins.Current()->DisplayChannel(withInfo);
ProcessKey(FirstKey);
}
cDisplayChannel::~cDisplayChannel()
{
delete displayChannel;
cStatus::MsgOsdClear();
}
void cDisplayChannel::DisplayChannel(void)
{
displayChannel->SetChannel(channel, number);
cStatus::MsgOsdChannel(ChannelString(channel, number));
lastPresent = lastFollowing = NULL;
}
void cDisplayChannel::DisplayInfo(void)
{
if (withInfo && channel) {
cSchedulesLock SchedulesLock;
const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
if (Schedules) {
const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
if (Schedule) {
const cEvent *Present = Schedule->GetPresentEvent(true);
const cEvent *Following = Schedule->GetFollowingEvent(true);
if (Present != lastPresent || Following != lastFollowing) {
SetTrackDescriptions(true);
displayChannel->SetEvents(Present, Following);
cStatus::MsgOsdProgramme(Present ? Present->StartTime() : 0, Present ? Present->Title() : NULL, Present ? Present->ShortText() : NULL, Following ? Following->StartTime() : 0, Following ? Following->Title() : NULL, Following ? Following->ShortText() : NULL);
lastPresent = Present;
lastFollowing = Following;
}
}
}
}
}
void cDisplayChannel::Refresh(void)
{
channel = Channels.GetByNumber(cDevice::CurrentChannel());
DisplayChannel();
displayChannel->SetEvents(NULL, NULL);
lastTime.Set();
}
eOSState cDisplayChannel::ProcessKey(eKeys Key)
{
switch (Key) {
case k0:
if (number == 0) {
// keep the "Toggle channels" function working
cRemote::Put(Key);
return osEnd;
}
case k1 ... k9:
if (number >= 0) {
number = number * 10 + Key - k0;
if (number > 0) {
channel = Channels.GetByNumber(number);
displayChannel->SetEvents(NULL, NULL);
withInfo = false;
DisplayChannel();
lastTime.Set();
// Lets see if there can be any useful further input:
int n = channel ? number * 10 : 0;
cChannel *ch = channel;
while (ch && (ch = Channels.Next(ch)) != NULL) {
if (!ch->GroupSep()) {
if (n <= ch->Number() && ch->Number() <= n + 9) {
n = 0;
break;
}
if (ch->Number() > n)
n *= 10;
}
}
if (n > 0) {
// This channel is the only one that fits the input, so let's take it right away:
displayChannel->Flush(); // makes sure the user sees his last input
Channels.SwitchTo(number);
return osEnd;
}
}
}
break;
case kLeft|k_Repeat:
case kLeft:
case kRight|k_Repeat:
case kRight:
withInfo = false;
number = 0;
if (group < 0) {
cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
if (channel)
group = channel->Index();
}
if (group >= 0) {
int SaveGroup = group;
if (NORMALKEY(Key) == kRight)
group = Channels.GetNextGroup(group) ;
else
group = Channels.GetPrevGroup(group < 1 ? 1 : group);
if (group < 0)
group = SaveGroup;
channel = Channels.Get(group);
if (channel) {
displayChannel->SetEvents(NULL, NULL);
DisplayChannel();
if (!channel->GroupSep())
group = -1;
}
}
lastTime.Set();
break;
case kUp|k_Repeat:
case kUp:
case kDown|k_Repeat:
case kDown:
cDevice::SwitchChannel(NORMALKEY(Key) == kUp ? 1 : -1);
// no break here
case kChanUp|k_Repeat:
case kChanUp:
case kChanDn|k_Repeat:
case kChanDn:
withInfo = true;
group = -1;
number = 0;
Refresh();
break;
case kNone:
if (number && lastTime.Elapsed() > DIRECTCHANNELTIMEOUT) {
if (Channels.GetByNumber(number))
Channels.SwitchTo(number);
else {
number = 0;
channel = NULL;
DisplayChannel();
lastTime.Set();
return osContinue;
}
return osEnd;
}
break;
//TODO
//XXX case kGreen: return osEventNow;
//XXX case kYellow: return osEventNext;
case kOk: if (group >= 0) {
channel = Channels.Get(Channels.GetNextNormal(group));
if (channel)
Channels.SwitchTo(channel->Number());
withInfo = true;
group = -1;
Refresh();
break;
}
else if (number > 0 && channel)
Channels.SwitchTo(number);
return osEnd;
default: if ((Key & (k_Repeat | k_Release)) == 0) {
cRemote::Put(Key);
return osEnd;
}
};
if (!timeout || lastTime.Elapsed() < (uint64)(Setup.ChannelInfoTime * 1000)) {
if (!number && group < 0 && channel && channel->Number() != cDevice::CurrentChannel())
Refresh(); // makes sure a channel switch through the SVDRP CHAN command is displayed
DisplayInfo();
displayChannel->Flush();
return osContinue;
}
return osEnd;
}
// --- cDisplayVolume --------------------------------------------------------
#define VOLUMETIMEOUT 1000 //ms
#define MUTETIMEOUT 5000 //ms
cDisplayVolume *cDisplayVolume::currentDisplayVolume = NULL;
cDisplayVolume::cDisplayVolume(void)
:cOsdObject(true)
{
currentDisplayVolume = this;
timeout.Set(cDevice::PrimaryDevice()->IsMute() ? MUTETIMEOUT : VOLUMETIMEOUT);
displayVolume = Skins.Current()->DisplayVolume();
Show();
}
cDisplayVolume::~cDisplayVolume()
{
delete displayVolume;
currentDisplayVolume = NULL;
}
void cDisplayVolume::Show(void)
{
displayVolume->SetVolume(cDevice::CurrentVolume(), MAXVOLUME, cDevice::PrimaryDevice()->IsMute());
}
cDisplayVolume *cDisplayVolume::Create(void)
{
if (!currentDisplayVolume)
new cDisplayVolume;
return currentDisplayVolume;
}
void cDisplayVolume::Process(eKeys Key)
{
if (currentDisplayVolume)
currentDisplayVolume->ProcessKey(Key);
}
eOSState cDisplayVolume::ProcessKey(eKeys Key)
{
switch (Key) {
case kVolUp|k_Repeat:
case kVolUp:
case kVolDn|k_Repeat:
case kVolDn:
Show();
timeout.Set(VOLUMETIMEOUT);
break;
case kMute:
if (cDevice::PrimaryDevice()->IsMute()) {
Show();
timeout.Set(MUTETIMEOUT);
}
else
timeout.Set();
break;
case kNone: break;
default: if ((Key & k_Release) == 0) {
cRemote::Put(Key);
return osEnd;
}
}
return timeout.TimedOut() ? osEnd : osContinue;
}
// --- cDisplayTracks --------------------------------------------------------
#define TRACKTIMEOUT 5000 //ms
cDisplayTracks *cDisplayTracks::currentDisplayTracks = NULL;
cDisplayTracks::cDisplayTracks(void)
:cOsdObject(true)
{
cDevice::PrimaryDevice()->EnsureAudioTrack();
SetTrackDescriptions(!cDevice::PrimaryDevice()->Replaying() || cTransferControl::ReceiverDevice());
currentDisplayTracks = this;
numTracks = track = 0;
audioChannel = cDevice::PrimaryDevice()->GetAudioChannel();
eTrackType CurrentAudioTrack = cDevice::PrimaryDevice()->GetCurrentAudioTrack();
for (int i = ttAudioFirst; i <= ttDolbyLast; i++) {
const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(i));
if (TrackId && TrackId->id) {
types[numTracks] = eTrackType(i);
descriptions[numTracks] = strdup(*TrackId->description ? TrackId->description : *TrackId->language ? TrackId->language : *itoa(i));
if (i == CurrentAudioTrack)
track = numTracks;
numTracks++;
}
}
timeout.Set(TRACKTIMEOUT);
displayTracks = Skins.Current()->DisplayTracks(tr("Button$Audio"), numTracks, descriptions);
Show();
}
cDisplayTracks::~cDisplayTracks()
{
delete displayTracks;
currentDisplayTracks = NULL;
for (int i = 0; i < numTracks; i++)
free(descriptions[i]);
cStatus::MsgOsdClear();
}
void cDisplayTracks::Show(void)
{
int ac = IS_AUDIO_TRACK(types[track]) ? audioChannel : -1;
displayTracks->SetTrack(track, descriptions);
displayTracks->SetAudioChannel(ac);
displayTracks->Flush();
cStatus::MsgSetAudioTrack(track, descriptions);
cStatus::MsgSetAudioChannel(ac);
}
cDisplayTracks *cDisplayTracks::Create(void)
{
if (cDevice::PrimaryDevice()->NumAudioTracks() > 0) {
if (!currentDisplayTracks)
new cDisplayTracks;
return currentDisplayTracks;
}
Skins.Message(mtWarning, tr("No audio available!"));
return NULL;
}
void cDisplayTracks::Process(eKeys Key)
{
if (currentDisplayTracks)
currentDisplayTracks->ProcessKey(Key);
}
eOSState cDisplayTracks::ProcessKey(eKeys Key)
{
int oldTrack = track;
int oldAudioChannel = audioChannel;
switch (Key) {
case kUp|k_Repeat:
case kUp:
case kDown|k_Repeat:
case kDown:
if (NORMALKEY(Key) == kUp && track > 0)
track--;
else if (NORMALKEY(Key) == kDown && track < numTracks - 1)
track++;
timeout.Set(TRACKTIMEOUT);
break;
case kLeft|k_Repeat:
case kLeft:
case kRight|k_Repeat:
case kRight: if (IS_AUDIO_TRACK(types[track])) {
static int ac[] = { 1, 0, 2 };
audioChannel = ac[cDevice::PrimaryDevice()->GetAudioChannel()];
if (NORMALKEY(Key) == kLeft && audioChannel > 0)
audioChannel--;
else if (NORMALKEY(Key) == kRight && audioChannel < 2)
audioChannel++;
audioChannel = ac[audioChannel];
timeout.Set(TRACKTIMEOUT);
}
break;
case kAudio|k_Repeat:
case kAudio:
if (++track >= numTracks)
track = 0;
timeout.Set(TRACKTIMEOUT);
break;
case kOk:
if (track != cDevice::PrimaryDevice()->GetCurrentAudioTrack())
oldTrack = -1; // make sure we explicitly switch to that track
timeout.Set();
break;
case kNone: break;
default: if ((Key & k_Release) == 0)
return osEnd;
}
if (track != oldTrack || audioChannel != oldAudioChannel)
Show();
if (track != oldTrack) {
cDevice::PrimaryDevice()->SetCurrentAudioTrack(types[track]);
Setup.CurrentDolby = IS_DOLBY_TRACK(types[track]);
}
if (audioChannel != oldAudioChannel)
cDevice::PrimaryDevice()->SetAudioChannel(audioChannel);
return timeout.TimedOut() ? osEnd : osContinue;
}
// --- cRecordControl --------------------------------------------------------
cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause)
{
// We're going to manipulate an event here, so we need to prevent
// others from modifying any EPG data:
cSchedulesLock SchedulesLock;
cSchedules::Schedules(SchedulesLock);
event = NULL;
instantId = NULL;
fileName = NULL;
recorder = NULL;
device = Device;
if (!device) device = cDevice::PrimaryDevice();//XXX
timer = Timer;
if (!timer) {
timer = new cTimer(true, Pause);
Timers.Add(timer);
Timers.SetModified();
asprintf(&instantId, cDevice::NumDevices() > 1 ? "%s - %d" : "%s", timer->Channel()->Name(), device->CardIndex() + 1);
}
timer->SetPending(true);
timer->SetRecording(true);
event = timer->Event();
if (event || GetEvent())
dsyslog("Title: '%s' Subtitle: '%s'", event->Title(), event->ShortText());
cRecording Recording(timer, event);
fileName = strdup(Recording.FileName());
// crude attempt to avoid duplicate recordings:
if (cRecordControls::GetRecordControl(fileName)) {
isyslog("already recording: '%s'", fileName);
if (Timer) {
timer->SetPending(false);
timer->SetRecording(false);
timer->OnOff();
}
else {
Timers.Del(timer);
Timers.SetModified();
if (!cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo()
cReplayControl::SetRecording(fileName, Recording.Name());
}
timer = NULL;
return;
}
cRecordingUserCommand::InvokeCommand(RUC_BEFORERECORDING, fileName);
isyslog("record %s", fileName);
if (MakeDirs(fileName, true)) {
const cChannel *ch = timer->Channel();
recorder = new cRecorder(fileName, ch->Ca(), timer->Priority(), ch->Vpid(), ch->Apids(), ch->Dpids(), ch->Spids());
if (device->AttachReceiver(recorder)) {
Recording.WriteInfo();
cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true);
if (!Timer && !cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo()
cReplayControl::SetRecording(fileName, Recording.Name());
Recordings.AddByName(fileName);
return;
}
else
DELETENULL(recorder);
}
if (!Timer) {
Timers.Del(timer);
Timers.SetModified();
timer = NULL;
}
}
cRecordControl::~cRecordControl()
{
Stop();
free(instantId);
free(fileName);
}
#define INSTANT_REC_EPG_LOOKAHEAD 300 // seconds to look into the EPG data for an instant recording
bool cRecordControl::GetEvent(void)
{
const cChannel *channel = timer->Channel();
time_t Time = timer->HasFlags(tfInstant) ? timer->StartTime() + INSTANT_REC_EPG_LOOKAHEAD : timer->StartTime() + (timer->StopTime() - timer->StartTime()) / 2;
for (int seconds = 0; seconds <= MAXWAIT4EPGINFO; seconds++) {
{
cSchedulesLock SchedulesLock;
const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
if (Schedules) {
const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
if (Schedule) {
event = Schedule->GetEventAround(Time);
if (event) {
if (seconds > 0)
dsyslog("got EPG info after %d seconds", seconds);
return true;
}
}
}
}
if (seconds == 0)
dsyslog("waiting for EPG info...");
sleep(1);
}
dsyslog("no EPG info available");
return false;
}
void cRecordControl::Stop(void)
{
if (timer) {
DELETENULL(recorder);
timer->SetRecording(false);
timer = NULL;
cStatus::MsgRecording(device, NULL, fileName, false);
cRecordingUserCommand::InvokeCommand(RUC_AFTERRECORDING, fileName);
}
}
bool cRecordControl::Process(time_t t)
{
if (!recorder || !timer || !timer->Matches(t))
return false;
AssertFreeDiskSpace(timer->Priority());
return true;
}
// --- cRecordControls -------------------------------------------------------
cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL };
int cRecordControls::state = 0;
bool cRecordControls::Start(cTimer *Timer, bool Pause)
{
ChangeState();
int ch = Timer ? Timer->Channel()->Number() : cDevice::CurrentChannel();
cChannel *channel = Channels.GetByNumber(ch);
if (channel) {
bool NeedsDetachReceivers = false;
int Priority = Timer ? Timer->Priority() : Pause ? Setup.PausePriority : Setup.DefaultPriority;
cDevice *device = cDevice::GetDevice(channel, Priority, &NeedsDetachReceivers);
if (device) {
if (NeedsDetachReceivers) {
Stop(device);
if (device == cTransferControl::ReceiverDevice())
cControl::Shutdown(); // in case this device was used for Transfer Mode
}
dsyslog("switching device %d to channel %d", device->DeviceNumber() + 1, channel->Number());
if (!device->SwitchChannel(channel, false)) {
cThread::EmergencyExit(true);
return false;
}
if (!Timer || Timer->Matches()) {
for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (!RecordControls[i]) {
RecordControls[i] = new cRecordControl(device, Timer, Pause);
return RecordControls[i]->Process(time(NULL));
}
}
}
}
else if (!Timer || (Timer->Priority() >= Setup.PrimaryLimit && !Timer->Pending()))
isyslog("no free DVB device to record channel %d!", ch);
}
else
esyslog("ERROR: channel %d not defined!", ch);
return false;
}
void cRecordControls::Stop(const char *InstantId)
{
ChangeState();
for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i]) {
const char *id = RecordControls[i]->InstantId();
if (id && strcmp(id, InstantId) == 0) {
cTimer *timer = RecordControls[i]->Timer();
RecordControls[i]->Stop();
if (timer) {
isyslog("deleting timer %s", *timer->ToDescr());
Timers.Del(timer);
Timers.SetModified();
}
break;
}
}
}
}
void cRecordControls::Stop(cDevice *Device)
{
ChangeState();
for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i]) {
if (RecordControls[i]->Device() == Device) {
isyslog("stopping recording on DVB device %d due to higher priority", Device->CardIndex() + 1);
RecordControls[i]->Stop();
}
}
}
}
bool cRecordControls::PauseLiveVideo(void)
{
Skins.Message(mtStatus, tr("Pausing live video..."));
cReplayControl::SetRecording(NULL, NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed()
if (Start(NULL, true)) {
sleep(2); // allow recorded file to fill up enough to start replaying
cReplayControl *rc = new cReplayControl;
cControl::Launch(rc);
cControl::Attach();
sleep(1); // allow device to replay some frames, so we have a picture
Skins.Message(mtStatus, NULL);
rc->ProcessKey(kPause); // pause, allowing replay mode display
return true;
}
Skins.Message(mtStatus, NULL);
return false;
}
const char *cRecordControls::GetInstantId(const char *LastInstantId)
{
for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i]) {
if (!LastInstantId && RecordControls[i]->InstantId())
return RecordControls[i]->InstantId();
if (LastInstantId && LastInstantId == RecordControls[i]->InstantId())
LastInstantId = NULL;
}
}
return NULL;
}
cRecordControl *cRecordControls::GetRecordControl(const char *FileName)
{
for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i] && strcmp(RecordControls[i]->FileName(), FileName) == 0)
return RecordControls[i];
}
return NULL;
}
void cRecordControls::Process(time_t t)
{
for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i]) {
if (!RecordControls[i]->Process(t)) {
DELETENULL(RecordControls[i]);
ChangeState();
}
}
}
}
void cRecordControls::ChannelDataModified(cChannel *Channel)
{
for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i]) {
if (RecordControls[i]->Timer() && RecordControls[i]->Timer()->Channel() == Channel) {
if (RecordControls[i]->Device()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
isyslog("stopping recording due to modification of channel %d", Channel->Number());
RecordControls[i]->Stop();
// This will restart the recording, maybe even from a different
// device in case conditional access has changed.
ChangeState();
}
}
}
}
}
bool cRecordControls::Active(void)
{
for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i])
return true;
}
return false;
}
void cRecordControls::Shutdown(void)
{
for (int i = 0; i < MAXRECORDCONTROLS; i++)
DELETENULL(RecordControls[i]);
ChangeState();
}
bool cRecordControls::StateChanged(int &State)
{
int NewState = state;
bool Result = State != NewState;
State = state;
return Result;
}
// --- cReplayControl --------------------------------------------------------
char *cReplayControl::fileName = NULL;
char *cReplayControl::title = NULL;
cReplayControl::cReplayControl(void)
:cDvbPlayerControl(fileName)
{
displayReplay = NULL;
visible = modeOnly = shown = displayFrames = false;
lastCurrent = lastTotal = -1;
lastPlay = lastForward = false;
lastSpeed = -1;
timeoutShow = 0;
timeSearchActive = false;
marks.Load(fileName);
cRecording Recording(fileName);
cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true);
}
cReplayControl::~cReplayControl()
{
Hide();
cStatus::MsgReplaying(this, NULL, fileName, false);
Stop();
}
void cReplayControl::SetRecording(const char *FileName, const char *Title)
{
free(fileName);
free(title);
fileName = FileName ? strdup(FileName) : NULL;
title = Title ? strdup(Title) : NULL;
}
const char *cReplayControl::LastReplayed(void)
{
return fileName;
}
void cReplayControl::ClearLastReplayed(const char *FileName)
{
if (fileName && FileName && strcmp(fileName, FileName) == 0) {
free(fileName);
fileName = NULL;
}
}
void cReplayControl::ShowTimed(int Seconds)
{
if (modeOnly)
Hide();
if (!visible) {
shown = ShowProgress(true);
timeoutShow = (shown && Seconds > 0) ? time(NULL) + Seconds : 0;
}
}
void cReplayControl::Show(void)
{
ShowTimed();
}
void cReplayControl::Hide(void)
{
if (visible) {
delete displayReplay;
displayReplay = NULL;
needsFastResponse = visible = false;
modeOnly = false;
lastPlay = lastForward = false;
lastSpeed = -1;
timeSearchActive = false;
}
}
void cReplayControl::ShowMode(void)
{
if (visible || Setup.ShowReplayMode && !cOsd::IsOpen()) {
bool Play, Forward;
int Speed;
if (GetReplayMode(Play, Forward, Speed) && (!visible || Play != lastPlay || Forward != lastForward || Speed != lastSpeed)) {
bool NormalPlay = (Play && Speed == -1);
if (!visible) {
if (NormalPlay)
return; // no need to do indicate ">" unless there was a different mode displayed before
visible = modeOnly = true;
displayReplay = Skins.Current()->DisplayReplay(modeOnly);
}
if (modeOnly && !timeoutShow && NormalPlay)
timeoutShow = time(NULL) + MODETIMEOUT;
displayReplay->SetMode(Play, Forward, Speed);
lastPlay = Play;
lastForward = Forward;
lastSpeed = Speed;
}
}
}
bool cReplayControl::ShowProgress(bool Initial)
{
int Current, Total;
if (GetIndex(Current, Total) && Total > 0) {
if (!visible) {
displayReplay = Skins.Current()->DisplayReplay(modeOnly);
displayReplay->SetMarks(&marks);
needsFastResponse = visible = true;
}
if (Initial) {
if (title)
displayReplay->SetTitle(title);
lastCurrent = lastTotal = -1;
}
if (Total != lastTotal) {
displayReplay->SetTotal(IndexToHMSF(Total));
if (!Initial)
displayReplay->Flush();
}
if (Current != lastCurrent || Total != lastTotal) {
displayReplay->SetProgress(Current, Total);
if (!Initial)
displayReplay->Flush();
displayReplay->SetCurrent(IndexToHMSF(Current, displayFrames));
displayReplay->Flush();
lastCurrent = Current;
}
lastTotal = Total;
ShowMode();
return true;
}
return false;
}
void cReplayControl::TimeSearchDisplay(void)
{
char buf[64];
strcpy(buf, tr("Jump: "));
int len = strlen(buf);
char h10 = '0' + (timeSearchTime >> 24);
char h1 = '0' + ((timeSearchTime & 0x00FF0000) >> 16);
char m10 = '0' + ((timeSearchTime & 0x0000FF00) >> 8);
char m1 = '0' + (timeSearchTime & 0x000000FF);
char ch10 = timeSearchPos > 3 ? h10 : '-';
char ch1 = timeSearchPos > 2 ? h1 : '-';
char cm10 = timeSearchPos > 1 ? m10 : '-';
char cm1 = timeSearchPos > 0 ? m1 : '-';
sprintf(buf + len, "%c%c:%c%c", ch10, ch1, cm10, cm1);
displayReplay->SetJump(buf);
}
void cReplayControl::TimeSearchProcess(eKeys Key)
{
#define STAY_SECONDS_OFF_END 10
int Seconds = (timeSearchTime >> 24) * 36000 + ((timeSearchTime & 0x00FF0000) >> 16) * 3600 + ((timeSearchTime & 0x0000FF00) >> 8) * 600 + (timeSearchTime & 0x000000FF) * 60;
int Current = (lastCurrent / FRAMESPERSEC);
int Total = (lastTotal / FRAMESPERSEC);
switch (Key) {
case k0 ... k9:
if (timeSearchPos < 4) {
timeSearchTime <<= 8;
timeSearchTime |= Key - k0;
timeSearchPos++;
TimeSearchDisplay();
}
break;
case kFastRew:
case kLeft:
case kFastFwd:
case kRight: {
int dir = ((Key == kRight || Key == kFastFwd) ? 1 : -1);
if (dir > 0)
Seconds = min(Total - Current - STAY_SECONDS_OFF_END, Seconds);
SkipSeconds(Seconds * dir);
timeSearchActive = false;
}
break;
case kPlay:
case kUp:
case kPause:
case kDown:
Seconds = min(Total - STAY_SECONDS_OFF_END, Seconds);
Goto(Seconds * FRAMESPERSEC, Key == kDown || Key == kPause);
timeSearchActive = false;
break;
default:
timeSearchActive = false;
break;
}
if (!timeSearchActive) {
if (timeSearchHide)
Hide();
else
displayReplay->SetJump(NULL);
ShowMode();
}
}
void cReplayControl::TimeSearch(void)
{
timeSearchTime = timeSearchPos = 0;
timeSearchHide = false;
if (modeOnly)
Hide();
if (!visible) {
Show();
if (visible)
timeSearchHide = true;
else
return;
}
timeoutShow = 0;
TimeSearchDisplay();
timeSearchActive = true;
}
void cReplayControl::MarkToggle(void)
{
int Current, Total;
if (GetIndex(Current, Total, true)) {
cMark *m = marks.Get(Current);
lastCurrent = -1; // triggers redisplay
if (m)
marks.Del(m);
else {
marks.Add(Current);
ShowTimed(2);
bool Play, Forward;
int Speed;
if (GetReplayMode(Play, Forward, Speed) && !Play)
Goto(Current, true);
}
marks.Save();
}
}
void cReplayControl::MarkJump(bool Forward)
{
if (marks.Count()) {
int Current, Total;
if (GetIndex(Current, Total)) {
cMark *m = Forward ? marks.GetNext(Current) : marks.GetPrev(Current);
if (m) {
Goto(m->position, true);
displayFrames = true;
}
}
}
}
void cReplayControl::MarkMove(bool Forward)
{
int Current, Total;
if (GetIndex(Current, Total)) {
cMark *m = marks.Get(Current);
if (m) {
displayFrames = true;
int p = SkipFrames(Forward ? 1 : -1);
cMark *m2;
if (Forward) {
if ((m2 = marks.Next(m)) != NULL && m2->position <= p)
return;
}
else {
if ((m2 = marks.Prev(m)) != NULL && m2->position >= p)
return;
}
Goto(m->position = p, true);
marks.Save();
}
}
}
void cReplayControl::EditCut(void)
{
if (fileName) {
Hide();
if (!cCutter::Active()) {
if (!marks.Count())
Skins.Message(mtError, tr("No editing marks defined!"));
else if (!cCutter::Start(fileName))
Skins.Message(mtError, tr("Can't start editing process!"));
else
Skins.Message(mtInfo, tr("Editing process started"));
}
else
Skins.Message(mtError, tr("Editing process already active!"));
ShowMode();
}
}
void cReplayControl::EditTest(void)
{
int Current, Total;
if (GetIndex(Current, Total)) {
cMark *m = marks.Get(Current);
if (!m)
m = marks.GetNext(Current);
if (m) {
if ((m->Index() & 0x01) != 0)
m = marks.Next(m);
if (m) {
Goto(m->position - SecondsToFrames(3));
Play();
}
}
}
}
cOsdObject *cReplayControl::GetInfo(void)
{
cRecording *Recording = Recordings.GetByName(cReplayControl::LastReplayed());
if (Recording)
return new cMenuRecording(Recording);
return NULL;
}
eOSState cReplayControl::ProcessKey(eKeys Key)
{
if (!Active())
return osEnd;
if (visible) {
if (timeoutShow && time(NULL) > timeoutShow) {
Hide();
ShowMode();
timeoutShow = 0;
}
else if (modeOnly)
ShowMode();
else
shown = ShowProgress(!shown) || shown;
}
bool DisplayedFrames = displayFrames;
displayFrames = false;
if (timeSearchActive && Key != kNone) {
TimeSearchProcess(Key);
return osContinue;
}
bool DoShowMode = true;
switch (Key) {
// Positioning:
case kPlay:
case kUp: Play(); break;
case kPause:
case kDown: Pause(); break;
case kFastRew|k_Release:
case kLeft|k_Release:
if (Setup.MultiSpeedMode) break;
case kFastRew:
case kLeft: Backward(); break;
case kFastFwd|k_Release:
case kRight|k_Release:
if (Setup.MultiSpeedMode) break;
case kFastFwd:
case kRight: Forward(); break;
case kRed: TimeSearch(); break;
case kGreen|k_Repeat:
case kGreen: SkipSeconds(-60); break;
case kYellow|k_Repeat:
case kYellow: SkipSeconds( 60); break;
case kStop:
case kBlue: Hide();
Stop();
return osEnd;
default: {
DoShowMode = false;
switch (Key) {
// Editing:
case kMarkToggle: MarkToggle(); break;
case kMarkJumpBack|k_Repeat:
case kMarkJumpBack: MarkJump(false); break;
case kMarkJumpForward|k_Repeat:
case kMarkJumpForward: MarkJump(true); break;
case kMarkMoveBack|k_Repeat:
case kMarkMoveBack: MarkMove(false); break;
case kMarkMoveForward|k_Repeat:
case kMarkMoveForward: MarkMove(true); break;
case kEditCut: EditCut(); break;
case kEditTest: EditTest(); break;
default: {
displayFrames = DisplayedFrames;
switch (Key) {
// Menu control:
case kOk: if (visible && !modeOnly) {
Hide();
DoShowMode = true;
}
else
Show();
break;
case kBack: return osRecordings;
default: return osUnknown;
}
}
}
}
}
if (DoShowMode)
ShowMode();
return osContinue;
}