/**
 *  GraphTFT plugin for the Video Disk Recorder 
 * 
 *  display.c - A plugin for the Video Disk Recorder
 *
 *  (c) 2004 Lars Tegeler, Sascha Volkenandt
 *  (c) 2006-2008 Jörg Wendel
 *
 * This code is distributed under the terms and conditions of the
 * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
 *
 * $Id: display.c,v 1.12 2007/12/03 19:58:20 root Exp $
 * 
 **/

//***************************************************************************
// Includes
//***************************************************************************

#include <algorithm>
#include <sstream>

#include <vdr/menu.h>
#include <vdr/eitscan.h>

#include <display.h>
#include <theme.h>
#include <setup.h>

// the renderdevices

#ifdef HAVE_IMLIB 
# include "fbrenderer.h"
# include "dvbrenderer.h"
# include "dmyrenderer.h"
#endif

#ifdef HAVE_DFB
# include "dfbrenderer.h"
#endif

//***************************************************************************
// Object
//***************************************************************************

cGraphTFTDisplay::cGraphTFTDisplay(const char* aSyntaxVersion)
{
   syntaxVersion = aSyntaxVersion;
   versionReported = no;
   startedAt = time(0);

   // init renderer

   _render = 0;
   isDummyRender = no;
   
   // init thread stuff

   _active = no;
   comThread = 0;
   touchThread = 0;

   // init contents

   currentSection = 0;
   lastSection = 0;
   _eventsReady = no;
   _menu.charInTabs[0] = _menu.charInTabs[1] = _menu.charInTabs[2] = 
      _menu.charInTabs[3] = _menu.charInTabs[4] = 
      _menu.charInTabs[5] = _menu.charInTabs[6] =  0;
   _mode = NormalView;
   _sectionName = "";
   channelType = ctTv;
   _channel = 0;
   _presentChannel = 0;
   _presentEvent = 0;
   _followingEvent = 0;
   _volume = cDevice::CurrentVolume();
   _mute = cDevice::PrimaryDevice()->IsMute();
   _showRecording = 0;
   _replay.control = 0;
   _replay.name = "";
   _replay.fileName = "";
   _replay.lastMode = ModeUnknown;
   _menu.currentRowLast = na;
   _menu.currentRow = na;
   _menu.visibleRows = 0;
   _menu.topRow = na;
   _menu.lineHeight = na;
   displayActive = yes;
   forceNextDraw = yes;
   nextCyclicForce = 0;
   forceWriteThrough = no;

   _event = 0;
   _recording = "";
   _coverPath = "";

   _music.filename = "";
   _music.artist = "";
   _music.album = "";
   _music.genre = "";
   _music.comment = "";
   _music.year = na;
   _music.frequence = 0;
   _music.bitrate = 0;
   _music.smode = "";
   _music.index = 0;
   _music.count = 0;
   _music.status = "";
   _music.currentTrack = "";
   _music.loop = no;
   _music.shuffle = no;
   _music.shutdown = no;
   _music.recording = no;
   _music.rating = 0;
   _music.lyrics = no;
   _music.copy = no;
   _music.timer = no;

   // snapshot

   snapshotPending = no;

   // calibration stuff

   calibration.cursorX = 0;
   calibration.cursorY = 0;
   calibration.instruction = "";
   calibration.info = "";
   calibration.state = csUnknown;
   mouseX = 0;
   mouseY = 0;
   mouseKey = 0;
   touchMenu = 0;
   touchMenuHideTime = 0;
   touchMenuHideAt = 0;

   // init pbp stuff

   _pbpActive = false;
}

cGraphTFTDisplay::~cGraphTFTDisplay() 
{
   Stop();

   if (touchThread) delete touchThread;
   if (comThread) delete comThread;
   if (_render) delete _render;
}

//***************************************************************************
// Init
//***************************************************************************

int cGraphTFTDisplay::Init(const char* dev, const char* cfgDir, int port)
{
   char* pos = 0;
   int devnum = na;

#ifdef HAVE_DFB

   if ((pos = strstr(dev, "directFB")))
   {
      devnum = 0;
      _render = new DFbRenderer(GraphTFTSetup.xOffset, GraphTFTSetup.yOffset,
                                GraphTFTSetup.width, GraphTFTSetup.height,
                                GraphTFTSetup.PluginConfPath, GraphTFTSetup.Iso2Utf,
                                Thms::theTheme->getDir());
   }

#endif // HAVE_DFB

#ifdef HAVE_IMLIB

   if (!_render)
   {
      if ((pos = strstr(dev, "/dev/fb")))
      {
         devnum = atoi(pos+7);
         _render = new FbRenderer(GraphTFTSetup.xOffset, GraphTFTSetup.yOffset,
                                  GraphTFTSetup.width, GraphTFTSetup.height,
                                  GraphTFTSetup.PluginConfPath, GraphTFTSetup.Iso2Utf,
                                  Thms::theTheme->getDir());
      }

      else if ((pos = strstr(dev, "vdr/")))
      {    
         devnum = atoi(pos+4);

         // DVB uses hardcoded size instead of setup values

         if (GraphTFTSetup.width != 720 ||  GraphTFTSetup.height != 576)
            tell(0, "Info: Ignoring configured values for display width/heigth, forcing "
                 "720/576 for DVB device!");

         _render = new DvbRenderer(GraphTFTSetup.xOffset, GraphTFTSetup.yOffset,
                                   720, 576,
                                   GraphTFTSetup.PluginConfPath, GraphTFTSetup.Iso2Utf,
                                   Thms::theTheme->getDir());
      }

      else if (strcmp(dev, "none") == 0)
      {
         isDummyRender = yes;
         _render = new DummyRenderer(GraphTFTSetup.xOffset, GraphTFTSetup.yOffset,
                                     GraphTFTSetup.width, GraphTFTSetup.height,
                                     GraphTFTSetup.PluginConfPath, GraphTFTSetup.Iso2Utf,
                                     Thms::theTheme->getDir());
      }
      else
      {
         tell(0, "Ignoring unexpected device '%s'", dev);
      }

#ifdef WITH_TCP_COM

      // comthread uses theme width/height instead of display width/height

      comThread = new ComThread(this, 
                                Thms::theTheme->getWidth(), 
                                Thms::theTheme->getHeight());

      if (comThread->init(_render, port) != success)
      {
         tell(0, "Can't establish listener, tcp communication not available!");
         delete comThread;
         comThread = 0;
      }
      else
      {
         comThread->Start();
      }

#endif // WITH_TCP_COM
   }

#endif // HAVE_IMLIB

#ifdef WITH_TOUCH

   // touch thread ..

   touchThread = new cTouchThread(this);
   touchThread->setDevice(GraphTFTSetup.touchDevice);

   if (touchThread->open() != success)
   {
      tell(0, "Can't establish touch thread, touch panel not available!");
      delete touchThread;
      touchThread = 0;
   }
   else
   {
      touchThread->setSetting(&GraphTFTSetup.touchSettings);
      touchThread->Start();
   }

#endif

   // either the com thread or a device is needed

   if (!comThread && devnum == na)
      return fail;

   // apply settings 

   _render->setBorder(GraphTFTSetup.xBorder, GraphTFTSetup.yBorder);
   _render->setProperties(GraphTFTSetup.xOffset, GraphTFTSetup.yOffset,
                          Thms::theTheme->getWidth(), Thms::theTheme->getHeight(),
                          GraphTFTSetup.Iso2Utf, Thms::theTheme->getDir());

   // initialize the renderer/device

   if (_render->init(devnum) != 0)
      return fail;

   // Show the start image

   _render->setPlayMode(no);

   cDisplayItem::setRenderer(_render);
   cDisplayItem::setVdrStatus(this);
   setupChanged();

   Start();

   return success;
}

//***************************************************************************
// Broadcast
//***************************************************************************

void cGraphTFTDisplay::broadcast(int force)
{
   forceNextDraw = force;
   _doUpdate.Broadcast();
}

//***************************************************************************
// Set Mode
//***************************************************************************

int cGraphTFTDisplay::setMode(DisplayMode mode, const char* menuName, int force)
{ 
   if (_mode == ModeCalibration && !force)
      return done;

   if (_mode != mode || (menuName && _sectionName != menuName))
   {
      _mode = mode;
      _sectionName = menuName ? menuName : "Menu";

      tell(0, "Mode is set to (0x%X), menu section to '%s'", 
        _mode, _sectionName.c_str());

      _doUpdate.Broadcast();
   }

   return done;
}

//***************************************************************************
// Setup Changed
//***************************************************************************

void cGraphTFTDisplay::setupChanged()
{
   versionReported = no;
   logDevice = GraphTFTSetup.LogDevice;
   logLevel = GraphTFTSetup.Level;

   if (comThread)
      comThread->setJpegQuality(GraphTFTSetup.JpegQuality);

   if (touchThread)
      touchThread->setDevice(GraphTFTSetup.touchDevice, yes);

   if (_render)
   {
      _render->flushCache();
      _render->setBorder(GraphTFTSetup.xBorder, GraphTFTSetup.yBorder);
      _render->setProperties(GraphTFTSetup.xOffset, GraphTFTSetup.yOffset,
                             Thms::theTheme->getWidth(), Thms::theTheme->getHeight(),
                             GraphTFTSetup.Iso2Utf,
                             Thms::theTheme->getDir());
      _render->setFontPath(Thms::theTheme->getFontPath());
   }
}

//***************************************************************************
// Switch/Set Calibrate
//***************************************************************************

void cGraphTFTDisplay::switchCalibrate(int state)
{
   if (isMode(ModeCalibration))
      setCalibrate(off);
   else
      setCalibrate(on, state);
}

void cGraphTFTDisplay::setCalibrate(int active, int state)
{
   static int lastActive = no;
   static cGraphTFTService::DisplayMode lastMode = cGraphTFTDisplay::NormalView;
   static string lastSection = "";
   
   if (active != lastActive)
   {
      lastActive = active;
      tell(0, "Info: %s calibration mode", active ? "starting" : "stopping");
   }

   calibration.settings = GraphTFTSetup.touchSettings;

   if (state == csUnknown && touchThread)
      touchThread->setCalibrate(active);

   if (active)
   {
      // store actual mode

      lastMode = _mode;
      lastSection = _sectionName;
      setMode(ModeCalibration, 0, /*force*/ true);

      calibration.state = state;

      if (state == csUnknown)
      {
         calibration.settings.swapXY = no;
         calibration.info = "calibration started";

         if (touchThread)
            touchThread->resetSetting();
      }
      else
      {
         calibration.instruction = "verify by touching ...";
         calibration.info = "testing calibration";
      }

      calibrateTouch(0, 0);
   }
   else
   {
      setMode(lastMode, lastSection.c_str(), /*force*/ true);
   }

   broadcast(yes);
}

//***************************************************************************
// Music Plugin Interface
//***************************************************************************

void cGraphTFTDisplay::musicAddPlaylistItem(const char* item, int index)
{
   if (index == 0)
      _music.tracks.clear();

   _music.tracks.push_back(item);
}

void cGraphTFTDisplay::setMusicPlayerState(cTftCS::MusicServicePlayerInfo* p)
{
   if (!p) return;

   _music.filename = p->filename;
   _music.artist = p->artist;
   _music.album = p->album;
   _music.genre = p->genre;
   _music.comment = p->comment;
   _music.year = p->year > 0 ? Str::toStr(p->year) : "--";
   _music.frequence = p->frequence;
   _music.bitrate = p->bitrate;
   _music.smode = p->smode;
   _music.index = p->index;
   _music.count = p->count;
   _music.status = p->status;
   _music.currentTrack = p->currentTrack;
   _music.loop = p->loop;
   _music.shuffle = p->shuffle;
   _music.shutdown = p->shutdown;
   _music.recording = p->recording;
   _music.rating = p->rating;
   _music.lyrics = no;   // TODO
   _music.copy = no;     // TODO
   _music.timer = no;    // TODO
}

void cGraphTFTDisplay::setMusicPlayerHelpButtons(cTftCS::MusicServiceHelpButtons* p)
{
   _music.red = p->red;
   _music.green = p->green;
   _music.yellow = p->yellow;
   _music.blue = p->blue;
}

//***************************************************************************
// Get Channel By Name
//***************************************************************************

const cChannel* cGraphTFTDisplay::channelByName(const char* name)
{
   cChannel* channel;

   if (Str::isEmpty(name) || !isalnum(name[0]))
      return 0;

   for (channel = Channels.First(); channel; channel = Channels.Next(channel))
   {
      if (strcmp(channel->ShortName(), name) == 0 || strcmp(channel->Name(), name) == 0)
      {
         tell(6, "found channel '%s'", channel->ShortName());
         return channel;
      }
   }
   
   return 0;
}

//***************************************************************************
// Stop
//***************************************************************************

void cGraphTFTDisplay::Stop()
{
   if (_active) 
   {
      _active = no;
      _doUpdate.Broadcast();
      Cancel(3);
   }
}

//***************************************************************************
// 
//***************************************************************************

void cGraphTFTDisplay::Pbp() 
{
   tell(1, "Pbp()");

   if (!_pbpActive)
   {
      PbpInit();
      PbpStart();
   }
   else
   {
      PbpStop();
   }
}

//***************************************************************************
// 
//***************************************************************************

void cGraphTFTDisplay::PbpRestart()
{
   tell(1, "PbpRestart()");

   if (_pbpActive)
   {
      PbpStop();
      PbpStart();
   }
}

//***************************************************************************
// 
//***************************************************************************

void cGraphTFTDisplay::PbpSwitch(bool Back) 
{
   const cChannel* newChan = 0;

   tell(1, "PbpSwitch()");

   if (_pbpActive)
   {
      PbpStop();

      if (!Back)
      {
         newChan = cDevice::CurrentChannel() != 0 ? Channels.GetByNumber(cDevice::CurrentChannel()) : NULL;
      }

      cDevice::PrimaryDevice()->SwitchChannel(_chan, true);

      if (!Back)
      {
         if (newChan != NULL)
         {
            _chan = newChan;
#if APIVERSNUM >= 10500
            _dev = cDevice::GetDevice(_chan, 1, false);
#else
            _dev = cDevice::GetDevice(_chan, 1);
#endif
            PbpStart();
         }
      }
   }
}

//***************************************************************************
// 
//***************************************************************************

void cGraphTFTDisplay::PbpUpDown(bool Up) 
{
   tell(1, "display.c: PbpUpDown()");

   if (_pbpActive)
   {
      // Set new Channel and device
      // but not today ;-)

      if (Up)
      {
         // set TFT Channel one up
      }
      else
      {
         // and here down
      }

      // Now make a restart

      // PbpRestart();
   }
}

//***************************************************************************
// 
//***************************************************************************

bool cGraphTFTDisplay::PbpIsRunning()
{
   tell(1, "PbpIsRunning() (%s)", _pbpActive ? "yes" : "no");

   return _pbpActive;
}

//***************************************************************************
// 
//***************************************************************************

void cGraphTFTDisplay::PbpInit() 
{
   tell(1, "PbpInit()");

   _chan = cDevice::CurrentChannel() != 0 ? Channels.GetByNumber(cDevice::CurrentChannel()) : 0;

   if (_chan)
#if APIVERSNUM >= 10500
      _dev = cDevice::GetDevice(_chan, 1, false);
#else
      _dev = cDevice::GetDevice(_chan, 1);
#endif

      //_dev = cDevice::ActualDevice();
}

//***************************************************************************
// 
//***************************************************************************

void cGraphTFTDisplay::PbpStart() 
{
   tell(1, "PbpStart() dev '%s', chan '%s'", 
     _dev  ? "yes" : "no", 
     _chan ? "yes" : "no");

   if (_dev && _chan)
   {
      tell(1, "Set PlayMode to Video");

      Stop();
      
      _render->setPlayMode(yes);
      _dev->SwitchChannel(_chan, false);

      // active transfer

      _transfer = new cGraphTFTTransfer(_render, _chan);
      _dev->AttachReceiver(_transfer);

      _pbpActive = true;
   }
}

//***************************************************************************
// 
//***************************************************************************

void cGraphTFTDisplay::PbpStop() 
{
   tell(1, "PbpStop()");

   if (_transfer) delete _transfer;

   _render->setPlayMode(no);
   _pbpActive = no;
   Start();
   _doUpdate.Broadcast();
}

//***************************************************************************
// Get Tabbed Text
//***************************************************************************

const char* cGraphTFTDisplay::GetTabbedText(const char* s, int Tab)
{
   if (!s)
      return 0;

   static char buffer[1000+TB];
   const char *a = s;
   const char *b = strchrnul(a, '\t');

   while (*b && Tab-- > 0) 
   {
      a = b + 1;
      b = strchrnul(a, '\t');
   }

   if (!*b)
      return (Tab <= 0) ? a : 0;

   unsigned int n = b - a;

   if (n >= sizeof(buffer))
      n = sizeof(buffer) - 1;

   strncpy(buffer, a, n);
   buffer[n] = 0;

   return buffer;
}

//***************************************************************************
// Action
//***************************************************************************

void cGraphTFTDisplay::Action()
{
   uint64_t updateIn;
   int n;

   tell(0,"GraphTFT plugin display thread started (pid=%d)", getpid());

   // display the start image

   _render->image(Thms::theTheme->getStartImage().c_str(), 0,0,0,0);
   _render->refresh();

   // and give the plugin time to collect some data AND give vdr some time to
   // finish initialization before acquiring the lock

   sleep(3);

   while (!Thms::theTheme->isInitialized())
      usleep(100000);

   _mutex.Lock();       // mutex gets ONLY unlocked when sleeping
   _active = yes;

   // main loop

   while (_active)
   {
      tell(2, "action loop");
      
      if (touchMenu)
      {
         if (touchMenuHideAt && cTimeMs::Now() > touchMenuHideAt-100)
            touchMenu = 0;

         forceNextDraw = yes;
      }

      if (GraphTFTSetup.redrawEvery && cTimeMs::Now() > nextCyclicForce)
      {
         nextCyclicForce = cTimeMs::Now() + SECONDS(GraphTFTSetup.redrawEvery);
         forceNextDraw = yes;
         forceWriteThrough = yes;
      }

      if (isModeNormal(_mode))
      {
         // do the work ...

         if (GraphTFTSetup.normalMode == "Standard")
            n = display(channelType == ctTv ? "NormalTV" : "NormalRadio");
         else
            n = display("Normal" + GraphTFTSetup.normalMode);
      }
      else
      {
         switch (_mode) 
         {
            case ReplayNormal:    n = display("ReplayNormal");  break;
            case ReplayMP3:       n = display("ReplayMP3");     break;
            case ReplayDVD:       n = display("ReplayDVD");     break;
            case ModeCalibration: n = display("Calibration");   break;
            case ModeMenu:        n = display(_sectionName);    break;
            default:              n = display("NormalTV");
         }
      }

      updateIn = SECONDS(60);  // the default

      if (currentSection)
      {
         updateIn = currentSection->getNextUpdateTime() - cTimeMs::Now();

         // auto hide of touch menu

         if (touchMenu && touchMenuHideAt)
         {
            if ((touchMenuHideAt - cTimeMs::Now()) < updateIn)
               updateIn = touchMenuHideAt - cTimeMs::Now();

            tell(2, "Autohide scheduled in %ldms", updateIn);
         }            
      }
         
      if (updateIn < 10)     // 10ms -> the minimum
         updateIn = 10;

      // can't calc this inline, due to a format string problem ... ?
      
      int s = updateIn/1000; int us = updateIn%1000;

      tell(1, "Displayed %d Items, schedule next "
           "update in %d,%03d seconds", n, s, us);

      _doUpdate.TimedWait(_mutex, updateIn);

      // snapshot
      
      if (snapshotPending)
         takeSnapshot();
   }

   isyslog("GraphTFT plugin display thread ended (pid=%d)", getpid());
}

//***************************************************************************
// Take Snapshot
//***************************************************************************

void cGraphTFTDisplay::takeSnapshot()
{
   char* path = 0;
   char* file = 0;

   snapshotPending = no;
   
   // it's better to wait half a second, 
   // giving the menu a chance to close ...

   _doUpdate.TimedWait(_mutex, 500);

   // tv view or replay running

   if (_replay.fileName.length())
   {
      const cRecording* replay;

      if ((replay = Recordings.GetByName(_replay.fileName.c_str())) && replay->Info())
         asprintf(&file, "%s", replay->Info()->Title());
   }
   else
   {
      if (_presentEvent)
         asprintf(&file, "%s", _presentEvent->Title());
   }

   if (!file)
   {
      Skins.Message(mtInfo, tr("Can't save snapshot, missing event information"));
      return ;
   }

   strreplace(file, '/', ' ');
   asprintf(&path, "%s/%s.jpg", GraphTFTSetup.snapshotPath, file);
   
   if (cDevice::PrimaryDevice()->GrabImageFile(path, yes, 
                                               GraphTFTSetup.snapshotQuality, 
                                               GraphTFTSetup.snapshotWidth, 
                                               GraphTFTSetup.snapshotHeight))
      Skins.Message(mtInfo, tr("Snapshot saved"));
   else
      Skins.Message(mtInfo, tr("Error saving snapshot"));

   _render->flushCache();
   free(path);
   free(file);
}     

//***************************************************************************
// Stopped Timer
//***************************************************************************

bool cGraphTFTDisplay::StoppedTimer(const char* Name)
{
   cTimer* timer = Timers.First();

   while (timer)
   {
      if (strcmp(Name, timer->File()) == 0)
         break;

      timer = Timers.Next(timer);
   }

   return timer == 0 || !timer->Recording();
}

//***************************************************************************
// Update Programme
//***************************************************************************

void cGraphTFTDisplay::UpdateProgramme()
{
   const cChannel* channel = Channels.GetByNumber(_channel);

   if (channel)
   {
      tell(3, "UpdateProgramme for channel '%s'", channel->Name());

      cMutexLock lock(&_mutex);

      const cEvent* present = _presentEvent;
      const cEvent* following = _followingEvent;

      _presentEvent = _followingEvent = 0;

      cSchedulesLock schedulesLock;
      const cSchedules* schedules = cSchedules::Schedules(schedulesLock);

      if (schedules) 
      {
         const cSchedule *schedule = schedules->GetSchedule(channel->GetChannelID());

         if (schedule) 
         {
            _presentEvent = schedule->GetPresentEvent();
            _followingEvent = schedule->GetFollowingEvent();
         }
      }

      if (present != _presentEvent || following != _followingEvent)
         updateGroup(groupChannel);
   }
}

//***************************************************************************
// Osd Channel Switch
//***************************************************************************

void cGraphTFTDisplay::ChannelSwitch(const cDevice* Device, int ChannelNumber) 
{
   tell(3, "ChannelSwitch on %p: %d", Device, ChannelNumber);

   if (Device->IsPrimaryDevice() 
       && !EITScanner.UsesDevice(Device) 
       && _channel != ChannelNumber 
       && cDevice::CurrentChannel() != _channel) 
   {
      const cChannel* channel;

      cMutexLock lock(&_mutex);
      _channel = ChannelNumber;
      _presentChannel = Channels.GetByNumber(_channel);

      if ((channel = Channels.GetByNumber(_channel)))
      {
         char *temp;
         unsigned int pos;

         asprintf(&temp, "%d %s", channel->Number(), channel->Name());
         _channelText = temp;

         if ((pos = _channelText.rfind(';')) != string::npos)
            _channelText.erase(pos);
         if ((pos = _channelText.rfind(',')) != string::npos)
            _channelText.erase(pos);

         free(temp);

         if (!isModeMenu(_mode)) 
         {
            switch (channel->Vpid()) 
            {
               case 0:
               case 1:
               case 0x1fff: channelType = ctRadio;  break;
               default:     channelType = ctTv;     break;
            }

            setMode(NormalView);
         }
      }

      updateGroup(groupChannel);
      _doUpdate.Broadcast();
   }
}

//***************************************************************************
// Osd Set Event
//***************************************************************************

void cGraphTFTDisplay::OsdSetEvent(const cEvent* event)
{
   tell(3, "OsdSetEvent: '%s'", event ? event->Title() : "");

   _event = event;
}

//***************************************************************************
// Osd Set Event
//***************************************************************************

void cGraphTFTDisplay::OsdSetRecording(const cRecording* recording)
{
   tell(2, "OsdSetRecording: '%s'", recording ? recording->Title() : "");

   _recording = recording ? recording->FileName() : "";
}

//***************************************************************************
// Osd Channel
//***************************************************************************

void cGraphTFTDisplay::OsdChannel(const char *Text) 
{
   // Elchi workaround still needed (i don't know) ..

   return ;  
}

//***************************************************************************
// Osd Programme
//***************************************************************************

void cGraphTFTDisplay::OsdProgramme(time_t PresentTime, const char* PresentTitle, 
                                    const char* PresentSubtitle, time_t FollowingTime, 
                                    const char* FollowingTitle, const char* FollowingSubtitle)
{
   // not used anymore
}

//***************************************************************************
// Set Volume
//***************************************************************************

void cGraphTFTDisplay::SetVolume(int, bool) 
{
   tell(3, "SetVolume to %d, muted is %s", 
     cDevice::CurrentVolume(), 
     cDevice::PrimaryDevice()->IsMute() ? "true" : "false");

   if (_volume != cDevice::CurrentVolume()
       || _mute != cDevice::PrimaryDevice()->IsMute()) 
   {
      cMutexLock lock(&_mutex);

      _volume = cDevice::CurrentVolume();
      _mute = cDevice::PrimaryDevice()->IsMute();

      updateGroup(groupVolume);
      _doUpdate.Broadcast();
   }
}

//***************************************************************************
// Recording
//***************************************************************************

void cGraphTFTDisplay::Recording(const cDevice* Device, const char* Name, 
                                 const char* FileName, bool On) 
{
   tell(3, "Recording %s to %p", Name, Device);

   cMutexLock lock(&_mutex);

   if (On) 
   {
      RecordingInfo info;

      info.device = Device;
      info.name = Name ? Name : "";
      _recordings.push_back(info);

      updateGroup(groupRecording);
      _doUpdate.Broadcast();
   } 
   else 
   {
      RecordingInfos::iterator it = _recordings.begin();

      for (; it != _recordings.end(); ++it) 
      {
         if ((*it).device == Device && StoppedTimer((*it).name.c_str())) 
         {
            tell(5, "deleting recording %s", (*it).name.c_str());
            _recordings.erase(it);
            break;
         }
      }

      if (_recordings.size())
         _showRecording = _showRecording % _recordings.size();

      updateGroup(groupRecording);
      _doUpdate.Broadcast();
   }
}

//***************************************************************************
// Replaying
//***************************************************************************

void cGraphTFTDisplay::Replaying(const cControl* Control, const char* Name,
                                 const char* FileName, bool On) 
{
   static const char* suffixes[] = { ".ogg", ".mpg", ".mpeg", ".mp3", ".avi", 0 };

   tell(2, "Replaying '%s' of '%s' file '%s'; control* is (%p)", 
        On ? "start" : "stop", 
        Name, Str::notNull(FileName),
        Control);

   cMutexLock lock(&_mutex);

   if (On)
   {
      unsigned int i, n;

      if (!Name) Name = "";

      _replay.control = (cControl*)Control;
      _replay.lastMode = _mode;
      setMode(ReplayNormal);
      _replay.fileName = Str::notNull(FileName);
      // OsdSetRecording(Recordings.GetByName(Str::notNull(FileName)));

      if (strlen(Name) > 6 && Name[0]=='[' && Name[3]==']' && Name[5]=='(') 
      {
         for (i = 6; Name[i]; ++i) 
         {
            if (Name[i] == ' ' && Name[i-1] == ')')
               break;
         }

         if (Name[i]) 
         { 
            // replaying mp3

            const char* name = skipspace(Name + i);
            _replay.name = *name ? name : tr("Unknown title");
            setMode(ReplayMP3);
         }
      } 
      else if (strcmp(Name, "DVD") == 0) 
      {
         _replay.name = Name;
         setMode(ReplayDVD);
      } 
      else if (strlen(Name) > 7) 
      {
         for (i = 0, n = 0; Name[i]; ++i) 
         {
            if (Name[i] == ' ' && Name[i-1] == ',' && ++n == 4)
               break;
         }

         if (Name[i]) 
         { 
            // replaying DVD

            const char *name = skipspace(Name + i);
            _replay.name = *name ? name : tr("Unknown title");
            replace(_replay.name.begin(), _replay.name.end(), '_', ' ');
            setMode(ReplayDVD);
         }
      }

      if (_mode == ReplayNormal) 
      {
         _replay.name = Name;

         if ((i = _replay.name.rfind('~')) != string::npos)
            _replay.name.erase(0, i + 1);
      }

      char* tmp;
      asprintf(&tmp, "%s", _replay.name.c_str());
      _replay.name = Str::allTrim(tmp);
      free(tmp);

      for (int l = 0; suffixes[l]; l++)
      {
         if ((i = _replay.name.rfind(suffixes[l])) != string::npos) 
            _replay.name.erase(i);
      }

      tell(2, "reformatted name is '%s'", _replay.name.c_str());
      updateGroup(groupReplay);
      _doUpdate.Broadcast();
   } 

   else 
   {
      _replay.control = 0;
      _replay.name = "";
      _replay.fileName = "";
 
      setMode(NormalView);
      // OsdSetRecording(0);

      _doUpdate.Broadcast();
   }
}

//***************************************************************************
// Osd Status Message
//***************************************************************************

void cGraphTFTDisplay::OsdStatusMessage(const char* Message) 
{
   tell(3, "OsdStatusMessage %s", Message);

   cMutexLock lock(&_mutex);

   _message = Message ? Message : "";
   
   updateGroup(groupMessage);
   _doUpdate.Broadcast();
}

//***************************************************************************
// Osd Title
//***************************************************************************

void cGraphTFTDisplay::OsdTitle(const char* Title) 
{
   tell(5, "OsdTitle %s", Title);

   if (Title)
   {
      unsigned int pos;
      cMutexLock lock(&_mutex);

      _message = "";
      _menu.title = Title;

      if ((pos = _menu.title.find('\t')) != string::npos)
         _menu.title.erase(pos);
   }
}

//***************************************************************************
// Osd Event Item (called after corresponding OsdItem!)
//***************************************************************************

void cGraphTFTDisplay::OsdEventItem(const cEvent* Event, const char* Text, 
                                    int Index, int Count)
{
   tell(2, "OsdEventItem '%s' at index (%d), count (%d)", 
        Event ? Event->Title() : "<null>", Index, Count);

   cMutexLock lock(&_mutex);

   if (_menu.items[Index].text != Text)
      tell(0, "Fatal: Item index don't match!");
   else
   {
      _menu.items[Index].event = Event;

      if (Event)
         _menu.items[Index].channel = Channels.GetByChannelID(Event->ChannelID());
   }

   if (Index == Count-1)
   {
      _eventsReady = yes;

      tell(2, "OsdEventItem Force update due to index/count (%d/%d)", Index, Count);

      updateGroup(groupMenu);
      _doUpdate.Broadcast();
   }
}

//***************************************************************************
// Osd Item
//***************************************************************************

void cGraphTFTDisplay::OsdItem(const char* Text, int Index) 
{
   tell(2, "OsdItem '%s' at index (%d)", Text, Index);

//    for (unsigned int i = 0; i < strlen(Text); i++)
//       tell(0, "(%d) '%c' (0x%x)", i, Text[i], (unsigned char)Text[i]);

   cMutexLock lock(&_mutex);

   if (Text)
   {
      MenuItem item;
      item.text = Text;
      item.type = itNormal;
      item.event = 0;
      item.channel = 0;
      item.tabCount = 0;
      int count = 1;
      char* pp;
      _menu.currentRow = na;

      const char* p = Text;

      while (*p)
      {
         if (*p == '\t')
            count++;
         p++;
      }

      _message = "";

      for (int i = 0; i < MaxTabs; ++i) 
      {
         const char* tp = GetTabbedText(Text, i);
         
         if (tp)
         {
            char* tab;
            int len;

            tab = strdup(tp);
            len = strlen(Str::allTrim(tab));

            // bigger than last row ?

            if (_menu.charInTabs[i] < len)
               _menu.charInTabs[i] = len;

            // detect partingLine

            if (i == 0 && strstr(tab, "-----") && (pp = strchr(tab, ' ')))
            {
               char* e;
               *pp = 0;
               pp++;

               item.type = itPartingLine;
               
               if ((e = strchr(pp, ' ')))
                  *e = 0;
              
               item.tabs[0] = Str::allTrim(pp);
            }

            else if (strstr(tab, "-----") && count < 2)
            {
               // arghdirector
               
               item.type = itPartingLine;
               item.tabs[0] = "";
            }
            else if (i == 1 && strstr(item.tabs[0].c_str(), "-----"))
            {
               char* p;

               // some special handling for lines of epgsearch plugin like
               // ---------------------------------- \t Die 24.10.2006 ------------------------

               item.type = itPartingLine;

               if ((p = strstr(tab, " ---")))
                  *p = 0;

               if (!strstr(tab, "-----"))
                  item.tabs[0] = Str::allTrim(tab);
               else
                  item.tabs[0] = "";

               tell(2, "Detected parting line '%s'", item.tabs[0].c_str());
            }
            else
            {
               item.tabs[i] = tab;

               // zwei teure lookups !!

               if (!item.channel)
                  item.channel = channelByName(tab);

               if (!item.recording)
               {
                  item.recording = Recordings.GetByName(tab);

                  // TODO, nicht klar ob es ein Zufallstreffer ist :(

                  if (item.recording)
                     _eventsReady = yes;
               }
            }

            tell(4, "tab (%d) '%s'", i, tp);
            item.tabCount++;
            free(tab);
         }
         else
            break;
      }

      tell(4, "adding item with (%d) tabs", item.tabCount);
      _menu.items.push_back(item);
   }
}

//***************************************************************************
// Osd Current Item
//***************************************************************************

void cGraphTFTDisplay::OsdCurrentItem(const char* Text)
{
   tell(2, "OsdCurrentItem '%s'", Text);

   if (!Text)
      return ;

   string text = Text;

   if (text != _menu.current)
   {
      int i;

      cMutexLock lock(&_mutex);

      _message = "";
      _menu.current = text;

      // lookup the item in the list and set the new current position

      for (i = 0; i < (int)_menu.items.size(); i++) 
      {
         if (_menu.items[i].text == _menu.current)
         {
            _menu.currentRow = i;

            updateGroup(groupMenu);
            _doUpdate.Broadcast();
            return ;
         }
      }

      // We don't have found the item, resume 
      //  old one with changed values ...

      if (_menu.currentRow < 0 || _menu.currentRow >= (int)_menu.items.size())
         return ;

      tell(3, "OsdCurrentItem: Text of current item changed to %s", Text);

#if __GNUC__ < 3
      MenuItem item = _menu.items[_menu.currentRow];
#else
      MenuItem item = _menu.items.at(_menu.currentRow);
#endif

      vector<MenuItem>::iterator tempIterator;
      tempIterator = _menu.items.begin();

      for (int i = 0; i < _menu.currentRow; i++)
         tempIterator++;

      tempIterator = _menu.items.erase(tempIterator);
      item.text = Text;

      for (int i = 0; i < MaxTabs; ++i) 
      {
         const char* tab = GetTabbedText(Text, i);

         if (tab)
            item.tabs[i] = tab;

         if (GetTabbedText(Text, i+1) == 0)
            break;
      }

      _menu.items.insert(tempIterator, item);
      updateGroup(groupMenu);
      _doUpdate.Broadcast();
   }
}

//***************************************************************************
// Osd Clear
//***************************************************************************

void cGraphTFTDisplay::OsdClear()
{
   tell(3, "OsdClear");

   cMutexLock lock(&_mutex);

   _menu.title = "";
   _menu.items.clear();
   _eventsReady = no;
   _menu.text = "";
   _menu.current = "";

   _menu.charInTabs[0] = _menu.charInTabs[1] = _menu.charInTabs[2] = 
      _menu.charInTabs[3] = _menu.charInTabs[4] = _menu.charInTabs[5] = 0;

   if (_message != "")
   {
      _message = "";
      updateGroup(groupMessage);
      _doUpdate.Broadcast();
   }
}

//***************************************************************************
// Osd Help Keys
//***************************************************************************

void cGraphTFTDisplay::OsdHelpKeys(const char* Red, const char* Green, 
                                   const char* Yellow, const char* Blue) 
{
   tell(3, "OsdHelpKeys: '%s', '%s', '%s', '%s'", Red, Green, Yellow, Blue);

   string red = Red ? Red : "";
   string green = Green ? Green : "";
   string yellow = Yellow ? Yellow : "";
   string blue = Blue ? Blue : "";

   if (_menu.buttons[0] != red ||
       _menu.buttons[1] != green ||
       _menu.buttons[2] != yellow ||
       _menu.buttons[3] != blue)
   {
      cMutexLock lock(&_mutex);

      _menu.buttons[0] = red;
      _menu.buttons[1] = green;
      _menu.buttons[2] = yellow;
      _menu.buttons[3] = blue;
      
      updateGroup(groupButton);
      _doUpdate.Broadcast();
   }
}

//***************************************************************************
// Osd Text Item
//***************************************************************************

void cGraphTFTDisplay::OsdTextItem(const char* Text, bool Scroll) 
{
   tell(3, "OsdTextItem: '%s' scroll: %d", Text, Scroll);

   if (Text)
   {
      cMutexLock lock(&_mutex);

      _menu.text = Text;
      _doUpdate.Broadcast();
   }
}

//***************************************************************************
// Osd Menu Display
//***************************************************************************

void cGraphTFTDisplay::OsdMenuDisplay(const char* kind)
{
   tell(3, "OsdMenuDisplay - kind '%s'", kind);

   cMutexLock lock(&_mutex);

   if (!isModeMenu(_mode)) 
      _menu.lastMode = _mode;
   
   setMode(ModeMenu, kind);
}

//***************************************************************************
// Osd Menu Destroy
//***************************************************************************

void cGraphTFTDisplay::OsdMenuDestroy()
{
   tell(3, "OsdMenuDestroy");

   cMutexLock lock(&_mutex);

   // all menues closed !

   if (isModeMenu(_mode))        // 'if' is needed for replay via extrecmenu
      setMode(_menu.lastMode); 
}

//***************************************************************************
// 
//***************************************************************************

cDisplayItem* cGraphTFTDisplay::getItemAt(int x, int y)
{
   // first check foreground items ..

   for (cDisplayItem* p = currentSection->getItems()->First(); p; 
        p = currentSection->getItems()->Next(p))
   {
      if (!p->Foreground() || !p->evaluateCondition())
         continue;

      if (p->OnClick() == "" && p->OnDblClick() == "" && p->OnUp() == "" && p->OnDown() == "")
         continue;

      if (x >= p->X() && x <= p->X()+p->Width() &&
          y >= p->Y() && y <= p->Y()+p->Height())
      {
         return p;
      }
   }

   // now the other ..

   for (cDisplayItem* p = currentSection->getItems()->First(); p; 
        p = currentSection->getItems()->Next(p))
   {
      if (p->Foreground() || !p->evaluateCondition())
         continue;

      if (p->OnClick() == "" && p->OnDblClick() == "" && p->OnUp() == "" && p->OnDown() == "")
         continue;

      if (x >= p->X() && x <= p->X()+p->Width() &&
          y >= p->Y() && y <= p->Y()+p->Height())
      {
         return p;
      }
   }

   return 0;
}

//***************************************************************************
// Mouse Event
//***************************************************************************

void cGraphTFTDisplay::mouseEvent(int x, int y, int button, int flag, int data)
{
   static int whipeDiff = 0;

   if (!currentSection)
      return ;

   mouseX = x;
   mouseY = y;
   mouseKey = button;

   cMutexLock lock(&_mutex);

   cDisplayItem* p = getItemAt(x, y);

   if (p)
      tell(2, "Mouse action on item %d [%s] at (%d/%d)", p->Item(),
           p->Text().c_str(), x, y);
   
   if (isMode(ModeCalibration))
   {
      calibrateTouch(x, y);
      updateGroup(groupCalibrate);
      broadcast(yes);

      if (calibration.state < csTest)
         return ;
   }

   if (button == cGraphTftComService::mbWheelUp)
   {
      if (!p || p->OnUp() == "")
         cRemote::Put(cKey::FromString("Up"));
      else
         processAction(p, p->OnUp());
   }

   else if (button == cGraphTftComService::mbWheelDown)
   {
      if (!p || p->OnDown() == "")
         cRemote::Put(cKey::FromString("Down"));
      else
         processAction(p, p->OnDown());
   }

   else if (button == cGraphTftComService::mbRight)
   {
      cRemote::Put(cKey::FromString("Back"));
   }

   else if (button == cGraphTftComService::mbLeft)
   {
      if (!p)
         return ;

      if (flag & cGraphTftComService::efVWhipe)
      {
         tell(3, "vertical whipe of (%d) pixel", data);

         whipeDiff += data;

         int step = abs(whipeDiff) / p->WhipeRes();

         if (step)
         {
            tell(3, "do step of (%d)", step);

            if (whipeDiff < 0)
            {
               if (p->OnDown() != "")
                  processAction(p, p->OnDown(), step);
            }
            else
            {
               if (p->OnUp() != "")
                  processAction(p, p->OnUp(), step);
            }

            whipeDiff = whipeDiff % p->WhipeRes();
         }
         
         return ;
      }

      whipeDiff = 0;

      // menue navigation area ?
            
      if (p->Item() == itemMenuNavigationArea && !touchMenu &&
          _menu.lineHeight > 0)
      {
         int clickRow;
         int yOff = y - p->Y();
         int currentRowY = (_menu.currentRow - _menu.topRow) * _menu.lineHeight;
               
         if (yOff < currentRowY)
            clickRow = yOff / _menu.lineHeight + _menu.topRow;
         else if (yOff < currentRowY + _menu.lineHeightSelected)
            clickRow = _menu.currentRow;
         else
            clickRow = (yOff-_menu.lineHeightSelected) / _menu.lineHeight + _menu.topRow+1;
               
         if (_menu.currentRow < clickRow)        // down
            for (int i = _menu.currentRow; i < clickRow; i++)
               cRemote::Put(cKey::FromString("Down"));
         else if (_menu.currentRow > clickRow)   // up
            for (int i = _menu.currentRow; i > clickRow; i--)
               cRemote::Put(cKey::FromString("Up"));
      }
            
      // check if item with defined action
            
      if (p->OnClick() != "" && flag == cGraphTftComService::efNone)
      {
         if (p->OnClick().find("touchMenu") == 1)
         {
            char* val;
            char* val2;
            char* click;

            asprintf(&click, "%s", p->OnClick().c_str());

            // touch menu handling

            if ((val = strchr(click, ':')) && *(val++))
            {
               if ((val2 = strchr(val, ':')) && *(val2++) 
                   && atoi(val) == touchMenu)
                  touchMenu = atoi(val2);
               else
                  touchMenu = atoi(val);
            }
            else
               touchMenu = !touchMenu;
                  
            if (p->Delay())
               touchMenuHideTime = p->Delay();

            if (touchMenuHideTime)
               touchMenuHideAt = cTimeMs::Now() + touchMenuHideTime;

            tell(2, "touch menu switched to (%d) hide in (%d) seconds", 
                 touchMenu, touchMenuHideTime);

            free(click);
            broadcast(yes);
         }
         else
         {
            // no key, and not the 'touchMenu' command

            processAction(p, p->OnClick());
         }
      }

      if (p->OnDblClick() != "" && (flag & cGraphTftComService::efDoubleClick))
      {
         processAction(p, p->OnDblClick());
      }
   }
}

//***************************************************************************
// Process Action
//***************************************************************************

int cGraphTFTDisplay::processAction(cDisplayItem* p, string action, int step)
{
   const char* v;
   Scan scan(action.c_str());
   string name;
   eKeys key;
   int value = 0;
   
   scan.eat();
   key = cKey::FromString(scan.lastIdent());

   tell(2, "Performing mouse action (%d) times, key '%s', %sfound in VDRs keytab",
        step, scan.all(), key != kNone ? "" : "not ");

   // first check if 'normal' key actions

   if (key != kNone)
   {     
      for (int i = 0; i < abs(step); i++)
      {
         scan.reset();

         while (scan.eat() == success)
         {
            if ((key = cKey::FromString(scan.lastIdent())) != kNone)
               cRemote::Put(key);
         }
      }

      return success;
   }

   // perform special action on theme variable

   if (!scan.isIdent() || !(v = p->lookupVariable(scan.lastIdent())))
   {
      tell(0, "Error: Invalid variable '%s' in '%s'",
           scan.lastIdent(), scan.all());
      return fail;
   }
   
   name = scan.lastIdent();
   
   if (scan.eat() != success || !scan.isOperator())
   {
      tell(0, "Error: Invalid operator '%s' in '%s'",
           scan.lastIdent(), scan.all());
      return fail;
   }

   // get the actual value of the variable 

   value = atoi(v);

   // perform action on the value

   if (scan.hasValue("++"))
      value += step;
   else if (scan.hasValue("--"))
      value -= step;
   else if (scan.hasValue(":"))
   {
      int v1;

      if (scan.eat() != success || !scan.isNum())
      {
         tell(0, "Missing int value in '%s', ignoring", scan.all());
         return fail;
      }

      v1 = scan.lastInt();

      if (value == v1 && scan.eat() == success && scan.isOperator() && scan.hasValue(":"))
      {
         if (scan.eat() != success || !scan.isNum())
         {
            tell(0, "Missing second int value in '%s', ignoring", scan.all());
            return fail;
         }

         value = scan.lastInt();
      }
      else
      {
         value = v1;
      }
   }
   else
   {
      tell(0, "Unexpected operation in '%s', ignoring", scan.all());
      return fail;
   }

   tell(6, "Setting '%s' from (%s) to (%d)", name.c_str(), v, value);
   p->setVariable(name.c_str(), value);
   broadcast(yes);

   return done;
}

//***************************************************************************
// Calibrate Touch Device
//***************************************************************************

int cGraphTFTDisplay::calibrateTouch(int x, int y)
{
   static double upperLeftX;
   static double upperLeftY;
   
   const char* s = Thms::theTheme->lookupVar("calibrationFrameOffset");
   int offset = s ? atoi(s) : 20;

   calibration.state++;

   switch (calibration.state)
   {
      case csUpperLeft:
      {
         calibration.instruction = "Click upper left corner";
         calibration.cursorX = offset;
         calibration.cursorY = offset;
         calibration.lastX = 0;
         calibration.lastY = 0;
         break;
      }
      case csUpperRight:
      {
         upperLeftX = x;
         upperLeftY = y;
         
         calibration.instruction = "Click upper right corner";
         calibration.cursorX = Thms::theTheme->getWidth() - offset;
         calibration.cursorY = offset;
         break;
      }
      case csLowerLeft:
      {
         // check for swap

         if (abs(calibration.lastY - y) > abs(calibration.lastX - x))
         {
            calibration.settings.swapXY = yes;
            if (touchThread) touchThread->resetSetting(yes);

            calibration.info = "detected flags (swapXY)";

            // restart calibration due to XY swap!

            calibration.state = csUnknown;
            calibrateTouch(0, 0);
            break;
         }

         calibration.settings.scaleX = ((double)(Thms::theTheme->getWidth() - 2*offset))
            / ((double)(x-calibration.lastX));

         calibration.instruction = "Click lower left corner";
         calibration.cursorX = offset;
         calibration.cursorY = Thms::theTheme->getHeight() - offset;

         break;
      }
      case csLowerRight:
      {
         calibration.settings.scaleY = ((double)(Thms::theTheme->getHeight()- 2*offset))
            / ((double)(y-calibration.lastY)); // upperLeftY

         calibration.settings.offsetX = (int)((((double)offset)/calibration.settings.scaleX) - upperLeftX);
         calibration.settings.offsetY = (int)((((double)offset)/calibration.settings.scaleY) - upperLeftY);
         calibration.settings.scaleWidth = Thms::theTheme->getWidth();
         calibration.settings.scaleHeight = Thms::theTheme->getHeight();

         calibration.instruction = "Click lower right corner";
         calibration.cursorX = Thms::theTheme->getWidth() - offset;
         calibration.cursorY = Thms::theTheme->getHeight() - offset;
         break;
      }
      case csDone:
      {
         if (touchThread)
         {
            touchThread->setSetting(&calibration.settings);
            touchThread->setCalibrate(off);
            GraphTFTSetup.touchSettings = calibration.settings;
            GraphTFTSetup.Store(yes);
         }

         tell(0, "Calibration done, offset (%d/%d), scale (%f/%f)",
              calibration.settings.offsetX, calibration.settings.offsetY,
              calibration.settings.scaleX, calibration.settings.scaleY);

         calibration.info = "verify by touching ...";
         calibration.instruction = "Calibration done";
         calibration.cursorX = x;
         calibration.cursorY = y;
      }
      case csTest:
      {
         calibration.info = "verify by touching ...";
         calibration.instruction = "Calibration done";
         calibration.cursorX = x;
         calibration.cursorY = y;

         break;
      }
      default:
      {
         calibration.cursorX = x;
         calibration.cursorY = y;
      }
   }

   tell(0, "Callibration step '%s'", calibration.instruction.c_str());

   calibration.lastX = x;
   calibration.lastY = y;

   return done;
}

//***************************************************************************
// Display
//***************************************************************************

int cGraphTFTDisplay::display(string sectionName)
{
   int count = 0;

   LogDuration ld("cGraphTFTDisplay::display()");

   if (!displayActive)
      return 0;

   if (isModeNormal(_mode))
      UpdateProgramme();

   else if (isModeMenu(_mode) && !Thms::theTheme->getSection(sectionName))
   {
      tell(0, "Info: Section faked to '%s' due to section '%s' not defined!", 
        "Menu", sectionName.c_str());

      sectionName = "Menu";
   }

   if (!(currentSection = Thms::theTheme->getSection(sectionName)))
      return 0;

   // set/reset force flag

   cDisplayItem::setForce(forceNextDraw);
   forceNextDraw = no;

   if (cDisplayItem::getForce())
      tell(1, "Force draw of all items now %s", 
           forceWriteThrough ? "- write through" : "");
   
   // section changed

   if (currentSection != lastSection)
   {
      tell(0, "Section changed from '%s' to '%s'", 
        lastSection ? lastSection->getName().c_str() : "<none>", 
        currentSection->getName().c_str());

      lastSection = currentSection;

      _menu.topRow = na;
      _menu.lineHeight = na;
      cDisplayItem::clearSelectedItem();     

      clear();
      cDisplayItem::setForce(yes);
   }

   // for now ...

   if (isModeMenu(_mode))
   {
      cDisplayItem::setForce(yes);
      tell(3, "force due to menu section!");

      // reset x of menu items

      for (unsigned int i = 0; i < _menu.items.size(); i++) 
         _menu.items[i].nextX = 0;
   }
    

   // check if any foreground item is visible

   for (cDisplayItem* p = currentSection->getItems()->First(); p; 
        p = currentSection->getItems()->Next(p))
   {
      if (p->Foreground() && p->evaluateCondition())
      {
         cDisplayItem::setForce(yes);
         break ;
      }  
   }

   // draw items

   for (cDisplayItem* p = currentSection->getItems()->First(); p; 
        p = currentSection->getItems()->Next(p))
   {
      if (!p->isForegroundItem() && !p->Foreground())
         count += p->refresh();
   }

   for (cDisplayItem* p = currentSection->getItems()->First(); p; 
        p = currentSection->getItems()->Next(p))
   {
      if (p->isForegroundItem() || p->Foreground())
         count += p->refresh();
   }

   // update display

   if (count)
      refresh();

   return count;
}

void cGraphTFTDisplay::clear()
{
   _render->clear();
}

//***************************************************************************
// Refresh
//***************************************************************************

void cGraphTFTDisplay::refresh()
{
   LogDuration ld("cGraphTFTDisplay::refresh()");

   // refresh local display

   if (!isDummyRender)
      _render->refresh(forceWriteThrough);

   forceWriteThrough = no;

   // refresh tcp client

   if (comThread)
      comThread->refresh();

   // dump to file ...

   if (GraphTFTSetup.DumpImage) 
   {
      static int _Dumped = cTimeMs::Now();
      int lastDumpDiff = _Dumped ? cTimeMs::Now() - _Dumped : 0;

      if (lastDumpDiff > (GraphTFTSetup.DumpRefresh * 1000))
      {
         // with dummy renderer refresh only for dump 
         
         if (isDummyRender)
            _render->refresh();

         // dump
         
         _Dumped = cTimeMs::Now();
         _render->dumpImage2File("/graphTFT.png", 
                                 GraphTFTSetup.DumpImageX, 
                                 GraphTFTSetup.DumpImageY);
      }
   }

   if (!versionReported && time(0) > startedAt+30)
   {
      versionReported = yes;

      if (Thms::theTheme->check(syntaxVersion) != success)
      {
         Skins.Message(mtInfo, tr("graphTFT: The syntax version "
                                  "of the theme file don't match!"));
         
         tell(0, "Syntax version of theme '%s' mismatch!", 
              Thms::theTheme->getName().c_str());
      }
   }
}

int cGraphTFTDisplay::updateGroup(int group)
{
   if (!currentSection)
      return ignore;

   return currentSection->updateGroup(group);
}
