/**
 *  GraphTFT plugin for the Video Disk Recorder 
 * 
 *  display.c - A plugin for the Video Disk Recorder
 *
 *  (c) 2004-2006 Lars Tegeler, Sascha Volkenandt, 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()  
{
   // init renderer

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

   _active = no;
   comThread = 0;

   // init contents

   currentSection = 0;
   lastSection = 0;
   _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;
   _presentEvent = 0;
   _followingEvent = 0;
   _volume = cDevice::CurrentVolume();
   _mute = cDevice::PrimaryDevice()->IsMute();
   _showRecording = 0;
   _replay.control = 0;
   _replay.name = "";
   _replay.fileName = "";
   _replay.lastMode = ModeUnknown;
   _menu.currentPosLast = 0;
   _menu.currentPos = 0;
   _menu.topPos = na;
   _menu.lineHeight = na;
   displayActive = yes;
   forceNextDraw = yes;
   _event = 0;
   _recording = "";

   // snapshot

   snapshotPending = no;

   // calibration stuff

   calibrationPos.x = 100;
   calibrationPos.y = 100;

   // init pbp stuff

   _pbpActive = false;
}

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

   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,
                                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,
                                  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,
                                   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,
                                     theTheme->getDir());
      }
      else
      {
         tell(0, "Ignoring unexpected device '%s'", dev);
      }

#ifdef WITH_TCP_COM

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

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

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

#endif // WITH_TCP_COM
   }

#endif // HAVE_IMLIB

   // either the com thread or a device is needed

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

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

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

   // Show the start image

   _render->image(theTheme->getStartImage().c_str(), 0,0,0,0);
   _render->refresh();
   _render->setPlayMode(no);

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

   Start();

   return 1;
}
//***************************************************************************
// Broadcast
//***************************************************************************

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

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

int cGraphTFTDisplay::setMode(DisplayMode mode, const char* menuName, int force)
{ 
   if (_mode == Calibration && !force)
      return done;
   
   if (_mode != mode || (menuName && _sectionName != menuName))
   {
      _mode = mode;
      _sectionName = menuName ? menuName : "Menu";

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

      _doUpdate.Broadcast();
   }

   return done;
}

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

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

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

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

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

void cGraphTFTDisplay::setCalibrate(int active)
{
   tell(1, "Info: %s calibration mode", active ? "starting" : "stopping");

   if (active)
      setMode(cGraphTFTDisplay::Calibration);
   else
      setMode(cGraphTFTDisplay::NormalView, 0, /*force*/ true);

   _doUpdate.Broadcast();
}

//***************************************************************************
// 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;

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

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

   usleep(500000);

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

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

   // main loop

   while (_active)
   {
      tell(2, "action loop");

      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 Calibration:       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();

         // can't calc this inline, due to a format string problem ... ?

         int s = updateIn/1000; int us = updateIn%1000;

         tell(2, "Displayed %d Items, schedule next update in %d,%03d seconds", 
           n, s, us);
      }
         
      if (updateIn < 10)
         updateIn = 10;

      _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"));
   
   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()
{
   int lastChannel = _channel;
   const cEvent* lastPresentEvent = _presentEvent;
   const cChannel* channel = Channels.GetByNumber(_channel);

   cMutexLock lock(&_mutex);

   // first delete

   if (!channel || _channel != lastChannel) 
   {
      _channelInfo = "";
      _presentEvent = _followingEvent = 0;
      updateGroup(groupChannel);
   }
   
   // now update

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

      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 (_presentEvent != lastPresentEvent)
               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) 
   {
      const cChannel* channel;

      cMutexLock lock(&_mutex);
      _channel = ChannelNumber;

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

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

         if ((pos = _channelText.rfind(';')) != -1)
            _channelText.erase(pos);
         if ((pos = _channelText.rfind(',')) != -1)
            _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(0, "OsdSetRecording: '%s'", recording ? recording->Title() : "");

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

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

void cGraphTFTDisplay::OsdChannel(const char *Text) 
{
   // if Elchi workaround still needed (i don't know), 
   // if you like tu use/try it delete the return here !

   return ;  

   tell(3, "OsdChannel: %s", Text);

   if (_channelInfo != Text)
   {
      int pos;
      string temp = Text;
      cMutexLock lock(&_mutex);

      // workaround for some osd themes (Elchi)

      _channelInfo = "";
      _presentEvent = _followingEvent = 0;

      if ((pos = temp.find(' ')) != -1) 
      {
         _channelInfo += temp.substr(0, pos) + " ";

         while (pos < (int)temp.length() && temp[pos] == ' ')
            ++pos;

         int l = temp.length();

         while (temp[l - 1] == ' ')
            --l;

         _channelInfo += temp.substr(pos, l - pos);
      } 
      else
         _channelInfo = Text;

      if ((pos = _channelInfo.rfind(';')) != -1)
         _channelInfo.erase(pos);

      if ((pos = _channelInfo.rfind(',')) != -1)
         _channelInfo.erase(pos);

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

//***************************************************************************
// 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);

   if (On) 
   {
      RecordingInfo info;

      cMutexLock lock(&_mutex);

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

      updateGroup(groupRecording);
      _doUpdate.Broadcast();
   } 
   else 
   {
      cMutexLock lock(&_mutex);

      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) 
{
   tell(3, "Replaying %s '%s' from %p", On ? "start" : "stop", Name, Control);

   cMutexLock lock(&_mutex);

   if (On)
   {
      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('~')) != -1) 
            _replay.name.erase(0, i + 1);
      }

      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)
   {
      int pos;
      cMutexLock lock(&_mutex);

      _message = "";
      _menu.title = Title;

      if ((pos = _menu.title.find('\t')) != -1)
         _menu.title.erase(pos);
   }
}

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

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

   cMutexLock lock(&_mutex);

   if (Text)
   {
      MenuItem item;
      item.text = Text;
      item.type = itNormal;
      item.tabCount = 0;
      int count = 1;

      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);
         
         tell(4, "tab (%d) '%s'", i, tp);

         if (tp)
         {
            char* tab;
            int len;

            tab = strdup(tp);
            strreplace(tab, '\177', '|');
            strreplace(tab, '\203', ' ');
            strreplace(tab, '\204', ' ');
            strreplace(tab, ']', ' ');
            strreplace(tab, '[', ' ');

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

            // bigger than last row

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

            // detect partingLine

            if (strstr(tab, "-----") && count < 2)
            {
               // arghdirector
               
               item.type = itPartingLine;
               item.tabs[0] = "";
            }
            
            else if (i == 1 && strstr(item.tabs[0].c_str(), "-----"))
            {
               // some special handling for lines of epgsearch plugin like
               // ---------------------------------- \t Die 24.10.2006 ------------------------

               item.type = itPartingLine;
               char* p;

               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;
            }

            item.tabCount++;
            free(tab);
         }
         else
            break;
      }
      
      _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.currentPos = i;

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

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

      if (_menu.currentPos >= (int)_menu.items.size())
         return ;

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

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

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

      for (int i = 0; i < _menu.currentPos; 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(2, "OsdClear");

   cMutexLock lock(&_mutex);

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

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

   if (_channelInfo != "") 
   {
      _channelInfo = "";
      updateGroup(groupChannel);
      _doUpdate.Broadcast();
   }

   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);

   cMutexLock lock(&_mutex);

   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)
   {
      _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);

   cMutexLock lock(&_mutex);

   if (Text)
   { 
      _menu.textStartLine = 0;
      _menu.text = Text;
      // replace(_menu.text.begin(), _menu.text.end(), '\n', ' ');
   }
   else
   {
      _menu.textStartLine += Scroll ? -1 : 1;

      if (_menu.textStartLine < 0) 
         _menu.textStartLine = 0;
   }

   _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); 
}

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

void cGraphTFTDisplay::mouseEvent(int x, int y, int button, int flag)
{
   if (!currentSection)
      return ;

   cMutexLock lock(&_mutex);

   if (isModeCallibration(_mode))
   {
      calibrationPos.x = x;
      calibrationPos.y = y;

      _doUpdate.Broadcast();
   }

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

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

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

   else if (button == cGraphTftComService::mbLeft)
   {
      for (cDisplayItem* p = currentSection->getItems()->First(); p; 
           p = currentSection->getItems()->Next(p))
      {
         // check click coordinates 
         
         if (x >= p->X() && x <= p->X()+p->Width() &&
             y >= p->Y() && y <= p->Y()+p->Height())
         {
            tell(2, "item %d on (%d/%d) clicked, sending key '%s', %sfound in keytab", p->Item(),
              x, y, p->OnClick().c_str() ? p->OnClick().c_str() : "<none>",
              p->OnClick().c_str() && cKey::FromString(p->OnClick().c_str()) ? "" : "not ");

            // menue navigation area ?
            
            if (p->Item() == itemMenuNavigationArea &&
                _menu.lineHeight > 0)
            {
               int yOff = y - p->Y();
               int clickRow = yOff / _menu.lineHeight + _menu.topPos;
               
               if (_menu.currentPos < clickRow)        // down
                  for (int i = _menu.currentPos; i < clickRow; i++)
                     cRemote::Put(cKey::FromString("Down"));
               else if (_menu.currentPos > clickRow)   // up
                  for (int i = _menu.currentPos; i > clickRow; i--)
                     cRemote::Put(cKey::FromString("Up"));
            }
            
            // check if item with key definition and double clicked
            
            if (p->OnClick() != "" && flag == 0)
            {
               tell(2, "item %d clicked, sending key '%s'", p->Item(), 
                 p->OnClick().c_str());

               cRemote::Put(cKey::FromString(p->OnClick().c_str()));
            }

            if (p->OnDblClick() != "" && (flag & cGraphTftComService::efDoubleClick))
            {
               tell(2, "item %d double-clicked, sending key '%s'", p->Item(), 
                 p->OnDblClick().c_str());
               
               cRemote::Put(cKey::FromString(p->OnDblClick().c_str()));
            }
         }
      }
   }
}

//***************************************************************************
// 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) && !theTheme->getSection(sectionName))
   {
      tell(2, "Info: Section faked to '%s' due to section '%s' not defined!", 
        "Menu", sectionName.c_str());

      sectionName = "Menu";
   }

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

   // reset/reset force flag

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

   // 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.topPos = na;
      _menu.lineHeight = na;
      
      clear();
      cDisplayItem::setForce(yes);
   }

   // for now ...

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

   for (cDisplayItem* p = currentSection->getItems()->First(); p; 
        p = currentSection->getItems()->Next(p))
   {
      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();

   // 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))
      {
         _Dumped = cTimeMs::Now();
         _render->dumpImage2File("/graphTFT.png", 
                                 GraphTFTSetup.DumpImageX, 
                                 GraphTFTSetup.DumpImageY);
      }
   }
}

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

   return currentSection->updateGroup(group);
}
