Version 1.7.28
authorKlaus Schmidinger <Klaus (dot) Schmidinger (at) tvdr (dot) de>
Sun, 3 Jun 2012 10:44:00 +0000 (12:44 +0200)
committerDieter Hametner <dh (plus) vdr (at) gekrumbel (dot) de>
Sun, 3 Jun 2012 21:41:03 +0000 (23:41 +0200)
Original announce message:
VDR developer version 1.7.28 is now available at

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

A 'diff' against the previous version is available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.27-1.7.28.diff

MD5 checksums:

3ccff2dcc42d112e23dd64f2c39f02f1  vdr-1.7.28.tar.bz2
7249ead4aca4b24e53d49d11c67e1613  vdr-1.7.27-1.7.28.diff

WARNING:
========

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

The new default skin "LCARS" displays the signal strengths and qualities of
all devices in its main menu. For devices that have an stb0899 frontend chip
(like the TT-budget S2-3200) retrieving this information from the driver is
rather slow, which results in a sluggish response to user input in the main
menu. To speed this up you may want to apply the patches from

   ftp://ftp.tvdr.de/vdr/Developer/Driver-Patches

to the LinuxDVB driver source.

From the HISTORY file:
- Fixed cPixmapMemory::DrawEllipse() for quadrants -1 and -4.
- Fixed getting the maximum short channel name length in case there are no short names
  at all (reported by Derek Kelly).
- The new function cDevice::DeviceType() returns a string identifying the type of
  the given device.
- Now limiting the number of characters of a channel's (short) name to 16 in the
  schedules menus, to keep that column from getting overly wide in case there is
  a channel with a very long name that has no short name.
- Fixed EPG scan on systems with only a single DVB device that use software output
  (reported by Juergen Lock).
- Skins can now inquire the menu category for which their cSkinDisplayMenu is currently
  being used. This can be done either through a call to cSkinDisplayMenu::MenuCategory()
  or by reimplementing cSkinDisplayMenu::SetMenuCategory(). This information allows a
  skin to use special icons or decorations for the various types of menus in VDR.
- The new setup option "DVB/Standard compliance" can be used to switch between different
  variations of the DVB standard (thanks to Rolf Ahrenberg). Currently there is "DVB"
  (for the original DVB standard) and "ANSI/SCTE", which is used to properly handle
  certain private stream types.
- The disk usage is no longer automatically added to the title of the main and
  "Recordings" menus. This has always been a mekeshift solution and it is now up
  to the individual skin if, where and how it wants to display this information.
  A skin can use the new cVideoDiskUsage class to implement such a display. For
  compatibility, the default skins "Classic VDR", "ST:TNG Panels" and "Text mode"
  (i.e. curses) have been changed to behave like before. Other skins may want to
  display the disk usage in totally different ways.
- A cOsdMenu can now handle skins that display different numbers of items in the
  various menu categories.
- OSD and skin are now reinitialized after a plugin setup page has been confirmed,
  to have them react immediately in case any change to a plugin's setup parameter
  has an effect on the OSD.
- The Timers list is now marked as modified whenever a recording starts or ends.
- Fixed cDevice::StillPicture(), making sure it doesn't call the derived class's
  function if no buffer has been allocated (reported by Marcus Roscher).
- Fixed the SVDRP command UPDR, which didn't update the global recordings list
  (reported by Lars Hanisch).
- cControl::Control() now has an additional boolean parameter, which can be set to
  true to get the current player control even if it is hidden.
- The new functions cControl::GetRecording() and cControl::GetHeader() can be used
  to retrieve information about what the current player is playing.
- Fixed a possible high CPU load when pausing replay (thanks to Reinhard Nissl).
- Fixed character comparisons in cSubtitleObject::DecodeCharacterString() (reported
  by Reinhard Mantey).
- Renamed the function cString::sprintf(const char *fmt, va_list &ap) to vsprintf(),
  because it might inadvertently be called with a 'char *' as the second argument on
  some compilers and cause a crash (reported by Sundararaj Reel).
- Removed the "bondedMasterFailed" mechanism from cDvbTuner, because it caused
  problems with the EPG scan in case a transponder is not receivable in a setup with
  bonded devices (reported by Michael Schneider).
- Making sure setup strings don't contain any newline characters (thanks to Joachim
  Wilke).
- The new member function cSkinDisplayReplay::SetRecording() allows a skin to display
  more information about the currently played recording.
- Fixed a mismatched 'delete' in cSchedules::SetEpgDataFileName() (thanks to Reinhard
  Mantey).
- The DrawText() functions of the OSD now accept the new alignment flag taBorder,
  which triggers keeping a proper distance from the edge that taLeft or taRight
  aligns to.
- Fixed checking for UTF-8 support in cFont::Bidi() (reported by Torsten Lang).
- If a recording has no info file, the 'title' of the recording's info is now set
  to the recording's name.
- cVector::Clear() now reinitializes any previously used members.
- Fixed resetting CAMs (thanks to Marco Skambraks).
- The new function RgbShade() (include osd.h) can be used to generate a brighter or
  darker version of a given color.
- The new class cSortedTimers can be used to quickly get a list of all timers, sorted
  by their start time.
- The new skin "LCARS" is an enhanced version of the "ST:TNG" skin (which is still
  there in its original layout, for those who don't like the LCARS skin, or can't use
  it due to OSD limitations). The LCARS skin utilizes the new "menu category" feature
  to display additional information on the main menu page. It shows upcoming timers
  and the system's devices, as well as which device is recording which timers. The
  upper pane of the main menu displays the programme data in live and replay mode,
  and a progress bar. An indicator on the right side of the device list shows which
  device is currently used for live viewing, and whether it is in transfer mode.
  The individual device displays show the device number, the device type, which CAM
  (if any ) is currently assigned to the device, and the signal strength and quality.
  On the left side of the OSD there is a permanent display of the current date and
  time, the disk usage and the system load.
  "LCARS" is the new default skin of VDR. It requires at least a 4bpp (16 color) full
  screen OSD, but you can still operate it if your OSD can handle only fewer colors
  (in which case you may want to switch to the "ST:TNG" or "Classic VDR" skin).
- Finally removed the code marked with __RECORDING_H_DEPRECATED_DIRECT_MEMBER_ACCESS
  and LEGACY_CRECEIVER.
- Now making sure that the "small font" is never larger than the "osd font".
- Fixed font handling with fontconfig 2.9.0 or newer (thanks to Joerg Bornkessel).
- Extended the interface to the script that gets called for recordings, so that in
  the "edited" case it also provides the name of the original recording (thanks to
  Christian Richter).
- Added DeleteEvent() to the EPG handler interface, so that an EPG handler can trigger
  deleting of an event (thanks to Christian Kaiser).
- Speeded up opening menus on systems with many (several thousands) of recordings, by
  caching the information whether a recording is stored on the video directory file
  system within the cRecording data (based on a patch from Torsten Lang).

skinlcars.c [new file with mode: 0644]
skinlcars.h [new file with mode: 0644]

diff --git a/skinlcars.c b/skinlcars.c
new file mode 100644 (file)
index 0000000..a602923
--- /dev/null
@@ -0,0 +1,2114 @@
+/*
+ * skinlcars.c: A VDR skin with Star Trek's "LCARS" layout
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: skinlcars.c 2.2 2012/06/03 10:17:00 kls Exp $
+ */
+
+// "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures,
+// registered in the United States Patent and Trademark Office, all rights reserved.
+// The LCARS system is based upon the designs of Michael Okuda and his Okudagrams.
+//
+// "LCARS" is short for "Library Computer Access and Retrieval System".
+// Some resources used for writing this skin can be found at
+// http://www.lcars.org.uk
+// http://www.lcarsdeveloper.com
+// http://www.lcarscom.net
+// http://lds-jedi.deviantart.com/art/LCARS-Swept-Tutorial-213936938
+// http://lds-jedi.deviantart.com/art/LCARS-Button-Tutorial-210783437
+// http://zelldenver.deviantart.com/art/LCARS-Color-Standard-179565780
+// http://www.lcars47.com
+// http://www.bracercom.com/tutorial/content/CoherentLCARSInterface/LCARSCoherentInterface.html
+// http://www.bracercom.com/tutorial/content/lcars_manifesto/the_lcars_manifesto.html
+
+#include "skinlcars.h"
+#include "font.h"
+#include "menu.h"
+#include "osd.h"
+#include "themes.h"
+#include "videodir.h"
+
+#include "symbols/arrowdown.xpm"
+#include "symbols/arrowup.xpm"
+#include "symbols/audio.xpm"
+#include "symbols/audioleft.xpm"
+#include "symbols/audioright.xpm"
+#include "symbols/audiostereo.xpm"
+#include "symbols/dolbydigital.xpm"
+#include "symbols/encrypted.xpm"
+#include "symbols/ffwd.xpm"
+#include "symbols/ffwd1.xpm"
+#include "symbols/ffwd2.xpm"
+#include "symbols/ffwd3.xpm"
+#include "symbols/frew.xpm"
+#include "symbols/frew1.xpm"
+#include "symbols/frew2.xpm"
+#include "symbols/frew3.xpm"
+#include "symbols/mute.xpm"
+#include "symbols/pause.xpm"
+#include "symbols/play.xpm"
+#include "symbols/radio.xpm"
+#include "symbols/recording.xpm"
+#include "symbols/sfwd.xpm"
+#include "symbols/sfwd1.xpm"
+#include "symbols/sfwd2.xpm"
+#include "symbols/sfwd3.xpm"
+#include "symbols/srew.xpm"
+#include "symbols/srew1.xpm"
+#include "symbols/srew2.xpm"
+#include "symbols/srew3.xpm"
+#include "symbols/teletext.xpm"
+#include "symbols/volume.xpm"
+
+#define Gap            (Setup.FontOsdSize / 5 & ~1) // must be even
+#define TextFrame      (Setup.FontOsdSize / TEXT_ALIGN_BORDER)
+#define TextSpacing    (2 * TextFrame)
+#define SymbolSpacing  TextSpacing
+#define ShowSeenExtent (Setup.FontOsdSize / 5) // pixels by which the "seen" bar extends out of the frame
+
+#define DISKUSAGEALERTLIMIT 95 // percent of disk usage above which the display goes into alert mode
+#define SIGNALDISPLAYDELTA   2 // seconds between subsequent device signal displays
+
+static cTheme Theme;
+
+// Color domains:
+
+#define CLR_BACKGROUND      0x99000000
+#define CLR_MAIN_FRAME      0xFFF1DF6F
+#define CLR_CHANNEL_FRAME   0xFFFFCC99
+#define CLR_REPLAY_FRAME    0xFFCC6666
+#define CLR_DATE            0xFF99CCFF
+#define CLR_MENU_ITEMS      0xFFFFBC57
+#define CLR_TIMER           0xFF99CCFF
+#define CLR_DEVICE          0xFFF1B1AF
+#define CLR_CHANNEL_NAME    0xFF99CCFF
+#define CLR_EVENT_TITLE     0xFF99CCFF
+#define CLR_EVENT_TIME      0xFFFFCC66
+#define CLR_EVENT_SHORTTEXT 0xFFFFCC66
+#define CLR_TEXT            0xFF99CCFF
+#define CLR_TRACK           0xFFFFCC66
+#define CLR_SEEN            0xFFCC99CC
+#define CLR_ALERT           0xFFFF0000
+#define CLR_EXPOSED         0xFF990000
+#define CLR_WHITE           0xFFFFFFFF
+#define CLR_RED             0xFFB20000
+#define CLR_GREEN           0xFF00B200
+#define CLR_YELLOW          0xFFB2B200
+#define CLR_BLUE            0xFF0000B2
+#define CLR_BLACK           0xFF000000
+
+// General colors:
+
+THEME_CLR(Theme, clrBackground,             CLR_BACKGROUND);
+THEME_CLR(Theme, clrDateFg,                 CLR_BLACK);
+THEME_CLR(Theme, clrDateBg,                 CLR_DATE);
+THEME_CLR(Theme, clrTimerFg,                CLR_BLACK);
+THEME_CLR(Theme, clrTimerBg,                CLR_TIMER);
+THEME_CLR(Theme, clrDeviceFg,               CLR_BLACK);
+THEME_CLR(Theme, clrDeviceBg,               CLR_DEVICE);
+THEME_CLR(Theme, clrSignalValue,            CLR_GREEN);
+THEME_CLR(Theme, clrSignalRest,             CLR_RED);
+THEME_CLR(Theme, clrSeen,                   CLR_SEEN);
+THEME_CLR(Theme, clrTrackName,              CLR_TRACK);
+THEME_CLR(Theme, clrAlertFg,                CLR_WHITE);
+THEME_CLR(Theme, clrAlertBg,                CLR_ALERT);
+THEME_CLR(Theme, clrChannelName,            CLR_CHANNEL_NAME);
+THEME_CLR(Theme, clrEventTitle,             CLR_EVENT_TITLE);
+THEME_CLR(Theme, clrEventTime,              CLR_EVENT_TIME);
+THEME_CLR(Theme, clrEventShortText,         CLR_EVENT_SHORTTEXT);
+THEME_CLR(Theme, clrEventDescription,       CLR_TEXT);
+
+// Buttons:
+
+THEME_CLR(Theme, clrButtonRedFg,            CLR_WHITE);
+THEME_CLR(Theme, clrButtonRedBg,            CLR_RED);
+THEME_CLR(Theme, clrButtonGreenFg,          CLR_BLACK);
+THEME_CLR(Theme, clrButtonGreenBg,          CLR_GREEN);
+THEME_CLR(Theme, clrButtonYellowFg,         CLR_BLACK);
+THEME_CLR(Theme, clrButtonYellowBg,         CLR_YELLOW);
+THEME_CLR(Theme, clrButtonBlueFg,           CLR_WHITE);
+THEME_CLR(Theme, clrButtonBlueBg,           CLR_BLUE);
+
+// Messages:
+
+THEME_CLR(Theme, clrMessageStatusFg,        CLR_WHITE);
+THEME_CLR(Theme, clrMessageStatusBg,        CLR_BLUE);
+THEME_CLR(Theme, clrMessageInfoFg,          CLR_BLACK);
+THEME_CLR(Theme, clrMessageInfoBg,          CLR_GREEN);
+THEME_CLR(Theme, clrMessageWarningFg,       CLR_BLACK);
+THEME_CLR(Theme, clrMessageWarningBg,       CLR_YELLOW);
+THEME_CLR(Theme, clrMessageErrorFg,         CLR_WHITE);
+THEME_CLR(Theme, clrMessageErrorBg,         CLR_RED);
+
+// Volume:
+
+THEME_CLR(Theme, clrVolumeFrame,            CLR_MAIN_FRAME);
+THEME_CLR(Theme, clrVolumeSymbol,           CLR_BLACK);
+THEME_CLR(Theme, clrVolumeBarUpper,         RgbShade(CLR_MAIN_FRAME, -0.2));
+THEME_CLR(Theme, clrVolumeBarLower,         CLR_GREEN);
+
+// Channel display:
+
+THEME_CLR(Theme, clrChannelFrameFg,         CLR_BLACK);
+THEME_CLR(Theme, clrChannelFrameBg,         CLR_CHANNEL_FRAME);
+THEME_CLR(Theme, clrChannelSymbolOn,        CLR_BLACK);
+THEME_CLR(Theme, clrChannelSymbolOff,       RgbShade(CLR_CHANNEL_FRAME, -0.2));
+THEME_CLR(Theme, clrChannelSymbolRecFg,     CLR_WHITE);
+THEME_CLR(Theme, clrChannelSymbolRecBg,     CLR_RED);
+
+// Menu:
+
+THEME_CLR(Theme, clrMenuFrameFg,            CLR_BLACK);
+THEME_CLR(Theme, clrMenuFrameBg,            CLR_MAIN_FRAME);
+THEME_CLR(Theme, clrMenuTitle,              CLR_MAIN_FRAME);
+THEME_CLR(Theme, clrMenuMainBracket,        CLR_MENU_ITEMS);
+THEME_CLR(Theme, clrMenuTimerRecording,     CLR_DEVICE);
+THEME_CLR(Theme, clrMenuDeviceRecording,    CLR_TIMER);
+THEME_CLR(Theme, clrMenuItemCurrentFg,      CLR_BLACK);
+THEME_CLR(Theme, clrMenuItemCurrentBg,      CLR_MENU_ITEMS);
+THEME_CLR(Theme, clrMenuItemSelectable,     CLR_MENU_ITEMS);
+THEME_CLR(Theme, clrMenuItemNonSelectable,  CLR_TEXT);
+THEME_CLR(Theme, clrMenuScrollbarTotal,     CLR_MENU_ITEMS);
+THEME_CLR(Theme, clrMenuScrollbarShown,     CLR_SEEN);
+THEME_CLR(Theme, clrMenuScrollbarArrow,     CLR_BLACK);
+THEME_CLR(Theme, clrMenuText,               CLR_TEXT);
+
+// Replay display:
+
+THEME_CLR(Theme, clrReplayFrameFg,          CLR_BLACK);
+THEME_CLR(Theme, clrReplayFrameBg,          CLR_REPLAY_FRAME);
+THEME_CLR(Theme, clrReplayPosition,         CLR_SEEN);
+THEME_CLR(Theme, clrReplayJumpFg,           CLR_BLACK);
+THEME_CLR(Theme, clrReplayJumpBg,           CLR_SEEN);
+THEME_CLR(Theme, clrReplayProgressSeen,     CLR_SEEN);
+THEME_CLR(Theme, clrReplayProgressRest,     RgbShade(CLR_WHITE, -0.2));
+THEME_CLR(Theme, clrReplayProgressSelected, CLR_EXPOSED);
+THEME_CLR(Theme, clrReplayProgressMark,     CLR_BLACK);
+THEME_CLR(Theme, clrReplayProgressCurrent,  CLR_EXPOSED);
+
+// Track display:
+
+THEME_CLR(Theme, clrTrackFrameFg,           CLR_BLACK);
+THEME_CLR(Theme, clrTrackFrameBg,           CLR_TRACK);
+THEME_CLR(Theme, clrTrackItemFg,            CLR_BLACK);
+THEME_CLR(Theme, clrTrackItemBg,            RgbShade(CLR_TRACK, 0.5));
+THEME_CLR(Theme, clrTrackItemCurrentFg,     CLR_BLACK);
+THEME_CLR(Theme, clrTrackItemCurrentBg,     CLR_TRACK);
+
+// --- Helper functions ------------------------------------------------------
+
+static cOsd *CreateOsd(int Left, int Top, int x0, int y0, int x1, int y1)
+{
+  cOsd *Osd = cOsdProvider::NewOsd(Left, Top);
+  int Bpp[] = { 32, 8, 4, 2, 1 };
+  tArea Area = { x0, y0, x1, y1, 0 };
+  for (unsigned int i = 0; i < sizeof(Bpp) / sizeof(int); i++) {
+      Area.bpp = Bpp[i];
+      if (Osd->CanHandleAreas(&Area, 1) == oeOk) {
+         Osd->SetAreas(&Area, 1);
+         break;
+         }
+      }
+  return Osd;
+}
+
+static cFont *CreateTinyFont(int LineHeight)
+{
+  // Creates a font that is not higher than half of LineHeight.
+  LineHeight /= 2;
+  int Height = LineHeight;
+  for (;;) {
+      cFont *TinyFont = cFont::CreateFont(Setup.FontOsd, Height);
+      if (Height < 2 || TinyFont->Height() <= LineHeight)
+         return TinyFont;
+      delete TinyFont;
+      Height -= 1;
+      }
+}
+
+static bool DrawDeviceData(cOsd *Osd, const cDevice *Device, int x0, int y0, int x1, int y1, int &xs, const cFont *TinyFont, cString &LastDeviceType, cCamSlot *&LastCamSlot, bool Initial)
+{
+  cString DeviceType = Device->DeviceType();
+  cCamSlot *CamSlot = Device->CamSlot();
+  if (Initial || strcmp(DeviceType, LastDeviceType) || CamSlot != LastCamSlot) {
+     const cFont *font = cFont::GetFont(fontOsd);
+     tColor ColorFg = Theme.Color(clrDeviceFg);
+     tColor ColorBg = Theme.Color(clrDeviceBg);
+     Osd->DrawRectangle(x0, y0, x1 - 1, y1 - 1, ColorBg);
+     int x = x0;
+     // Device number:
+     cString Nr = itoa(Device->DeviceNumber() + 1);
+     int w = max(font->Width(Nr), y1 - y0);
+     Osd->DrawText(x, y0, Nr, ColorFg, ColorBg, font, w, y1 - y0, taCenter);
+     x += w;
+     // Device type:
+     Osd->DrawText(x, y0, DeviceType, ColorFg, ColorBg, TinyFont);
+     xs = max(xs, x + TinyFont->Width(DeviceType));
+     LastDeviceType = DeviceType;
+     // CAM:
+     if (CamSlot) {
+        cString s = cString::sprintf("CAM %d", CamSlot->SlotNumber());
+        Osd->DrawText(x, y1 - TinyFont->Height(), s, ColorFg, ColorBg, TinyFont);
+        xs = max(xs, x + TinyFont->Width(s));
+        }
+     LastCamSlot = CamSlot;
+     return true;
+     }
+  return false;
+}
+
+static void DrawDeviceSignal(cOsd *Osd, const cDevice *Device, int x0, int y0, int x1, int y1, int &LastSignalStrength, int &LastSignalQuality, bool Initial)
+{
+  int SignalStrength = Device->SignalStrength();
+  int SignalQuality = Device->SignalQuality();
+  int d = max((y1 - y0) / 10, 1);
+  int x00 = x0 + d;
+  int x01 = x1 - d;
+  int h = (y1 - y0 - 3 * d) / 2;
+  int w = x01 - x00;
+  int y00 = y0 + d;
+  int y01 = y00 + h;
+  int y03 = y1 - d;
+  int y02 = y03 - h;
+  if (SignalStrength >= 0 && (Initial || SignalStrength != LastSignalStrength)) {
+     int s = SignalStrength * w / 100;
+     Osd->DrawRectangle(x00, y00, x00 + s - 1, y01 - 1, Theme.Color(clrSignalValue));
+     Osd->DrawRectangle(x00 + s, y00, x01 - 1, y01 - 1, Theme.Color(clrSignalRest));
+     LastSignalStrength = SignalStrength;
+     }
+  if (SignalQuality >= 0 && (Initial || SignalQuality != LastSignalQuality)) {
+     int q = SignalQuality * w / 100;
+     Osd->DrawRectangle(x00, y02, x00 + q - 1, y03 - 1, Theme.Color(clrSignalValue));
+     Osd->DrawRectangle(x00 + q, y02, x01 - 1, y03 - 1, Theme.Color(clrSignalRest));
+     LastSignalQuality = SignalQuality;
+     }
+}
+
+// --- cSkinLCARSDisplayChannel ----------------------------------------------
+
+class cSkinLCARSDisplayChannel : public cSkinDisplayChannel {
+private:
+  cOsd *osd;
+  int xc00, xc01, xc02, xc03, xc04, xc05, xc06, xc07, xc08, xc09, xc10, xc11, xc12, xc13, xc14, xc15;
+  int yc00, yc01, yc02, yc03, yc04, yc05, yc06, yc07, yc08, yc09, yc10, yc11, yc12;
+  int xs; // starting column for signal display
+  bool withInfo;
+  int lineHeight;
+  cFont *tinyFont;
+  cFont *tallFont;
+  tColor frameColor;
+  bool message;
+  const cEvent *present;
+  bool initial;
+  cString lastDate;
+  int lastSeen;
+  int lastDeviceNumber;
+  cString lastDeviceType;
+  cCamSlot *lastCamSlot;
+  int lastSignalStrength;
+  int lastSignalQuality;
+  time_t lastSignalDisplay;
+  tTrackId lastTrackId;
+  static cBitmap bmTeletext, bmRadio, bmAudio, bmDolbyDigital, bmEncrypted, bmRecording;
+  void DrawDate(void);
+  void DrawTrack(void);
+  void DrawSeen(int Current, int Total);
+  void DrawDevice(void);
+  void DrawSignal(void);
+public:
+  cSkinLCARSDisplayChannel(bool WithInfo);
+  virtual ~cSkinLCARSDisplayChannel();
+  virtual void SetChannel(const cChannel *Channel, int Number);
+  virtual void SetEvents(const cEvent *Present, const cEvent *Following);
+  virtual void SetMessage(eMessageType Type, const char *Text);
+  virtual void Flush(void);
+  };
+
+cBitmap cSkinLCARSDisplayChannel::bmTeletext(teletext_xpm);
+cBitmap cSkinLCARSDisplayChannel::bmRadio(radio_xpm);
+cBitmap cSkinLCARSDisplayChannel::bmAudio(audio_xpm);
+cBitmap cSkinLCARSDisplayChannel::bmDolbyDigital(dolbydigital_xpm);
+cBitmap cSkinLCARSDisplayChannel::bmEncrypted(encrypted_xpm);
+cBitmap cSkinLCARSDisplayChannel::bmRecording(recording_xpm);
+
+cSkinLCARSDisplayChannel::cSkinLCARSDisplayChannel(bool WithInfo)
+{
+  tallFont = cFont::CreateFont(Setup.FontOsd, Setup.FontOsdSize * 1.8);
+  initial = true;
+  present = NULL;
+  lastSeen = -1;
+  lastDeviceNumber = -1;
+  lastCamSlot = NULL;
+  lastSignalStrength = -1;
+  lastSignalQuality = -1;
+  lastSignalDisplay = 0;
+  memset(&lastTrackId, 0, sizeof(lastTrackId));
+  const cFont *font = cFont::GetFont(fontOsd);
+  withInfo = WithInfo;
+  lineHeight = font->Height();
+  tinyFont = CreateTinyFont(lineHeight);
+  frameColor = Theme.Color(clrChannelFrameBg);
+  message = false;
+  int d = 5 * lineHeight;
+  xc00 = 0;
+  xc01 = xc00 + d / 2;
+  xc02 = xc00 + d;
+  xc03 = xc02 + lineHeight;
+  xc04 = xc02 + d / 4;
+  xc05 = xc02 + d;
+  xc06 = xc05 + Gap;
+  xc15 = cOsd::OsdWidth();
+  xc14 = xc15 - lineHeight;
+  xc13 = xc14 - Gap;
+  xc07 = (xc15 + xc00) / 2;
+  xc08 = xc07 + Gap;
+  xc09 = xc08 + lineHeight;
+  xc10 = xc09 + Gap;
+  xc11 = (xc10 + xc13 + Gap) / 2;
+  xc12 = xc11 + Gap;
+
+  yc00 = 0;
+  yc01 = yc00 + lineHeight;
+  yc02 = yc01 + lineHeight;
+  yc03 = yc02 + Gap;
+  yc04 = yc03 + 2 * lineHeight;
+  yc05 = yc04 + Gap;
+  yc06 = yc05 + 2 * lineHeight;
+
+  yc07 = yc06 + Gap;
+  yc12 = yc07 + 3 * lineHeight + Gap / 2;
+  yc11 = yc12 - lineHeight;
+  yc10 = yc11 - lineHeight;
+  yc09 = yc11 - d / 4;
+  yc08 = yc12 - d / 2;
+
+  xs = 0;
+
+  int y1 = withInfo ? yc12 : yc02;
+  int y0 = cOsd::OsdTop() + (Setup.ChannelInfoPos ? 0 : cOsd::OsdHeight() - y1);
+  osd = CreateOsd(cOsd::OsdLeft(), y0, xc00, yc00, xc15 - 1, y1 - 1);
+  osd->DrawRectangle(xc00, yc00, xc15 - 1, y1 - 1, Theme.Color(clrBackground));
+  // Rectangles:
+  osd->DrawRectangle(xc00, yc00, xc02 - 1, yc02 - 1, frameColor);
+  if (withInfo) {
+     osd->DrawRectangle(xc00, yc03, xc02 - 1, yc04 - 1, frameColor);
+     osd->DrawRectangle(xc00, yc05, xc02 - 1, yc06 - 1, frameColor);
+     // Elbow:
+     osd->DrawRectangle(xc00, yc07, xc01 - 1, yc08 - 1, frameColor);
+     osd->DrawRectangle(xc00, yc08, xc01 - 1, yc12 - 1, clrTransparent);
+     osd->DrawEllipse  (xc00, yc08, xc01 - 1, yc12 - 1, frameColor, 3);
+     osd->DrawRectangle(xc01, yc07, xc02 - 1, yc12 - 1, frameColor);
+     osd->DrawEllipse  (xc02, yc09, xc04 - 1, yc11 - 1, frameColor, -3);
+     osd->DrawRectangle(xc02, yc11, xc05 - 1, yc12 - 1, frameColor);
+     // Status area:
+     osd->DrawRectangle(xc06, yc11 + lineHeight / 2, xc07 - 1, yc12 - 1, frameColor);
+     osd->DrawRectangle(xc08, yc11, xc09 - 1, yc12 - 1, frameColor);
+     osd->DrawRectangle(xc10, yc11, xc11 - 1, yc12 - 1, Theme.Color(clrDeviceBg));
+     osd->DrawRectangle(xc12, yc11, xc13 - 1, yc12 - 1, Theme.Color(clrDateBg));
+     osd->DrawRectangle(xc14, yc11, xc14 + lineHeight / 2 - 1, yc12 - 1, frameColor);
+     osd->DrawRectangle(xc14 + lineHeight / 2, yc11 + lineHeight / 2, xc15 - 1, yc12 - 1, clrTransparent);
+     osd->DrawEllipse  (xc14 + lineHeight / 2, yc11, xc15 - 1, yc12 - 1, frameColor, 5);
+     }
+  // Icons:
+  osd->DrawRectangle(xc14, yc00, xc14 + lineHeight / 2 - 1, yc01 - 1, frameColor);
+  osd->DrawRectangle(xc14 + lineHeight / 2, yc00, xc15 - 1, yc00 + lineHeight / 2 - 1, clrTransparent);
+  osd->DrawEllipse  (xc14 + lineHeight / 2, yc00, xc15 - 1, yc01 - 1, frameColor, 5);
+}
+
+cSkinLCARSDisplayChannel::~cSkinLCARSDisplayChannel()
+{
+  delete tallFont;
+  delete tinyFont;
+  delete osd;
+}
+
+void cSkinLCARSDisplayChannel::DrawDate(void)
+{
+  cString s = DayDateTime();
+  if (initial || strcmp(s, lastDate)) {
+     osd->DrawText(xc12, yc11, s, Theme.Color(clrDateFg), Theme.Color(clrDateBg), cFont::GetFont(fontOsd), xc13 - xc12, lineHeight, taRight | taBorder);
+     lastDate = s;
+     }
+}
+
+void cSkinLCARSDisplayChannel::DrawTrack(void)
+{
+  cDevice *Device = cDevice::PrimaryDevice();
+  const tTrackId *Track = Device->GetTrack(Device->GetCurrentAudioTrack());
+  if (!Track && *lastTrackId.description || Track && strcmp(lastTrackId.description, Track->description)) {
+     osd->DrawText(xc03, yc07, Track ? Track->description : "", Theme.Color(clrTrackName), Theme.Color(clrBackground), cFont::GetFont(fontOsd), xc07 - xc03);
+     strn0cpy(lastTrackId.description, Track ? Track->description : "", sizeof(lastTrackId.description));
+     }
+}
+
+void cSkinLCARSDisplayChannel::DrawSeen(int Current, int Total)
+{
+  int Seen = min(xc07 - xc06, int((xc07 - xc06) * double(Current) / Total));
+  if (initial || Seen != lastSeen) {
+     int y0 = yc11 - ShowSeenExtent;
+     int y1 = yc11 + lineHeight / 2 - Gap / 2;
+     osd->DrawRectangle(xc06, y0, xc06 + Seen - 1, y1 - 1, Theme.Color(clrSeen));
+     osd->DrawRectangle(xc06 + Seen, y0, xc07 - 1, y1 - 1, Theme.Color(clrBackground));
+     lastSeen = Seen;
+     }
+}
+
+void cSkinLCARSDisplayChannel::DrawDevice(void)
+{
+  const cDevice *Device = cDevice::ActualDevice();
+  if (DrawDeviceData(osd, Device, xc10, yc11, xc11, yc12, xs, tinyFont, lastDeviceType, lastCamSlot, Device->DeviceNumber() != lastDeviceNumber)) {
+     lastDeviceNumber = Device->DeviceNumber();
+     // Make sure signal meters are redrawn:
+     lastSignalStrength = -1;
+     lastSignalQuality = -1;
+     lastSignalDisplay = 0;
+     }
+}
+
+void cSkinLCARSDisplayChannel::DrawSignal(void)
+{
+  time_t Now = time(NULL);
+  if (Now != lastSignalDisplay) {
+     DrawDeviceSignal(osd, cDevice::ActualDevice(), xs + lineHeight / 2, yc11, xc11, yc12, lastSignalStrength, lastSignalQuality, initial);
+     lastSignalDisplay = Now;
+     }
+}
+
+void cSkinLCARSDisplayChannel::SetChannel(const cChannel *Channel, int Number)
+{
+  int x = xc13;
+  int xi = x - SymbolSpacing -
+           bmRecording.Width() - SymbolSpacing -
+           bmEncrypted.Width() - SymbolSpacing -
+           bmDolbyDigital.Width() - SymbolSpacing -
+           bmAudio.Width() - SymbolSpacing -
+           max(bmTeletext.Width(), bmRadio.Width()) - SymbolSpacing;
+  osd->DrawRectangle(xi, yc00, xc13 - 1, yc01 - 1, frameColor);
+  if (Channel && !Channel->GroupSep()) {
+     bool rec = cRecordControls::Active();
+     x -= bmRecording.Width() + SymbolSpacing;
+     osd->DrawBitmap(x, yc00 + (yc01 - yc00 - bmRecording.Height()) / 2, bmRecording, Theme.Color(rec ? clrChannelSymbolRecFg : clrChannelSymbolOff), rec ? Theme.Color(clrChannelSymbolRecBg) : frameColor);
+     x -= bmEncrypted.Width() + SymbolSpacing;
+     osd->DrawBitmap(x, yc00 + (yc01 - yc00 - bmEncrypted.Height()) / 2, bmEncrypted, Theme.Color(Channel->Ca() ? clrChannelSymbolOn : clrChannelSymbolOff), frameColor);
+     x -= bmDolbyDigital.Width() + SymbolSpacing;
+     osd->DrawBitmap(x, yc00 + (yc01 - yc00 - bmDolbyDigital.Height()) / 2, bmDolbyDigital, Theme.Color(Channel->Dpid(0) ? clrChannelSymbolOn : clrChannelSymbolOff), frameColor);
+     x -= bmAudio.Width() + SymbolSpacing;
+     osd->DrawBitmap(x, yc00 + (yc01 - yc00 - bmAudio.Height()) / 2, bmAudio, Theme.Color(Channel->Apid(1) ? clrChannelSymbolOn : clrChannelSymbolOff), frameColor);
+     if (Channel->Vpid()) {
+        x -= bmTeletext.Width() + SymbolSpacing;
+        osd->DrawBitmap(x, yc00 + (yc01 - yc00 - bmTeletext.Height()) / 2, bmTeletext, Theme.Color(Channel->Tpid() ? clrChannelSymbolOn : clrChannelSymbolOff), frameColor);
+        }
+     else if (Channel->Apid(0)) {
+        x -= bmRadio.Width() + SymbolSpacing;
+        osd->DrawBitmap(x, yc00 + (yc01 - yc00 - bmRadio.Height()) / 2, bmRadio, Theme.Color(clrChannelSymbolOn), frameColor);
+        }
+     }
+  cString ChNumber("");
+  cString ChName("");
+  if (Channel) {
+     ChName = Channel->Name();
+     if (!Channel->GroupSep())
+        ChNumber = cString::sprintf("%d%s", Channel->Number(), Number ? "-" : "");
+     }
+  else if (Number)
+     ChNumber = cString::sprintf("%d-", Number);
+  else
+     ChName = ChannelString(NULL, NULL);
+  osd->DrawText(xc00, yc00, ChNumber, Theme.Color(clrChannelFrameFg), frameColor, tallFont, xc02 - xc00, yc02 - yc00, taTop | taRight | taBorder);
+  osd->DrawText(xc03, yc00, ChName, Theme.Color(clrChannelName), Theme.Color(clrBackground), tallFont, xi - xc03 - lineHeight, 0, taTop | taLeft);
+  lastSignalDisplay = 0;
+  if (withInfo)
+     DrawDevice();
+}
+
+void cSkinLCARSDisplayChannel::SetEvents(const cEvent *Present, const cEvent *Following)
+{
+  if (!withInfo)
+     return;
+  if (present != Present)
+     lastSeen = -1;
+  present = Present;
+  for (int i = 0; i < 2; i++) {
+      const cEvent *e = !i ? Present : Following;
+      int y = !i ? yc03 : yc05;
+      if (e) {
+         osd->DrawText(xc00, y, e->GetTimeString(), Theme.Color(clrChannelFrameFg), frameColor, cFont::GetFont(fontOsd), xc02 - xc00, 0, taRight | taBorder);
+         osd->DrawText(xc03, y, e->Title(), Theme.Color(clrEventTitle), Theme.Color(clrBackground), cFont::GetFont(fontOsd), xc13 - xc03);
+         osd->DrawText(xc03, y + lineHeight, e->ShortText(), Theme.Color(clrEventShortText), Theme.Color(clrBackground), cFont::GetFont(fontSml), xc13 - xc03);
+         }
+      else {
+         osd->DrawRectangle(xc00, y, xc02 - 1, y + lineHeight, frameColor);
+         osd->DrawRectangle(xc02, y, xc13 - 1, y + 2 * lineHeight, Theme.Color(clrBackground));
+         }
+      }
+}
+
+void cSkinLCARSDisplayChannel::SetMessage(eMessageType Type, const char *Text)
+{
+  if (Text) {
+     int y0 = yc11 - ShowSeenExtent;
+     int y1 = yc11;
+     osd->SaveRegion(xc06, y0, xc13 - 1, yc12 - 1);
+     osd->DrawRectangle(xc06, y0, xc07, y1 - 1, Theme.Color(clrBackground)); // clears the "seen" bar
+     osd->DrawText(xc06, yc11, Text, Theme.Color(clrMessageStatusFg + 2 * Type), Theme.Color(clrMessageStatusBg + 2 * Type), cFont::GetFont(fontSml), xc13 - xc06, yc12 - yc11, taCenter);
+     }
+  else
+     osd->RestoreRegion();
+}
+
+void cSkinLCARSDisplayChannel::Flush(void)
+{
+  if (withInfo) {
+     if (!message) {
+        DrawDate();
+        DrawTrack();
+        DrawDevice();
+        DrawSignal();
+        }
+     int Current = 0;
+     int Total = 0;
+     if (present) {
+        time_t t = time(NULL);
+        if (t > present->StartTime())
+           Current = t - present->StartTime();
+        Total = present->Duration();
+        }
+     DrawSeen(Current, Total);
+     }
+  osd->Flush();
+  initial = false;
+}
+
+// --- cSkinLCARSDisplayMenu -------------------------------------------------
+
+class cSkinLCARSDisplayMenu : public cSkinDisplayMenu {
+private:
+  cOsd *osd;
+  int xa00, xa01, xa02, xa03, xa04, xa05, xa06, xa07, xa08, xa09;
+  int yt00, yt01, yt02, yt03, yt04, yt05, yt06;
+  int yc00, yc01, yc02, yc03, yc04, yc05, yc06, yc07, yc08, yc09, yc10, yc11;
+  int yb00, yb01, yb02, yb03, yb04, yb05, yb06, yb07, yb08, yb09, yb10, yb11, yb12, yb13, yb14, yb15;
+  int xm00, xm01, xm02, xm03, xm04, xm05, xm06, xm07, xm08;
+  int ym00, ym01, ym02, ym03, ym04, ym05, ym06, ym07;
+  int xs00, xs01, xs02, xs03, xs04, xs05, xs06, xs07, xs08, xs09, xs10, xs11, xs12, xs13;
+  int ys00, ys01, ys02, ys03, ys04, ys05;
+  int xi00, xi01, xi02, xi03;
+  int yi00, yi01;
+  int xb00, xb01, xb02, xb03, xb04, xb05, xb06, xb07, xb08, xb09, xb10, xb11, xb12, xb13, xb14, xb15;
+  int xd00, xd01, xd02, xd03, xd04, xd05, xd06, xd07;
+  int yd00, yd01, yd02, yd03, yd04, yd05;
+  int xs; // starting column for signal display
+  int lineHeight;
+  cFont *tinyFont;
+  cFont *tallFont;
+  tColor frameColor;
+  int currentIndex;
+  cVector<int> deviceOffset;
+  cVector<bool> deviceRecording;
+  cString lastDeviceType[MAXDEVICES];
+  cVector<cCamSlot *> lastCamSlot;
+  cVector<int> lastSignalStrength;
+  cVector<int> lastSignalQuality;
+  bool initial;
+  enum eCurrentMode { cmUnknown, cmLive, cmPlay };
+  eCurrentMode lastMode;
+  cString lastDate;
+  int lastDiskUsageState;
+  bool lastDiskAlert;
+  double lastSystemLoad;
+  int lastTimersState;
+  time_t lastSignalDisplay;
+  int lastLiveIndicatorY;
+  bool lastLiveIndicatorTransferring;
+  const cChannel *lastChannel;
+  const cEvent *lastEvent;
+  const cRecording *lastRecording;
+  cString lastHeader;
+  int lastSeen;
+  static cBitmap bmArrowUp, bmArrowDown, bmTransferMode;
+  void DrawMainFrameUpper(tColor Color);
+  void DrawMainFrameLower(void);
+  void DrawMainButton(const char *Text, int x0, int x1, int x2, int x3, int y0, int y1, tColor ColorFg, tColor ColorBg, const cFont *Font);
+  void DrawMenuFrame(void);
+  void DrawMainBracket(void);
+  void DrawStatusElbows(void);
+  void DrawDate(void);
+  void DrawDisk(void);
+  void DrawLoad(void);
+  void DrawFrameDisplay(void);
+  void DrawScrollbar(int Total, int Offset, int Shown, bool CanScrollUp, bool CanScrollDown);
+  void DrawTimer(const cTimer *Timer, int y, bool MultiRec);
+  void DrawTimers(void);
+  void DrawDevice(const cDevice *Device);
+  void DrawDevices(void);
+  void DrawLiveIndicator(void);
+  void DrawSignals(void);
+  void DrawLive(const cChannel *Channel);
+  void DrawPlay(cControl *Control);
+  void DrawInfo(const cEvent *Event, bool WithTime);
+  void DrawSeen(int Current, int Total);
+  void DrawTextScrollbar(void);
+public:
+  cSkinLCARSDisplayMenu(void);
+  virtual ~cSkinLCARSDisplayMenu();
+  virtual void Scroll(bool Up, bool Page);
+  virtual int MaxItems(void);
+  virtual void Clear(void);
+  virtual void SetMenuCategory(eMenuCategory MenuCategory);
+  virtual void SetTitle(const char *Title);
+  virtual void SetButtons(const char *Red, const char *Green = NULL, const char *Yellow = NULL, const char *Blue = NULL);
+  virtual void SetMessage(eMessageType Type, const char *Text);
+  virtual void SetItem(const char *Text, int Index, bool Current, bool Selectable);
+  virtual void SetScrollbar(int Total, int Offset);
+  virtual void SetEvent(const cEvent *Event);
+  virtual void SetRecording(const cRecording *Recording);
+  virtual void SetText(const char *Text, bool FixedFont);
+  virtual int GetTextAreaWidth(void) const;
+  virtual const cFont *GetTextAreaFont(bool FixedFont) const;
+  virtual void Flush(void);
+  };
+
+cBitmap cSkinLCARSDisplayMenu::bmArrowUp(arrowup_xpm);
+cBitmap cSkinLCARSDisplayMenu::bmArrowDown(arrowdown_xpm);
+cBitmap cSkinLCARSDisplayMenu::bmTransferMode(play_xpm);
+
+cSkinLCARSDisplayMenu::cSkinLCARSDisplayMenu(void)
+{
+  tallFont = cFont::CreateFont(Setup.FontOsd, Setup.FontOsdSize * 1.8);
+  initial = true;
+  lastMode = cmUnknown;
+  lastChannel = NULL;
+  lastEvent = NULL;
+  lastRecording = NULL;
+  lastSeen = -1;
+  lastTimersState = -1;
+  lastSignalDisplay = 0;
+  lastLiveIndicatorY = -1;
+  lastLiveIndicatorTransferring = false;
+  lastDiskUsageState = -1;
+  lastDiskAlert = false;
+  lastSystemLoad = -1;
+  const cFont *font = cFont::GetFont(fontOsd);
+  lineHeight = font->Height();
+  tinyFont = CreateTinyFont(lineHeight);
+  frameColor = Theme.Color(clrMenuFrameBg);
+  currentIndex = -1;
+  // The outer frame:
+  int d = 5 * lineHeight;
+  xa00 = 0;
+  xa01 = xa00 + d / 2;
+  xa02 = xa00 + d;
+  xa03 = xa02 + lineHeight;
+  xa04 = xa02 + d / 4;
+  xa05 = xa02 + d;
+  xa06 = xa05 + Gap;
+  xa09 = cOsd::OsdWidth();
+  xa08 = xa09 - lineHeight;
+  xa07 = xa08 - Gap;
+
+  yt00 = 0;
+  yt01 = yt00 + lineHeight;
+  yt02 = yt01 + lineHeight;
+  yt03 = yt01 + d / 4;
+  yt04 = yt02 + Gap;
+  yt05 = yt00 + d / 2;
+  yt06 = yt04 + 2 * lineHeight;
+
+  yc00 = yt06 + Gap;
+  yc05 = yc00 + 3 * lineHeight + Gap / 2;
+  yc04 = yc05 - lineHeight;
+  yc03 = yc04 - lineHeight;
+  yc02 = yc04 - d / 4;
+  yc01 = yc05 - d / 2;
+
+  yc06 = yc05 + Gap;
+  yc07 = yc06 + lineHeight;
+  yc08 = yc07 + lineHeight;
+  yc09 = yc07 + d / 4;
+  yc10 = yc06 + d / 2;
+  yc11 = yc06 + 3 * lineHeight + Gap / 2;
+
+  yb00 = yc11 + Gap;
+  yb01 = yb00 + 2 * lineHeight;
+  yb02 = yb01 + Gap;
+  yb03 = yb02 + 2 * lineHeight;
+  yb04 = yb03 + Gap;
+  yb05 = yb04 + 2 * lineHeight;
+  yb06 = yb05 + Gap;
+  yb07 = yb06 + 2 * lineHeight;
+  yb08 = yb07 + Gap;
+
+  yb15 = cOsd::OsdHeight();
+  yb14 = yb15 - lineHeight;
+  yb13 = yb14 - lineHeight;
+  yb12 = yb14 - d / 4;
+  yb11 = yb15 - d / 2;
+  yb10 = yb13 - Gap - 2 * lineHeight;
+  yb09 = yb10 - Gap;
+
+  // Compensate for large font size:
+  if (yb09 - yb08 < 2 * lineHeight) {
+     yb08 = yb06;
+     yb06 = 0; // drop empty rectangle
+     }
+  if (yb09 - yb08 < 2 * lineHeight) {
+     yb05 = yb09;
+     yb08 = 0; // drop "LCARS" display
+     }
+  if (yb05 - yb04 < 2 * lineHeight) {
+     yb03 = yb09;
+     yb04 = 0; // drop "LOAD" display
+     }
+  if (yb03 - yb02 < 2 * lineHeight) {
+     yb01 = yb09;
+     yb02 = 0; // drop "DISK" display
+     }
+  // Anything else is just insanely large...
+
+  // The main command menu:
+  xm00 = xa03;
+  xm01 = xa05;
+  xm02 = xa06;
+  xm08 = (xa09 + xa00) / 2;
+  xm07 = xm08 - lineHeight;
+  xm06 = xm07 - lineHeight / 2;
+  xm05 = xm06 - lineHeight / 2;
+  xm04 = xm05 - lineHeight;
+  xm03 = xm04 - Gap;
+  ym00 = yc08;
+  ym01 = ym00 + lineHeight / 2;
+  ym02 = ym01 + lineHeight / 2;
+  ym03 = ym02 + Gap;
+  ym07 = yb15;
+  ym06 = ym07 - lineHeight / 2;
+  ym05 = ym06 - lineHeight / 2;
+  ym04 = ym05 - Gap;
+
+  // The status area:
+  xs00 = xm08 + Gap + lineHeight + Gap;
+  xs13 = xa09;
+  xs12 = xa08;
+  xs11 = xa07;
+  xs05 = (xs00 + xs11 + Gap) / 2;
+  xs04 = xs05 - lineHeight / 2;
+  xs03 = xs04 - lineHeight / 2;
+  xs02 = xs03 - 2 * lineHeight;
+  xs01 = xs02 - Gap;
+  xs06 = xs05 + Gap;
+  xs07 = xs06 + lineHeight / 2;
+  xs08 = xs07 + lineHeight / 2;
+  xs09 = xs08 + 2 * lineHeight;
+  xs10 = xs09 + Gap;
+  ys00 = yc06;
+  ys01 = ys00 + lineHeight;
+  ys02 = ys01 + lineHeight / 2;
+  ys04 = ys01 + lineHeight;
+  ys03 = ys04 - Gap;
+  ys05 = yb15;
+
+  // The color buttons in submenus:
+  xb00 = xa06;
+  xb15 = xa07;
+  int w = (xa08 - xa06) / 4;
+  xb01 = xb00 + lineHeight / 2;
+  xb02 = xb01 + Gap;
+  xb04 = xb00 + w;
+  xb03 = xb04 - Gap;
+  xb05 = xb04 + lineHeight / 2;
+  xb06 = xb05 + Gap;
+  xb08 = xb04 + w;
+  xb07 = xb08 - Gap;
+  xb09 = xb08 + lineHeight / 2;
+  xb10 = xb09 + Gap;
+  xb12 = xb08 + w;
+  xb11 = xb12 - Gap;
+  xb13 = xb12 + lineHeight / 2;
+  xb14 = xb13 + Gap;;
+
+  // The color buttons in the main menu:
+  int r = lineHeight;
+  xd07 = xa09;
+  xd06 = xd07 - r;
+  xd05 = xd06 - 4 * r;
+  xd04 = xd05 - r;
+  xd03 = xd04 - Gap;
+  xd02 = xd03 - r;
+  xd01 = xd02 - 4 * r;
+  xd00 = xd01 - r;
+  yd00 = yt00;
+  yd05 = yc04 - Gap;
+  yd04 = yd05 - 2 * r;
+  yd03 = yd04 - Gap;
+  yd02 = yd03 - 2 * r;
+  yd01 = yd02 - Gap;
+
+  xs = 0;
+
+  osd = CreateOsd(cOsd::OsdLeft(), cOsd::OsdTop(), xa00, yt00, xa09 - 1, yb15 - 1);
+}
+
+cSkinLCARSDisplayMenu::~cSkinLCARSDisplayMenu()
+{
+  delete tallFont;
+  delete tinyFont;
+  delete osd;
+}
+
+void cSkinLCARSDisplayMenu::SetMenuCategory(eMenuCategory MenuCategory)
+{
+  if (initial || MenuCategory != cSkinDisplayMenu::MenuCategory()) {
+     cSkinDisplayMenu::SetMenuCategory(MenuCategory);
+     initial = true;
+     osd->DrawRectangle(xa00, yt00, xa09 - 1, yb15 - 1, Theme.Color(clrBackground));
+     if (MenuCategory == mcMain) {
+        yi00 = ym03;
+        yi01 = ym04;
+        xi00 = xm00;
+        xi01 = xm03;
+        xi02 = xm04;
+        xi03 = xm05;
+        lastTimersState = -1;
+        DrawMainFrameLower();
+        DrawMainBracket();
+        DrawStatusElbows();
+        }
+     else {
+        yi00 = yt02;
+        yi01 = yb13;
+        xi00 = xa03;
+        xi01 = xa07;
+        xi02 = xa08;
+        xi03 = xa09;
+        DrawMenuFrame();
+        }
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawMainFrameUpper(tColor Color)
+{
+  // Top left rectangles:
+  osd->DrawRectangle(xa00, yt00, xa02 - 1, yt02 - 1, Color);
+  osd->DrawRectangle(xa00, yt04, xa02 - 1, yt06 - 1, Color);
+  // Upper elbow:
+  osd->DrawRectangle(xa00, yc00, xa01 - 1, yc01 - 1, Color);
+  osd->DrawEllipse  (xa00, yc01, xa01 - 1, yc05 - 1, Color, 3);
+  osd->DrawRectangle(xa01, yc00, xa02 - 1, yc05 - 1, Color);
+  osd->DrawEllipse  (xa02, yc02, xa04 - 1, yc04 - 1, Color, -3);
+  osd->DrawRectangle(xa02, yc04, xa05 - 1, yc05 - 1, Color);
+  // Upper delimiter:
+  osd->DrawRectangle(xa06, yc04 + lineHeight / 2, xm08 - 1, yc05 - 1, Color);
+  osd->DrawRectangle(xm08 + Gap, yc04, xs00 - Gap - 1, yc05 - 1, Color);
+  osd->DrawRectangle(xs00, yc04, xs05 - 1, yc05 - 1, Color);
+  osd->DrawRectangle(xs06, yc04, xa07 - 1, yc05 - 1, Color);
+  osd->DrawRectangle(xa08, yc04, xa09 - 1, yc05 - 1, Color);
+}
+
+void cSkinLCARSDisplayMenu::DrawMainFrameLower(void)
+{
+  const cFont *font = cFont::GetFont(fontOsd);
+  // Lower elbow:
+  osd->DrawRectangle(xa00, yc10, xa01 - 1, yc11 - 1, frameColor);
+  osd->DrawEllipse  (xa00, yc06, xa01 - 1, yc10 - 1, frameColor, 2);
+  osd->DrawRectangle(xa01, yc06, xa02 - 1, yc11 - 1, frameColor);
+  osd->DrawEllipse  (xa02, yc07, xa04 - 1, yc09 - 1, frameColor, -2);
+  osd->DrawRectangle(xa02, yc06, xa05 - 1, yc07 - 1, frameColor);
+  // Lower delimiter:
+  osd->DrawRectangle(xa06, yc06, xm08 - 1, yc07 - lineHeight / 2 - 1, frameColor);
+  osd->DrawRectangle(xm08 + Gap, yc06, xs00 - Gap - 1, yc07 - 1, frameColor);
+  osd->DrawRectangle(xa08, yc06, xa09 - 1, yc07 - 1, frameColor);
+  // VDR version:
+  osd->DrawRectangle(xa00, yb10, xa02 - 1, yb15 - 1, frameColor);
+  osd->DrawText(xa00, yb10, "VDR", Theme.Color(clrMenuFrameFg), frameColor, tallFont, xa02 - xa00, yb11 - yb10, taTop | taRight | taBorder);
+  osd->DrawText(xa00, yb15 - lineHeight, VDRVERSION, Theme.Color(clrMenuFrameFg), frameColor, font, xa02 - xa00, lineHeight, taBottom | taRight | taBorder);
+}
+
+void cSkinLCARSDisplayMenu::DrawMainButton(const char *Text, int x0, int x1, int x2, int x3, int y0, int y1, tColor ColorFg, tColor ColorBg, const cFont *Font)
+{
+  int h = y1 - y0;
+  osd->DrawEllipse(x0, y0, x1 - 1, y1 - 1, ColorBg, 7);
+  osd->DrawText(x1 + Gap, y0, Text, ColorFg, ColorBg, Font, x2 - x1 - Gap, h, taBottom | taRight);
+  osd->DrawEllipse(x2, y0, x3 - 1, y1 - 1, ColorBg, 5);
+}
+
+void cSkinLCARSDisplayMenu::DrawMenuFrame(void)
+{
+  // Upper elbow:
+  osd->DrawRectangle(xa00, yt05, xa01 - 1, yt06 - 1, frameColor);
+  osd->DrawRectangle(xa00, yt00, xa01 - 1, yt05 - 1, clrTransparent);
+  osd->DrawEllipse  (xa00, yt00, xa01 - 1, yt05 - 1, frameColor, 2);
+  osd->DrawRectangle(xa01, yt00, xa02 - 1, yt06 - 1, frameColor);
+  osd->DrawEllipse  (xa02, yt01, xa04 - 1, yt03 - 1, frameColor, -2);
+  osd->DrawRectangle(xa02, yt00, xa05 - 1, yt01 - 1, frameColor);
+  osd->DrawRectangle(xa06, yt00, xa07 - 1, yt01 - 1, frameColor);
+  osd->DrawRectangle(xa08, yt00, xa08 + lineHeight / 2 - 1, yt01 - 1, frameColor);
+  osd->DrawRectangle(xa08 + lineHeight / 2, yt00, xa09 - 1, yt00 + lineHeight / 2 - 1, clrTransparent);
+  osd->DrawEllipse  (xa08 + lineHeight / 2, yt00, xa09 - 1, yt01 - 1, frameColor, 5);
+  // Center part:
+  osd->DrawRectangle(xa00, yc00, xa02 - 1, yc11 - 1, frameColor);
+  // Lower elbow:
+  osd->DrawRectangle(xa00, yb10, xa02 - 1, yb11 - 1, frameColor);
+  osd->DrawRectangle(xa00, yb11, xa01 - 1, yb15 - 1, clrTransparent);
+  osd->DrawEllipse  (xa00, yb11, xa01 - 1, yb15 - 1, frameColor, 3);
+  osd->DrawRectangle(xa01, yb11, xa02 - 1, yb15 - 1, frameColor);
+  osd->DrawEllipse  (xa02, yb12, xa04 - 1, yb14 - 1, frameColor, -3);
+  osd->DrawRectangle(xa02, yb14, xa05 - 1, yb15 - 1, frameColor);
+  osd->DrawRectangle(xa08, yb14, xa08 + lineHeight / 2 - 1, yb15 - 1, frameColor);
+  osd->DrawRectangle(xa08 + lineHeight / 2, yb14 + lineHeight / 2, xa09 - 1, yb15 - 1, clrTransparent);
+  osd->DrawEllipse  (xa08 + lineHeight / 2, yb14, xa09 - 1, yb15 - 1, frameColor, 5);
+  osd->DrawText(xa00, yb10, "VDR", Theme.Color(clrMenuFrameFg), frameColor, tallFont, xa02 - xa00, yb11 - yb10, taTop | taRight | taBorder);
+  // Color buttons:
+  osd->DrawRectangle(xb00, yb14, xb01 - 1, yb15 - 1, Theme.Color(clrButtonRedBg));
+  osd->DrawRectangle(xb04, yb14, xb05 - 1, yb15 - 1, Theme.Color(clrButtonGreenBg));
+  osd->DrawRectangle(xb08, yb14, xb09 - 1, yb15 - 1, Theme.Color(clrButtonYellowBg));
+  osd->DrawRectangle(xb12, yb14, xb13 - 1, yb15 - 1, Theme.Color(clrButtonBlueBg));
+}
+
+void cSkinLCARSDisplayMenu::DrawDate(void)
+{
+  cString s = DayDateTime();
+  if (initial || strcmp(s, lastDate)) {
+     const cFont *font = cFont::GetFont(fontOsd);
+     tColor ColorFg = Theme.Color(clrDateFg);
+     tColor ColorBg = Theme.Color(clrDateBg);
+     lastDate = s;
+     const char *t = strrchr(s, ' ');
+     osd->DrawText(xa00, yb01 - lineHeight, t, ColorFg, ColorBg, font, xa02 - xa00, lineHeight, taBottom | taRight | taBorder);
+     s.Truncate(t - s);
+     osd->DrawText(xa00, yb00, s, ColorFg, ColorBg, font, xa02 - xa00, yb01 - yb00 - lineHeight, taTop | taRight | taBorder);
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawDisk(void)
+{
+  if (yb02) {
+     if (cVideoDiskUsage::HasChanged(lastDiskUsageState) || initial) { // must call HasChanged() first, or it shows an outdated value in the 'initial' case!
+        const cFont *font = cFont::GetFont(fontOsd);
+        int DiskUsage = cVideoDiskUsage::UsedPercent();
+        bool DiskAlert = DiskUsage > DISKUSAGEALERTLIMIT;
+        tColor ColorFg = DiskAlert ? Theme.Color(clrAlertFg) : Theme.Color(clrMenuFrameFg);
+        tColor ColorBg = DiskAlert ? Theme.Color(clrAlertBg) : frameColor;
+        if (initial || DiskAlert != lastDiskAlert)
+           osd->DrawText(xa00, yb02, tr("DISK"), ColorFg, ColorBg, tinyFont, xa02 - xa00, yb03 - yb02, taTop | taLeft | taBorder);
+        osd->DrawText(xa01, yb02, itoa(DiskUsage), ColorFg, ColorBg, font, xa02 - xa01, lineHeight, taBottom | taRight | taBorder);
+        osd->DrawText(xa00, yb03 - lineHeight, cString::sprintf("%02d:%02d", cVideoDiskUsage::FreeMinutes() / 60, cVideoDiskUsage::FreeMinutes() % 60), ColorFg, ColorBg, font, xa02 - xa00, 0, taBottom | taRight | taBorder);
+        lastDiskAlert = DiskAlert;
+        }
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawLoad(void)
+{
+  if (yb04) {
+     tColor ColorFg = Theme.Color(clrMenuFrameFg);
+     tColor ColorBg = frameColor;
+     if (initial)
+        osd->DrawText(xa00, yb04, tr("LOAD"), ColorFg, ColorBg, tinyFont, xa02 - xa00, yb05 - yb04, taTop | taLeft | taBorder);
+     double SystemLoad;
+     if (getloadavg(&SystemLoad, 1) > 0) {
+        if (initial || SystemLoad != lastSystemLoad) {
+           osd->DrawText(xa00, yb05 - lineHeight, cString::sprintf("%.1f", SystemLoad), ColorFg, ColorBg, cFont::GetFont(fontOsd), xa02 - xa00, lineHeight, taBottom | taRight | taBorder);
+           lastSystemLoad = SystemLoad;
+           }
+        }
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawMainBracket(void)
+{
+  tColor Color = Theme.Color(clrMenuMainBracket);
+  osd->DrawRectangle(xm00, ym00, xm01 - 1, ym01 - 1, Color);
+  osd->DrawRectangle(xm02, ym00, xm07 - 1, ym01 - 1, Color);
+  osd->DrawEllipse  (xm07, ym00, xm08 - 1, ym02 - 1, Color, 1);
+  osd->DrawEllipse  (xm06, ym01, xm07 - 1, ym02 - 1, Color, -1);
+  osd->DrawRectangle(xm07, ym03, xm08 - 1, ym04 - 1, Color);
+  osd->DrawEllipse  (xm06, ym05, xm07 - 1, ym06 - 1, Color, -4);
+  osd->DrawEllipse  (xm07, ym05, xm08 - 1, ym07 - 1, Color, 4);
+  osd->DrawRectangle(xm02, ym06, xm07 - 1, ym07 - 1, Color);
+  osd->DrawRectangle(xm00, ym06, xm01 - 1, ym07 - 1, Color);
+}
+
+void cSkinLCARSDisplayMenu::DrawStatusElbows(void)
+{
+  const cFont *font = cFont::GetFont(fontOsd);
+  osd->DrawText     (xs00, ys00, tr("TIMERS"), Theme.Color(clrMenuFrameFg), frameColor, font, xs01 - xs00, lineHeight, taBottom | taLeft | taBorder);
+  osd->DrawRectangle(xs02, ys00, xs03 - 1, ys01 - 1, frameColor);
+  osd->DrawEllipse  (xs03, ys00, xs05 - 1, ys01 - 1, frameColor, 1);
+  osd->DrawEllipse  (xs03, ys01, xs04 - 1, ys02 - 1, frameColor, -1);
+  osd->DrawRectangle(xs04, ys01, xs05 - 1, ys03 - 1, frameColor);
+  osd->DrawRectangle(xs04, ys04, xs05 - 1, ys05 - 1, frameColor);
+  osd->DrawText     (xs10, ys00, tr("DEVICES"), Theme.Color(clrMenuFrameFg), frameColor, font, xs11 - xs10, lineHeight, taBottom | taRight | taBorder);
+  osd->DrawRectangle(xs08, ys00, xs09 - 1, ys01 - 1, frameColor);
+  osd->DrawEllipse  (xs06, ys00, xs08 - 1, ys01 - 1, frameColor, 2);
+  osd->DrawEllipse  (xs07, ys01, xs08 - 1, ys02 - 1, frameColor, -2);
+  osd->DrawRectangle(xs06, ys01, xs07 - 1, ys03 - 1, frameColor);
+  osd->DrawRectangle(xs06, ys04, xs07 - 1, ys05 - 1, frameColor);
+  osd->DrawRectangle(xs12, ys00, xs13 - 1, ys01 - 1, frameColor);
+}
+
+void cSkinLCARSDisplayMenu::DrawFrameDisplay(void)
+{
+  DrawDate();
+  DrawDisk();
+  DrawLoad();
+  if (initial) {
+     if (yb06)
+        osd->DrawRectangle(xa00, yb06, xa02 - 1, yb07 - 1, frameColor);
+     if (yb08) {
+        const cFont *font = cFont::GetFont(fontOsd);
+        osd->DrawRectangle(xa00, yb08, xa02 - 1, yb09 - 1, frameColor);
+        osd->DrawText(xa00, yb09 - lineHeight, "LCARS", Theme.Color(clrMenuFrameFg), frameColor, font, xa02 - xa00, lineHeight, taBottom | taRight | taBorder);
+        }
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawScrollbar(int Total, int Offset, int Shown, bool CanScrollUp, bool CanScrollDown)
+{
+  int x0, x1, tt, tb;
+  tColor ClearColor;
+  if (MenuCategory() == mcMain) {
+     x0 = xm07;
+     x1 = xm08;
+     tt = ym03;
+     tb = ym04;
+     ClearColor = Theme.Color(clrMenuMainBracket);
+     }
+  else {
+     x0 = xa02 + Gap;
+     x1 = x0 + lineHeight / 2;
+     tt = yc00;
+     tb = yc11;
+     ClearColor = Theme.Color(clrBackground);
+     int d = TextFrame;
+     if (CanScrollUp)
+        osd->DrawBitmap(xa02 - bmArrowUp.Width() - d, yc00 + d, bmArrowUp, Theme.Color(clrMenuScrollbarArrow), frameColor);
+     else
+        osd->DrawRectangle(xa02 - bmArrowUp.Width() - d, yc00 + d, xa02 - d - 1, yc00 + d + bmArrowUp.Height() - 1, frameColor);
+     if (CanScrollDown)
+        osd->DrawBitmap(xa02 - bmArrowDown.Width() - d, yc11 - d - bmArrowDown.Height(), bmArrowDown, Theme.Color(clrMenuScrollbarArrow), frameColor);
+     else
+        osd->DrawRectangle(xa02 - bmArrowDown.Width() - d, yc11 - d - bmArrowDown.Height(), xa02 - d - 1, yc11 - d - 1, frameColor);
+     }
+  if (Total > 0 && Total > Shown) {
+     int sw = x1 - x0;
+     int sh = max(int((tb - tt) * double(Shown) / Total + 0.5), sw);
+     int st = min(int(tt + (tb - tt) * double(Offset) / Total + 0.5), tb - sh);
+     int sb = min(st + sh, tb);
+     osd->DrawRectangle(x0, tt, x1 - 1, tb - 1, Theme.Color(clrMenuScrollbarTotal));
+     osd->DrawRectangle(x0, st, x1 - 1, sb - 1, Theme.Color(clrMenuScrollbarShown));
+     }
+  else if (MenuCategory() != mcMain)
+     osd->DrawRectangle(x0, tt, x1 - 1, tb - 1, ClearColor);
+}
+
+void cSkinLCARSDisplayMenu::DrawTimer(const cTimer *Timer, int y, bool MultiRec)
+{
+  // The timer data:
+  bool Alert = !Timer->Recording() && Timer->Pending();
+  tColor ColorFg = Alert ? Theme.Color(clrAlertFg) : Theme.Color(clrTimerFg);
+  tColor ColorBg = Alert ? Theme.Color(clrAlertFg) : Theme.Color(clrTimerBg);
+  osd->DrawRectangle(xs00, y, xs03 - 1, y + lineHeight - 1, ColorBg);
+  cString Date;
+  if (Timer->Recording())
+     Date = cString::sprintf("-%s", *TimeString(Timer->StopTime()));
+  else {
+     time_t Now = time(NULL);
+     cString Today = WeekDayName(Now);
+     cString Time = TimeString(Timer->StartTime());
+     cString Day = WeekDayName(Timer->StartTime());
+     if (Timer->StartTime() > Now + 6 * SECSINDAY)
+        Date = DayDateTime(Timer->StartTime());
+     else if (strcmp(Day, Today) != 0)
+        Date = cString::sprintf("%s %s", *Day, *Time);
+     else
+        Date = Time;
+     }
+  if (Timer->Flags() & tfVps)
+     Date = cString::sprintf("VPS %s", *Date);
+  const cChannel *Channel = Timer->Channel();
+  const cEvent *Event = Timer->Event();
+  int d = max(TextFrame / 2, 1);
+  if (Channel) {
+     osd->DrawText(xs00 + d, y, Channel->Name(), ColorFg, ColorBg, tinyFont, xs03 - xs00 - d);
+     osd->DrawText(xs03 - tinyFont->Width(Date) - d, y, Date, ColorFg, ColorBg, tinyFont);
+     }
+  if (Event)
+     osd->DrawText(xs00 + d, y + lineHeight - tinyFont->Height(), Event->Title(), ColorFg, ColorBg, tinyFont, xs03 - xs00 - 2 * d);
+  // The timer recording indicator:
+  if (Timer->Recording())
+     osd->DrawRectangle(xs03 + Gap, y - (MultiRec ? Gap : 0), xs04 - Gap / 2 - 1, y + lineHeight - 1, Theme.Color(clrMenuTimerRecording));
+}
+
+void cSkinLCARSDisplayMenu::DrawTimers(void)
+{
+  if (Timers.Modified(lastTimersState)) {
+     deviceRecording.Clear();
+     const cFont *font = cFont::GetFont(fontOsd);
+     osd->DrawRectangle(xs00, ys04, xs04 - 1, ys05 - 1, Theme.Color(clrBackground));
+     osd->DrawRectangle(xs07, ys04, xs13 - 1, ys05 - 1, Theme.Color(clrBackground));
+     cSortedTimers SortedTimers;
+     cVector<int> FreeDeviceSlots;
+     int y = ys04;
+     // Timers and recording devices:
+     while (1) {
+           int NumTimers = 0;
+           const cDevice *Device = NULL;
+           for (int i = 0; i < SortedTimers.Size(); i++) {
+               if (y + lineHeight > ys05)
+                  break;
+               if (const cTimer *Timer = SortedTimers[i]) {
+                  if (Timer->Recording()) {
+                     if (cRecordControl *RecordControl = cRecordControls::GetRecordControl(Timer)) {
+                        if (!Device || Device == RecordControl->Device()) {
+                           DrawTimer(Timer, y, NumTimers > 0);
+                           NumTimers++;
+                           if (!Device) {
+                              Device = RecordControl->Device();
+                              deviceOffset[Device->DeviceNumber()] = y;
+                              deviceRecording[Device->DeviceNumber()] = true;
+                              }
+                           else
+                              FreeDeviceSlots.Append(y);
+                           y += lineHeight + Gap;
+                           }
+                        else
+                           continue;
+                        }
+                     SortedTimers[i] = NULL;
+                     }
+                  else if (!Device) {
+                     DrawTimer(Timer, y, false);
+                     FreeDeviceSlots.Append(y);
+                     y += lineHeight + Gap;
+                     SortedTimers[i] = NULL;
+                     }
+                  }
+               }
+           if (!Device)
+              break;
+           }
+     // Devices currently not recording:
+     int Slot = 0;
+     for (int i = 0; i < cDevice::NumDevices(); i++) {
+         if (const cDevice *Device = cDevice::GetDevice(i)) {
+            if (!deviceRecording[Device->DeviceNumber()]) {
+               if (Slot < FreeDeviceSlots.Size()) {
+                  y = FreeDeviceSlots[Slot];
+                  Slot++;
+                  }
+               if (y + lineHeight > ys05)
+                  break;
+               deviceOffset[Device->DeviceNumber()] = y;
+               y += lineHeight + Gap;
+               }
+            }
+         }
+     osd->DrawText(xs02, ys00, itoa(Timers.Count()), Theme.Color(clrMenuFrameFg), frameColor, font, xs03 - xs02, ys01 - ys00, taBottom | taLeft | taBorder);
+     osd->DrawText(xs08, ys00, itoa(cDevice::NumDevices()), Theme.Color(clrMenuFrameFg), frameColor, font, xs09 - xs08, ys01 - ys00, taBottom | taRight | taBorder);
+     lastSignalDisplay = 0;
+     initial = true; // forces redrawing of devices
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawDevice(const cDevice *Device)
+{
+  int dn = Device->DeviceNumber();
+  int y = deviceOffset[dn];
+  if (y + lineHeight <= ys05) {
+     if (DrawDeviceData(osd, Device, xs08, y, xs11, y + lineHeight, xs, tinyFont, lastDeviceType[dn], lastCamSlot[dn], initial)) {
+        // Make sure signal meters are redrawn:
+        lastSignalStrength[dn] = -1;
+        lastSignalQuality[dn] = -1;
+        lastSignalDisplay = 0;
+        }
+     // The device recording indicator:
+     if (deviceRecording[dn])
+        osd->DrawRectangle(xs07 + Gap / 2, y, xs08 - Gap - 1, y + lineHeight - 1, Theme.Color(clrMenuDeviceRecording));
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawDevices(void)
+{
+  for (int i = 0; i < cDevice::NumDevices(); i++) {
+      if (const cDevice *Device = cDevice::GetDevice(i))
+         DrawDevice(Device);
+      }
+}
+
+void cSkinLCARSDisplayMenu::DrawLiveIndicator(void)
+{
+  cDevice *Device = cDevice::PrimaryDevice();
+  int y = -1;
+  bool Transferring = Device->Transferring();
+  if (!Device->Replaying() || Transferring)
+     y = deviceOffset[cDevice::ActualDevice()->DeviceNumber()];
+  if (initial || y != lastLiveIndicatorY || Transferring != lastLiveIndicatorTransferring) {
+     if (lastLiveIndicatorY >= 0)
+        osd->DrawRectangle(xs12, lastLiveIndicatorY, xs13 - 1, lastLiveIndicatorY + lineHeight - 1, Theme.Color(clrBackground));
+     if (y >= 0) {
+        osd->DrawRectangle(xs12, y, xs12 + lineHeight / 2 - 1, y + lineHeight - 1, frameColor);
+        osd->DrawEllipse  (xs12 + lineHeight / 2, y, xs13 - 1, y + lineHeight - 1, frameColor, 5);
+        if (Transferring)
+           osd->DrawBitmap((xs12 + xs13 - bmTransferMode.Width()) / 2, y + (lineHeight - bmTransferMode.Height()) / 2, bmTransferMode, Theme.Color(clrChannelFrameFg), Theme.Color(clrChannelFrameBg));
+        }
+     lastLiveIndicatorY = y;
+     lastLiveIndicatorTransferring = Transferring;
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawSignals(void)
+{
+  time_t Now = time(NULL);
+  if (initial || Now - lastSignalDisplay >= SIGNALDISPLAYDELTA) {
+     for (int i = 0; i < cDevice::NumDevices(); i++) {
+         if (const cDevice *Device = cDevice::GetDevice(i)) {
+            if (int y = deviceOffset[i])
+               DrawDeviceSignal(osd, Device, xs + lineHeight / 2, y, xs11, y + lineHeight, lastSignalStrength[i], lastSignalQuality[i], initial);
+            }
+         }
+     lastSignalDisplay = Now;
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawLive(const cChannel *Channel)
+{
+  if (lastMode != cmLive) {
+     initial = true;
+     lastMode = cmLive;
+     }
+  if (initial) {
+     DrawMainFrameUpper(Theme.Color(clrChannelFrameBg));
+     osd->DrawText(xd00, yd00, tr("LIVE"), Theme.Color(clrChannelFrameBg), Theme.Color(clrBackground), tallFont, xd07 - xd00, yd01 - yd00, taTop | taRight | taBorder);
+     }
+  if (initial || Channel != lastChannel) {
+     osd->DrawText(xa00, yt00, itoa(Channel->Number()), Theme.Color(clrChannelFrameFg), Theme.Color(clrChannelFrameBg), tallFont, xa02 - xa00, yt02 - yt00, taTop | taRight | taBorder);
+     osd->DrawText(xa03, yt00, Channel->Name(), Theme.Color(clrChannelName), Theme.Color(clrBackground), tallFont, xd00 - xa03, yd01 - yd00, taTop | taLeft);
+     lastChannel = Channel;
+     }
+  // The current programme:
+  cSchedulesLock SchedulesLock;
+  if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock)) {
+     if (const cSchedule *Schedule = Schedules->GetSchedule(Channel)) {
+        const cEvent *Event = Schedule->GetPresentEvent();
+        if (initial || Event != lastEvent) {
+           DrawInfo(Event, true);
+           lastEvent = Event;
+           lastSeen = -1;
+           }
+        int Current = 0;
+        int Total = 0;
+        if (Event) {
+           time_t t = time(NULL);
+           if (t > Event->StartTime())
+              Current = t - Event->StartTime();
+           Total = Event->Duration();
+           }
+        DrawSeen(Current, Total);
+        }
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawPlay(cControl *Control)
+{
+  if (lastMode != cmPlay) {
+     initial = true;
+     lastMode = cmPlay;
+     }
+  if (initial) {
+     DrawMainFrameUpper(Theme.Color(clrReplayFrameBg));
+     osd->DrawText(xd00, yd00, tr("PLAY"), Theme.Color(clrReplayFrameBg), Theme.Color(clrBackground), tallFont, xd07 - xd00, yd01 - yd00, taTop | taRight | taBorder);
+     }
+  // The current progress:
+  int Current = 0;
+  int Total = 0;
+  if (Control->GetIndex(Current, Total))
+     DrawSeen(Current, Total);
+  // The current programme:
+  if (const cRecording *Recording = Control->GetRecording()) {
+     if (initial || Recording != lastRecording) {
+        const cFont *font = cFont::GetFont(fontOsd);
+        if (const cRecordingInfo *Info = Recording->Info()) {
+           osd->DrawText(xa03, yt00, Info->ChannelName(), Theme.Color(clrChannelName), Theme.Color(clrBackground), tallFont, xd00 - xa03, yd01 - yd00, taTop | taLeft);
+           DrawInfo(Info->GetEvent(), false);
+           }
+        else
+           osd->DrawText(xa03, yt04, Recording->Name(), Theme.Color(clrEventTitle), Theme.Color(clrBackground), font, xd00 - xa03, 0, taTop | taLeft);
+        osd->DrawText(xa00, yt04, ShortDateString(Recording->Start()), Theme.Color(clrReplayFrameFg), Theme.Color(clrReplayFrameBg), font, xa02 - xa00, 0, taTop | taRight | taBorder);
+        osd->DrawText(xa00, yt06 - lineHeight, TimeString(Recording->Start()), Theme.Color(clrReplayFrameFg), Theme.Color(clrReplayFrameBg), font, xa02 - xa00, 0, taBottom | taRight | taBorder);
+        lastRecording = Recording;
+        }
+     }
+  else {
+     cString Header = Control->GetHeader();
+     if (!*lastHeader || strcmp(Header, lastHeader)) {
+        osd->DrawText(xa03, yt00, Header, Theme.Color(clrMenuText), Theme.Color(clrBackground), tallFont, xd00 - xa03, yd01 - yd00, taTop | taLeft);
+        lastHeader = Header;
+        }
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawInfo(const cEvent *Event, bool WithTime)
+{
+  if (Event) {
+     const cFont *font = cFont::GetFont(fontOsd);
+     int y = yt04;
+     osd->DrawText(xa03, y, Event->Title(), Theme.Color(clrEventTitle), Theme.Color(clrBackground), font, xd00 - xa03 - lineHeight, lineHeight, taBottom | taLeft);
+     y += lineHeight;
+     osd->DrawText(xa03, y, Event->ShortText(), Theme.Color(clrEventShortText), Theme.Color(clrBackground), cFont::GetFont(fontSml), xd00 - xa03 - lineHeight, lineHeight, taTop | taLeft);
+     if (WithTime) {
+        osd->DrawText(xa00, yt04, Event->GetTimeString(), Theme.Color(clrChannelFrameFg), Theme.Color(clrChannelFrameBg), font, xa02 - xa00, lineHeight, taTop | taRight | taBorder);
+        osd->DrawText(xa00, yt06 - lineHeight, cString::sprintf("-%s", *Event->GetEndTimeString()), Theme.Color(clrChannelFrameFg), Theme.Color(clrChannelFrameBg), font, xa02 - xa00, lineHeight, taBottom | taRight | taBorder);
+        }
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawSeen(int Current, int Total)
+{
+  int Seen = min(xm08 - xm02, int((xm08 - xm02) * double(Current) / Total));
+  if (initial || Seen != lastSeen) {
+     int y0 = yc04 - ShowSeenExtent;
+     int y1 = yc04 + lineHeight / 2 - Gap / 2;
+     osd->DrawRectangle(xm02, y0, xm02 + Seen - 1, y1 - 1, Theme.Color(clrSeen));
+     osd->DrawRectangle(xm02 + Seen, y0, xm08 - 1, y1 - 1, Theme.Color(clrBackground));
+     lastSeen = Seen;
+     }
+}
+
+void cSkinLCARSDisplayMenu::DrawTextScrollbar(void)
+{
+  if (textScroller.CanScroll())
+     DrawScrollbar(textScroller.Total(), textScroller.Offset(), textScroller.Shown(), textScroller.CanScrollUp(), textScroller.CanScrollDown());
+}
+
+void cSkinLCARSDisplayMenu::Scroll(bool Up, bool Page)
+{
+  cSkinDisplayMenu::Scroll(Up, Page);
+  DrawTextScrollbar();
+}
+
+int cSkinLCARSDisplayMenu::MaxItems(void)
+{
+  if (MenuCategory() == mcMain)
+     return (ym04 - ym03) / lineHeight;
+  else
+     return (yb13 - yt02) / lineHeight;
+}
+
+void cSkinLCARSDisplayMenu::Clear(void)
+{
+  textScroller.Reset();
+  osd->DrawRectangle(xi00, yi00, xi03 - 1, yi01 - 1, Theme.Color(clrBackground));
+}
+
+void cSkinLCARSDisplayMenu::SetTitle(const char *Title)
+{
+  if (MenuCategory() != mcMain) {
+     const cFont *font = cFont::GetFont(fontOsd);
+     int w = font->Width(Title);
+     osd->DrawRectangle(xa06, yt00, xa07 - w - Gap - 1, yt01 - 1, frameColor);
+     osd->DrawText(xa07 - w - Gap, yt00, Title, Theme.Color(clrMenuTitle), Theme.Color(clrBackground), font, w + Gap, yt01 - yt00, taRight);
+     }
+}
+
+void cSkinLCARSDisplayMenu::SetButtons(const char *Red, const char *Green, const char *Yellow, const char *Blue)
+{
+  const cFont *font = cFont::GetFont(fontSml);
+  if (MenuCategory() == mcMain) {
+     DrawMainButton(Red,    xd00, xd01, xd02, xd03, yd02, yd03, Theme.Color(clrButtonRedFg),    Theme.Color(clrButtonRedBg),    font);
+     DrawMainButton(Green,  xd04, xd05, xd06, xd07, yd02, yd03, Theme.Color(clrButtonGreenFg),  Theme.Color(clrButtonGreenBg),  font);
+     DrawMainButton(Yellow, xd00, xd01, xd02, xd03, yd04, yd05, Theme.Color(clrButtonYellowFg), Theme.Color(clrButtonYellowBg), font);
+     DrawMainButton(Blue,   xd04, xd05, xd06, xd07, yd04, yd05, Theme.Color(clrButtonBlueFg),   Theme.Color(clrButtonBlueBg),   font);
+     }
+  else {
+     int h = yb15 - yb14;
+     osd->DrawText(xb02, yb14, Red,    Theme.Color(clrButtonRedFg),    Theme.Color(clrButtonRedBg),    font, xb03 - xb02, h, taLeft | taBorder);
+     osd->DrawText(xb06, yb14, Green,  Theme.Color(clrButtonGreenFg),  Theme.Color(clrButtonGreenBg),  font, xb07 - xb06, h, taLeft | taBorder);
+     osd->DrawText(xb10, yb14, Yellow, Theme.Color(clrButtonYellowFg), Theme.Color(clrButtonYellowBg), font, xb11 - xb10, h, taLeft | taBorder);
+     osd->DrawText(xb14, yb14, Blue,   Theme.Color(clrButtonBlueFg),   Theme.Color(clrButtonBlueBg),   font, xb15 - xb14, h, taLeft | taBorder);
+     }
+}
+
+void cSkinLCARSDisplayMenu::SetMessage(eMessageType Type, const char *Text)
+{
+  if (Text) {
+     osd->SaveRegion(xb00, yb14, xb15 - 1, yb15 - 1);
+     osd->DrawText(xb00, yb14, Text, Theme.Color(clrMessageStatusFg + 2 * Type), Theme.Color(clrMessageStatusBg + 2 * Type), cFont::GetFont(fontSml), xb15 - xb00, yb15 - yb14, taCenter);
+     }
+  else
+     osd->RestoreRegion();
+}
+
+void cSkinLCARSDisplayMenu::SetItem(const char *Text, int Index, bool Current, bool Selectable)
+{
+  int y = yi00 + Index * lineHeight;
+  tColor ColorFg, ColorBg;
+  if (Current) {
+     ColorFg = Theme.Color(clrMenuItemCurrentFg);
+     ColorBg = Theme.Color(clrMenuItemCurrentBg);
+     osd->DrawRectangle(xi00, y, xi01 - 1, y + lineHeight - 1, ColorBg);
+     osd->DrawRectangle(xi02, y, xi02 + lineHeight / 2 - 1, y + lineHeight - 1, ColorBg);
+     osd->DrawEllipse  (xi02 + lineHeight / 2, y, xi03 - 1, y + lineHeight - 1, ColorBg, 5);
+     currentIndex = Index;
+     }
+  else {
+     ColorFg = Theme.Color(Selectable ? clrMenuItemSelectable : clrMenuItemNonSelectable);
+     ColorBg = Theme.Color(clrBackground);
+     if (currentIndex == Index)
+        osd->DrawRectangle(xi00, y, xi03 - 1, y + lineHeight - 1, Theme.Color(clrBackground));
+     }
+  const cFont *font = cFont::GetFont(fontOsd);
+  for (int i = 0; i < MaxTabs; i++) {
+      const char *s = GetTabbedText(Text, i);
+      if (s) {
+         int xt = xi00 + TextSpacing + Tab(i);
+         osd->DrawText(xt, y, s, ColorFg, ColorBg, font, xi01 - xt);
+         }
+      if (!Tab(i + 1))
+         break;
+      }
+  SetEditableWidth(xi02 - xi00 - TextSpacing - Tab(1));
+}
+
+void cSkinLCARSDisplayMenu::SetScrollbar(int Total, int Offset)
+{
+  DrawScrollbar(Total, Offset, MaxItems(), Offset > 0, Offset + MaxItems() < Total);
+}
+
+void cSkinLCARSDisplayMenu::SetEvent(const cEvent *Event)
+{
+  if (!Event)
+     return;
+  const cFont *font = cFont::GetFont(fontOsd);
+  int xl = xi00;
+  int y = yi00;
+  cTextScroller ts;
+  char t[32];
+  snprintf(t, sizeof(t), "%s  %s - %s", *Event->GetDateString(), *Event->GetTimeString(), *Event->GetEndTimeString());
+  ts.Set(osd, xl, y, xi01 - xl, yi01 - y, t, font, Theme.Color(clrEventTime), Theme.Color(clrBackground));
+  if (Event->Vps() && Event->Vps() != Event->StartTime()) {
+     cString buffer = cString::sprintf(" VPS: %s ", *Event->GetVpsString());
+     const cFont *font = cFont::GetFont(fontSml);
+     int w = font->Width(buffer);
+     osd->DrawText(xi01 - w, y, buffer, Theme.Color(clrMenuFrameFg), frameColor, font, w);
+     int yb = y + font->Height();
+     osd->DrawRectangle(xi02, y, xi02 + lineHeight / 2 - 1, yb - 1, frameColor);
+     osd->DrawEllipse  (xi02 + lineHeight / 2, y, xi03 - 1, yb - 1, frameColor, 5);
+     }
+  y += ts.Height();
+  if (Event->ParentalRating()) {
+     cString buffer = cString::sprintf(" %s ", *Event->GetParentalRatingString());
+     const cFont *font = cFont::GetFont(fontSml);
+     int w = font->Width(buffer);
+     osd->DrawText(xi01 - w, y, buffer, Theme.Color(clrMenuFrameFg), frameColor, font, w);
+     int yb = y + font->Height();
+     osd->DrawRectangle(xi02, y, xi02 + lineHeight / 2 - 1, yb - 1, frameColor);
+     osd->DrawEllipse  (xi02 + lineHeight / 2, y, xi03 - 1, yb - 1, frameColor, 5);
+     }
+  y += font->Height();
+  ts.Set(osd, xl, y, xi01 - xl, yi01 - y, Event->Title(), font, Theme.Color(clrEventTitle), Theme.Color(clrBackground));
+  y += ts.Height();
+  if (!isempty(Event->ShortText())) {
+     const cFont *font = cFont::GetFont(fontSml);
+     ts.Set(osd, xl, y, xi01 - xl, yi01 - y, Event->ShortText(), font, Theme.Color(clrEventShortText), Theme.Color(clrBackground));
+     y += ts.Height();
+     }
+  y += font->Height();
+  if (!isempty(Event->Description())) {
+     int yt = y;
+     int yb = yi01;
+     textScroller.Set(osd, xl, yt, xi01 - xl, yb - yt, Event->Description(), font, Theme.Color(clrEventDescription), Theme.Color(clrBackground));
+     DrawTextScrollbar();
+     }
+}
+
+void cSkinLCARSDisplayMenu::SetRecording(const cRecording *Recording)
+{
+  if (!Recording)
+     return;
+  const cRecordingInfo *Info = Recording->Info();
+  const cFont *font = cFont::GetFont(fontOsd);
+  int xl = xi00;
+  int y = yi00;
+  cTextScroller ts;
+  char t[32];
+  snprintf(t, sizeof(t), "%s  %s", *DateString(Recording->Start()), *TimeString(Recording->Start()));
+  ts.Set(osd, xl, y, xi01 - xl, yi01 - y, t, font, Theme.Color(clrEventTime), Theme.Color(clrBackground));
+  y += ts.Height();
+  if (Info->GetEvent()->ParentalRating()) {
+     cString buffer = cString::sprintf(" %s ", *Info->GetEvent()->GetParentalRatingString());
+     const cFont *font = cFont::GetFont(fontSml);
+     int w = font->Width(buffer);
+     osd->DrawText(xi01 - w, y, buffer, Theme.Color(clrMenuFrameFg), frameColor, font, w);
+     int yb = y + font->Height();
+     osd->DrawRectangle(xi02, y, xi02 + lineHeight / 2 - 1, yb - 1, frameColor);
+     osd->DrawEllipse  (xi02 + lineHeight / 2, y, xi03 - 1, yb - 1, frameColor, 5);
+     }
+  y += font->Height();
+  const char *Title = Info->Title();
+  if (isempty(Title))
+     Title = Recording->Name();
+  ts.Set(osd, xl, y, xi01 - xl, yi01 - y, Title, font, Theme.Color(clrEventTitle), Theme.Color(clrBackground));
+  y += ts.Height();
+  if (!isempty(Info->ShortText())) {
+     const cFont *font = cFont::GetFont(fontSml);
+     ts.Set(osd, xl, y, xi01 - xl, yi01 - y, Info->ShortText(), font, Theme.Color(clrEventShortText), Theme.Color(clrBackground));
+     y += ts.Height();
+     }
+  y += font->Height();
+  if (!isempty(Info->Description())) {
+     int yt = y;
+     int yb = yi01;
+     textScroller.Set(osd, xl, yt, xi01 - xl, yb - yt, Info->Description(), font, Theme.Color(clrEventDescription), Theme.Color(clrBackground));
+     DrawTextScrollbar();
+     }
+}
+
+void cSkinLCARSDisplayMenu::SetText(const char *Text, bool FixedFont)
+{
+  textScroller.Set(osd, xi00, yi00, GetTextAreaWidth(), yi01 - yi00, Text, GetTextAreaFont(FixedFont), Theme.Color(clrMenuText), Theme.Color(clrBackground));
+  DrawTextScrollbar();
+}
+
+int cSkinLCARSDisplayMenu::GetTextAreaWidth(void) const
+{
+  return xi01 - xi00;
+}
+
+const cFont *cSkinLCARSDisplayMenu::GetTextAreaFont(bool FixedFont) const
+{
+  const cFont *font = cFont::GetFont(FixedFont ? fontFix : fontOsd);
+  //XXX -> make a way to let the text define which font to use
+  return font;
+}
+
+void cSkinLCARSDisplayMenu::Flush(void)
+{
+  if (MenuCategory() == mcMain) {
+     cDevice *Device = cDevice::PrimaryDevice();
+     if (!Device->Replaying() || Device->Transferring()) {
+        const cChannel *Channel = Channels.GetByNumber(cDevice::PrimaryDevice()->CurrentChannel());
+        DrawLive(Channel);
+        }
+     else if (cControl *Control = cControl::Control(true))
+        DrawPlay(Control);
+     DrawTimers();
+     DrawDevices();
+     DrawLiveIndicator();
+     DrawSignals();
+     }
+  DrawFrameDisplay();
+  osd->Flush();
+  initial = false;
+}
+
+// --- cSkinLCARSDisplayReplay -----------------------------------------------
+
+class cSkinLCARSDisplayReplay : public cSkinDisplayReplay {
+private:
+  cOsd *osd;
+  int xp00, xp01, xp02, xp03, xp04, xp05, xp06, xp07, xp08, xp09, xp10, xp11, xp12, xp13, xp14, xp15;
+  int yp00, yp01, yp02, yp03, yp04, yp05, yp06, yp07, yp08, yp09;
+  bool modeOnly;
+  int lineHeight;
+  tColor frameColor;
+  cString lastDate;
+  tTrackId lastTrackId;
+  void DrawDate(void);
+  void DrawTrack(void);
+public:
+  cSkinLCARSDisplayReplay(bool ModeOnly);
+  virtual ~cSkinLCARSDisplayReplay();
+  virtual void SetRecording(const cRecording *Recording);
+  virtual void SetTitle(const char *Title);
+  virtual void SetMode(bool Play, bool Forward, int Speed);
+  virtual void SetProgress(int Current, int Total);
+  virtual void SetCurrent(const char *Current);
+  virtual void SetTotal(const char *Total);
+  virtual void SetJump(const char *Jump);
+  virtual void SetMessage(eMessageType Type, const char *Text);
+  virtual void Flush(void);
+  };
+
+cSkinLCARSDisplayReplay::cSkinLCARSDisplayReplay(bool ModeOnly)
+{
+  const cFont *font = cFont::GetFont(fontOsd);
+  modeOnly = ModeOnly;
+  lineHeight = font->Height();
+  frameColor = Theme.Color(clrReplayFrameBg);
+  int d = 5 * lineHeight;
+  xp00 = 0;
+  xp01 = xp00 + d / 2;
+  xp02 = xp00 + d;
+  xp03 = xp02 + lineHeight;
+  xp04 = xp02 + d / 4;
+  xp05 = xp02 + d;
+  xp06 = xp05 + Gap;
+  xp15 = cOsd::OsdWidth();
+  xp14 = xp15 - lineHeight;
+  xp13 = xp14 - Gap;
+  xp07 = (xp15 + xp00) / 2;
+  xp08 = xp07 + Gap;
+  xp09 = xp08 + lineHeight;
+  xp10 = xp09 + Gap;
+  xp11 = (xp10 + xp13 + Gap) / 2;
+  xp12 = xp11 + Gap;
+
+  yp00 = 0;
+  yp01 = yp00 + 2 * lineHeight;
+  yp02 = yp01 + Gap;
+  yp03 = yp02 + 2 * lineHeight;
+
+  yp04 = yp03 + Gap;
+  yp09 = yp04 + 3 * lineHeight + Gap / 2;
+  yp08 = yp09 - lineHeight;
+  yp07 = yp08 - lineHeight;
+  yp06 = yp08 - d / 4;
+  yp05 = yp09 - d / 2;
+
+  osd = CreateOsd(cOsd::OsdLeft(), cOsd::OsdTop() + cOsd::OsdHeight() - yp09, xp00, yp00, xp15 - 1, yp09 - 1);
+  osd->DrawRectangle(xp00, yp00, xp15 - 1, yp09 - 1, modeOnly ? clrTransparent : Theme.Color(clrBackground));
+  // Rectangles:
+  if (!modeOnly)
+     osd->DrawRectangle(xp00, yp00, xp02 - 1, yp01 - 1, frameColor);
+  osd->DrawRectangle(xp00, yp02, xp02 - 1, yp03 - 1, frameColor);
+  if (!modeOnly) {
+     // Elbow:
+     osd->DrawRectangle(xp00, yp04, xp01 - 1, yp05 - 1, frameColor);
+     osd->DrawRectangle(xp00, yp05, xp01 - 1, yp09 - 1, clrTransparent);
+     osd->DrawEllipse  (xp00, yp05, xp01 - 1, yp09 - 1, frameColor, 3);
+     osd->DrawRectangle(xp01, yp04, xp02 - 1, yp09 - 1, frameColor);
+     osd->DrawEllipse  (xp02, yp06, xp04 - 1, yp08 - 1, frameColor, -3);
+     osd->DrawRectangle(xp02, yp08, xp05 - 1, yp09 - 1, frameColor);
+     // Status area:
+     osd->DrawRectangle(xp06, yp08, xp07 - 1, yp09 - 1, frameColor);
+     osd->DrawRectangle(xp08, yp08, xp09 - 1, yp09 - 1, frameColor);
+     osd->DrawRectangle(xp10, yp08, xp11 - 1, yp09 - 1, frameColor);
+     osd->DrawRectangle(xp12, yp08, xp13 - 1, yp09 - 1, Theme.Color(clrDateBg));
+     osd->DrawRectangle(xp14, yp08, xp14 + lineHeight / 2 - 1, yp09 - 1, frameColor);
+     osd->DrawRectangle(xp14 + lineHeight / 2, yp08 + lineHeight / 2, xp15 - 1, yp09 - 1, clrTransparent);
+     osd->DrawEllipse  (xp14 + lineHeight / 2, yp08, xp15 - 1, yp09 - 1, frameColor, 5);
+     }
+}
+
+cSkinLCARSDisplayReplay::~cSkinLCARSDisplayReplay()
+{
+  delete osd;
+}
+
+void cSkinLCARSDisplayReplay::DrawDate(void)
+{
+  cString s = DayDateTime();
+  if (!*lastDate || strcmp(s, lastDate)) {
+     osd->DrawText(xp12, yp08, s, Theme.Color(clrDateFg), Theme.Color(clrDateBg), cFont::GetFont(fontOsd), xp13 - xp12, lineHeight, taRight | taBorder);
+     lastDate = s;
+     }
+}
+
+void cSkinLCARSDisplayReplay::DrawTrack(void)
+{
+  cDevice *Device = cDevice::PrimaryDevice();
+  const tTrackId *Track = Device->GetTrack(Device->GetCurrentAudioTrack());
+  if (!Track && *lastTrackId.description || Track && strcmp(lastTrackId.description, Track->description)) {
+     osd->DrawText(xp03, yp04, Track ? Track->description : "", Theme.Color(clrTrackName), Theme.Color(clrBackground), cFont::GetFont(fontOsd), xp07 - xp03);
+     strn0cpy(lastTrackId.description, Track ? Track->description : "", sizeof(lastTrackId.description));
+     }
+}
+
+void cSkinLCARSDisplayReplay::SetRecording(const cRecording *Recording)
+{
+  const cRecordingInfo *RecordingInfo = Recording->Info();
+  SetTitle(RecordingInfo->Title());
+  osd->DrawText(xp03, yp01 - lineHeight, RecordingInfo->ShortText(), Theme.Color(clrEventShortText), Theme.Color(clrBackground), cFont::GetFont(fontSml), xp13 - xp03);
+  osd->DrawText(xp00, yp00, ShortDateString(Recording->Start()), Theme.Color(clrReplayFrameFg), frameColor, cFont::GetFont(fontOsd), xp02 - xp00, 0, taTop | taRight | taBorder);
+  osd->DrawText(xp00, yp01 - lineHeight, TimeString(Recording->Start()), Theme.Color(clrReplayFrameFg), frameColor, cFont::GetFont(fontOsd), xp02 - xp00, 0, taBottom | taRight | taBorder);
+}
+
+void cSkinLCARSDisplayReplay::SetTitle(const char *Title)
+{
+  osd->DrawText(xp03, yp00, Title, Theme.Color(clrEventTitle), Theme.Color(clrBackground), cFont::GetFont(fontOsd), xp13 - xp03);
+}
+
+static const char *const *ReplaySymbols[2][2][5] = {
+  { { pause_xpm, srew_xpm, srew1_xpm, srew2_xpm, srew3_xpm },
+    { pause_xpm, sfwd_xpm, sfwd1_xpm, sfwd2_xpm, sfwd3_xpm }, },
+  { { play_xpm,  frew_xpm, frew1_xpm, frew2_xpm, frew3_xpm },
+    { play_xpm,  ffwd_xpm, ffwd1_xpm, ffwd2_xpm, ffwd3_xpm } }
+  };
+
+void cSkinLCARSDisplayReplay::SetMode(bool Play, bool Forward, int Speed)
+{
+  Speed = constrain(Speed, -1, 3);
+  cBitmap bm(ReplaySymbols[Play][Forward][Speed + 1]);
+  osd->DrawBitmap(xp01 - bm.Width() / 2, (yp02 + yp03 - bm.Height()) / 2, bm, Theme.Color(clrReplayFrameFg), frameColor);
+}
+
+void cSkinLCARSDisplayReplay::SetProgress(int Current, int Total)
+{
+  cProgressBar pb(xp13 - xp03, lineHeight, Current, Total, marks, Theme.Color(clrReplayProgressSeen), Theme.Color(clrReplayProgressRest), Theme.Color(clrReplayProgressSelected), Theme.Color(clrReplayProgressMark), Theme.Color(clrReplayProgressCurrent));
+  osd->DrawBitmap(xp03, yp02, pb);
+}
+
+void cSkinLCARSDisplayReplay::SetCurrent(const char *Current)
+{
+  const cFont *font = cFont::GetFont(fontOsd);
+  int w = font->Width(Current) + 10;
+  osd->DrawText(xp03, yp03 - lineHeight, Current, Theme.Color(clrReplayPosition), Theme.Color(clrBackground), font, w, 0, taLeft);
+}
+
+void cSkinLCARSDisplayReplay::SetTotal(const char *Total)
+{
+  const cFont *font = cFont::GetFont(fontOsd);
+  int w = font->Width(Total) + 10;
+  osd->DrawText(xp13 - w, yp03 - lineHeight, Total, Theme.Color(clrReplayPosition), Theme.Color(clrBackground), font, w, 0, taRight);
+}
+
+void cSkinLCARSDisplayReplay::SetJump(const char *Jump)
+{
+  osd->DrawText(xp06, yp08, Jump, Theme.Color(clrReplayJumpFg), Jump ? Theme.Color(clrReplayJumpBg) : frameColor, cFont::GetFont(fontOsd), xp07 - xp06, 0, taCenter);
+}
+
+void cSkinLCARSDisplayReplay::SetMessage(eMessageType Type, const char *Text)
+{
+  if (Text) {
+     osd->SaveRegion(xp06, yp08, xp13 - 1, yp09 - 1);
+     osd->DrawText(xp06, yp08, Text, Theme.Color(clrMessageStatusFg + 2 * Type), Theme.Color(clrMessageStatusBg + 2 * Type), cFont::GetFont(fontSml), xp13 - xp06, 0, taCenter);
+     }
+  else
+     osd->RestoreRegion();
+}
+
+void cSkinLCARSDisplayReplay::Flush(void)
+{
+  if (!modeOnly) {
+     DrawDate();
+     DrawTrack();
+     }
+  osd->Flush();
+}
+
+// --- cSkinLCARSDisplayVolume -----------------------------------------------
+
+class cSkinLCARSDisplayVolume : public cSkinDisplayVolume {
+private:
+  cOsd *osd;
+  int x0, x1, x2, x3, x4, x5, x6, x7;
+  int y0, y1;
+  tColor frameColor;
+  int mute;
+public:
+  cSkinLCARSDisplayVolume(void);
+  virtual ~cSkinLCARSDisplayVolume();
+  virtual void SetVolume(int Current, int Total, bool Mute);
+  virtual void Flush(void);
+  };
+
+cSkinLCARSDisplayVolume::cSkinLCARSDisplayVolume(void)
+{
+  const cFont *font = cFont::GetFont(fontOsd);
+  int lineHeight = font->Height();
+  frameColor = Theme.Color(clrVolumeFrame);
+  mute = -1;
+  x0 = 0;
+  x1 = lineHeight / 2;
+  x2 = lineHeight;
+  x3 = x2 + Gap;
+  x7 = cOsd::OsdWidth();
+  x6 = x7 - lineHeight / 2;
+  x5 = x6 - lineHeight / 2;
+  x4 = x5 - Gap;
+  y0 = 0;
+  y1 = lineHeight;
+  osd = CreateOsd(cOsd::OsdLeft(), cOsd::OsdTop() + cOsd::OsdHeight() - y1, x0, y0, x7 - 1, y1 - 1);
+  osd->DrawRectangle(x0, y0, x7 - 1, y1 - 1, Theme.Color(clrBackground));
+  osd->DrawRectangle(x0, y0, x1 - 1, y1 - 1, clrTransparent);
+  osd->DrawEllipse  (x0, y0, x1 - 1, y1 - 1, frameColor, 7);
+  osd->DrawRectangle(x1, y0, x2 - 1, y1 - 1, frameColor);
+  osd->DrawRectangle(x3, y0, x4 - 1, y1 - 1, frameColor);
+  osd->DrawRectangle(x5, y0, x6 - 1, y1 - 1, frameColor);
+  osd->DrawRectangle(x6, y0, x7 - 1, y1 - 1, clrTransparent);
+  osd->DrawEllipse  (x6, y0, x7 - 1, y1 - 1, frameColor, 5);
+}
+
+cSkinLCARSDisplayVolume::~cSkinLCARSDisplayVolume()
+{
+  delete osd;
+}
+
+void cSkinLCARSDisplayVolume::SetVolume(int Current, int Total, bool Mute)
+{
+  int xl = x3 + TextSpacing;
+  int xr = x4 - TextSpacing;
+  int yt = y0 + TextFrame;
+  int yb = y1 - TextFrame;
+  if (mute != Mute) {
+     osd->DrawRectangle(x3, y0, x4 - 1, y1 - 1, frameColor);
+     mute = Mute;
+     }
+  cBitmap bm(Mute ? mute_xpm : volume_xpm);
+  osd->DrawBitmap(xl, y0 + (y1 - y0 - bm.Height()) / 2, bm, Theme.Color(clrVolumeSymbol), frameColor);
+  if (!Mute) {
+     xl += bm.Width() + TextSpacing;
+     int w = (y1 - y0) / 3;
+     int d = TextFrame;
+     int n = (xr - xl + d) / (w + d);
+     int x = xr - n * (w + d);
+     tColor Color = Theme.Color(clrVolumeBarLower);
+     for (int i = 0; i < n; i++) {
+         if (Total * i >= Current * n)
+            Color = Theme.Color(clrVolumeBarUpper);
+         osd->DrawRectangle(x, yt, x + w - 1, yb - 1, Color);
+         x += w + d;
+         }
+     }
+}
+
+void cSkinLCARSDisplayVolume::Flush(void)
+{
+  osd->Flush();
+}
+
+// --- cSkinLCARSDisplayTracks -----------------------------------------------
+
+class cSkinLCARSDisplayTracks : public cSkinDisplayTracks {
+private:
+  cOsd *osd;
+  int xt00, xt01, xt02, xt03, xt04, xt05, xt06, xt07, xt08, xt09, xt10, xt11, xt12;
+  int yt00, yt01, yt02, yt03, yt04, yt05, yt06, yt07;
+  int lineHeight;
+  tColor frameColor;
+  int currentIndex;
+  static cBitmap bmAudioLeft, bmAudioRight, bmAudioStereo;
+  void SetItem(const char *Text, int Index, bool Current);
+public:
+  cSkinLCARSDisplayTracks(const char *Title, int NumTracks, const char * const *Tracks);
+  virtual ~cSkinLCARSDisplayTracks();
+  virtual void SetTrack(int Index, const char * const *Tracks);
+  virtual void SetAudioChannel(int AudioChannel);
+  virtual void Flush(void);
+  };
+
+cBitmap cSkinLCARSDisplayTracks::bmAudioLeft(audioleft_xpm);
+cBitmap cSkinLCARSDisplayTracks::bmAudioRight(audioright_xpm);
+cBitmap cSkinLCARSDisplayTracks::bmAudioStereo(audiostereo_xpm);
+
+cSkinLCARSDisplayTracks::cSkinLCARSDisplayTracks(const char *Title, int NumTracks, const char * const *Tracks)
+{
+  const cFont *font = cFont::GetFont(fontOsd);
+  lineHeight = font->Height();
+  frameColor = Theme.Color(clrTrackFrameBg);
+  currentIndex = -1;
+  xt00 = 0;
+  xt01 = xt00 + lineHeight / 2;
+  xt02 = xt01 + Gap;
+  xt03 = xt00 + 2 * lineHeight;
+  int ItemsWidth = font->Width(Title) + xt03 - xt02;
+  for (int i = 0; i < NumTracks; i++)
+      ItemsWidth = max(ItemsWidth, font->Width(Tracks[i]) + 2 * TextFrame);
+  xt04 = xt02 + ItemsWidth;
+  xt05 = xt04 + Gap;
+  xt06 = xt04 + lineHeight;
+  xt07 = xt05 + lineHeight;
+  xt08 = xt07 + lineHeight;
+  xt09 = xt08 + Gap;
+  xt10 = xt09 + lineHeight / 2;
+  xt11 = xt10 + Gap;
+  xt12 = xt11 + lineHeight;
+  yt00 = 0;
+  yt01 = yt00 + lineHeight;
+  yt02 = yt01 + lineHeight;
+  yt03 = yt02 + Gap;
+  yt04 = yt03 + NumTracks * lineHeight + (NumTracks - 1) * Gap;
+  yt05 = yt04 + Gap;
+  yt06 = yt05 + lineHeight;
+  yt07 = yt06 + lineHeight;
+  while (yt07 > cOsd::OsdHeight()) {
+        yt04 -= lineHeight + Gap;
+        yt05 = yt04 + Gap;
+        yt06 = yt05 + lineHeight;
+        yt07 = yt06 + lineHeight;
+        }
+  osd = CreateOsd(cOsd::OsdLeft(), cOsd::OsdTop() + cOsd::OsdHeight() - yt07, xt00, yt00, xt12 - 1, yt07 - 1);
+  // The upper elbow:
+  osd->DrawRectangle(xt00, yt00, xt12 - 1, yt07 - 1, Theme.Color(clrBackground));
+  osd->DrawRectangle(xt00, yt00, xt03 - 1, yt02 - 1, clrTransparent);
+  osd->DrawEllipse  (xt00, yt00, xt03 - 1, yt02 - 1, frameColor, 2);
+  osd->DrawRectangle(xt03, yt00, xt04 - 1, yt02 - 1, frameColor);
+  osd->DrawRectangle(xt04, yt00, xt08 - 1, yt01 - 1, frameColor);
+  osd->DrawEllipse  (xt04, yt01, xt06 - 1, yt02 - 1, frameColor, -2);
+  osd->DrawRectangle(xt09, yt00, xt10 - 1, yt01 - 1, frameColor);
+  osd->DrawRectangle(xt11, yt00, xt11 + lineHeight / 2 - 1, yt01 - 1, frameColor);
+  osd->DrawRectangle(xt11 + lineHeight / 2, yt00, xt12 - 1, yt00 + lineHeight / 2 - 1, clrTransparent);
+  osd->DrawEllipse  (xt11 + lineHeight / 2, yt00, xt12 - 1, yt01 - 1, frameColor, 5);
+  osd->DrawText(xt03, yt00, Title, Theme.Color(clrTrackFrameFg), frameColor, font, xt04 - xt03, 0, taTop | taRight);
+  // The items:
+  for (int i = 0; i < NumTracks; i++)
+      SetItem(Tracks[i], i, false);
+  // The lower elbow:
+  osd->DrawRectangle(xt00, yt05, xt03 - 1, yt07 - 1, clrTransparent);
+  osd->DrawEllipse  (xt00, yt05, xt03 - 1, yt07 - 1, frameColor, 3);
+  osd->DrawRectangle(xt03, yt05, xt04 - 1, yt07 - 1, frameColor);
+  osd->DrawRectangle(xt04, yt06, xt08 - 1, yt07 - 1, frameColor);
+  osd->DrawEllipse  (xt04, yt05, xt06 - 1, yt06 - 1, frameColor, -3);
+  osd->DrawRectangle(xt09, yt06, xt10 - 1, yt07 - 1, frameColor);
+  osd->DrawRectangle(xt11, yt06, xt11 + lineHeight / 2 - 1, yt07 - 1, frameColor);
+  osd->DrawRectangle(xt11 + lineHeight / 2, yt06 + lineHeight / 2, xt12 - 1, yt07 - 1, clrTransparent);
+  osd->DrawEllipse  (xt11 + lineHeight / 2, yt06, xt12 - 1, yt07 - 1, frameColor, 5);
+}
+
+cSkinLCARSDisplayTracks::~cSkinLCARSDisplayTracks()
+{
+  delete osd;
+}
+
+void cSkinLCARSDisplayTracks::SetItem(const char *Text, int Index, bool Current)
+{
+  int y0 = yt03 + Index * (lineHeight + Gap);
+  int y1 = y0 + lineHeight;
+  if (y1 > yt04)
+     return;
+  tColor ColorFg, ColorBg;
+  if (Current) {
+     ColorFg = Theme.Color(clrTrackItemCurrentFg);
+     ColorBg = Theme.Color(clrTrackItemCurrentBg);
+     osd->DrawRectangle(xt00, y0, xt01 - 1, y1 - 1, frameColor);
+     osd->DrawRectangle(xt02, y0, xt04 - 1, y1 - 1, ColorBg);
+     osd->DrawRectangle(xt05, y0, xt05 + lineHeight / 2 - 1, y1 - 1, ColorBg);
+     osd->DrawEllipse  (xt05 + lineHeight / 2, y0, xt07 - 1, y1 - 1, ColorBg, 5);
+     currentIndex = Index;
+     }
+  else {
+     ColorFg = Theme.Color(clrTrackItemFg);
+     ColorBg = Theme.Color(clrTrackItemBg);
+     osd->DrawRectangle(xt00, y0, xt01 - 1, y1 - 1, frameColor);
+     osd->DrawRectangle(xt02, y0, xt04 - 1, y1 - 1, ColorBg);
+     if (currentIndex == Index)
+        osd->DrawRectangle(xt05, y0, xt07 - 1, y1 - 1, Theme.Color(clrBackground));
+     }
+  const cFont *font = cFont::GetFont(fontOsd);
+  osd->DrawText(xt02, y0, Text, ColorFg, ColorBg, font, xt04 - xt02, y1 - y0, taTop | taLeft | taBorder);
+}
+
+void cSkinLCARSDisplayTracks::SetTrack(int Index, const char * const *Tracks)
+{
+  if (currentIndex >= 0)
+     SetItem(Tracks[currentIndex], currentIndex, false);
+  SetItem(Tracks[Index], Index, true);
+}
+
+void cSkinLCARSDisplayTracks::SetAudioChannel(int AudioChannel)
+{
+  cBitmap *bm = NULL;
+  switch (AudioChannel) {
+    case 0: bm = &bmAudioStereo; break;
+    case 1: bm = &bmAudioLeft;   break;
+    case 2: bm = &bmAudioRight;  break;
+    default: ;
+    }
+  if (bm)
+     osd->DrawBitmap(xt04 - bm->Width(), (yt06 + yt07 - bm->Height()) / 2, *bm, Theme.Color(clrTrackFrameFg), frameColor);
+  else
+     osd->DrawRectangle(xt03, yt06, xt04 - 1, yt07 - 1, frameColor);
+}
+
+void cSkinLCARSDisplayTracks::Flush(void)
+{
+  osd->Flush();
+}
+
+// --- cSkinLCARSDisplayMessage ----------------------------------------------
+
+class cSkinLCARSDisplayMessage : public cSkinDisplayMessage {
+private:
+  cOsd *osd;
+  int x0, x1, x2, x3, x4, x5, x6, x7;
+  int y0, y1;
+public:
+  cSkinLCARSDisplayMessage(void);
+  virtual ~cSkinLCARSDisplayMessage();
+  virtual void SetMessage(eMessageType Type, const char *Text);
+  virtual void Flush(void);
+  };
+
+cSkinLCARSDisplayMessage::cSkinLCARSDisplayMessage(void)
+{
+  const cFont *font = cFont::GetFont(fontOsd);
+  int lineHeight = font->Height();
+  x0 = 0;
+  x1 = lineHeight / 2;
+  x2 = lineHeight;
+  x3 = x2 + Gap;
+  x7 = cOsd::OsdWidth();
+  x6 = x7 - lineHeight / 2;
+  x5 = x6 - lineHeight / 2;
+  x4 = x5 - Gap;
+  y0 = 0;
+  y1 = lineHeight;
+  osd = CreateOsd(cOsd::OsdLeft(), cOsd::OsdTop() + cOsd::OsdHeight() - y1, x0, y0, x7 - 1, y1 - 1);
+}
+
+cSkinLCARSDisplayMessage::~cSkinLCARSDisplayMessage()
+{
+  delete osd;
+}
+
+void cSkinLCARSDisplayMessage::SetMessage(eMessageType Type, const char *Text)
+{
+  tColor ColorFg = Theme.Color(clrMessageStatusFg + 2 * Type);
+  tColor ColorBg = Theme.Color(clrMessageStatusBg + 2 * Type);
+  osd->DrawRectangle(x0, y0, x7 - 1, y1 - 1, Theme.Color(clrBackground));
+  osd->DrawRectangle(x0, y0, x1 - 1, y1 - 1, clrTransparent);
+  osd->DrawEllipse  (x0, y0, x1 - 1, y1 - 1, ColorBg, 7);
+  osd->DrawRectangle(x1, y0, x2 - 1, y1 - 1, ColorBg);
+  osd->DrawText(x3, y0, Text, ColorFg, ColorBg, cFont::GetFont(fontOsd), x4 - x3, 0, taCenter);
+  osd->DrawRectangle(x5, y0, x6 - 1, y1 - 1, ColorBg);
+  osd->DrawRectangle(x6, y0, x7 - 1, y1 - 1, clrTransparent);
+  osd->DrawEllipse  (x6, y0, x7 - 1, y1 - 1, ColorBg, 5);
+}
+
+void cSkinLCARSDisplayMessage::Flush(void)
+{
+  osd->Flush();
+}
+
+// --- cSkinLCARS ------------------------------------------------------------
+
+cSkinLCARS::cSkinLCARS(void)
+:cSkin("lcars", &::Theme)
+{
+}
+
+const char *cSkinLCARS::Description(void)
+{
+  return "LCARS";
+}
+
+cSkinDisplayChannel *cSkinLCARS::DisplayChannel(bool WithInfo)
+{
+  return new cSkinLCARSDisplayChannel(WithInfo);
+}
+
+cSkinDisplayMenu *cSkinLCARS::DisplayMenu(void)
+{
+  return new cSkinLCARSDisplayMenu;
+}
+
+cSkinDisplayReplay *cSkinLCARS::DisplayReplay(bool ModeOnly)
+{
+  return new cSkinLCARSDisplayReplay(ModeOnly);
+}
+
+cSkinDisplayVolume *cSkinLCARS::DisplayVolume(void)
+{
+  return new cSkinLCARSDisplayVolume;
+}
+
+cSkinDisplayTracks *cSkinLCARS::DisplayTracks(const char *Title, int NumTracks, const char * const *Tracks)
+{
+  return new cSkinLCARSDisplayTracks(Title, NumTracks, Tracks);
+}
+
+cSkinDisplayMessage *cSkinLCARS::DisplayMessage(void)
+{
+  return new cSkinLCARSDisplayMessage;
+}
diff --git a/skinlcars.h b/skinlcars.h
new file mode 100644 (file)
index 0000000..7306d12
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * skinlcars.h: A VDR skin with Star Trek's "LCARS" layout
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: skinlcars.h 1.1 2012/04/15 13:17:35 kls Exp $
+ */
+
+#ifndef __SKINLCARS_H
+#define __SKINLCARS_H
+
+#include "skins.h"
+
+class cSkinLCARS : public cSkin {
+public:
+  cSkinLCARS(void);
+  virtual const char *Description(void);
+  virtual cSkinDisplayChannel *DisplayChannel(bool WithInfo);
+  virtual cSkinDisplayMenu *DisplayMenu(void);
+  virtual cSkinDisplayReplay *DisplayReplay(bool ModeOnly);
+  virtual cSkinDisplayVolume *DisplayVolume(void);
+  virtual cSkinDisplayTracks *DisplayTracks(const char *Title, int NumTracks, const char * const *Tracks);
+  virtual cSkinDisplayMessage *DisplayMessage(void);
+  };
+
+#endif //__SKINLCARS_H