/*
 * menu.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */


#include <vdr/osd.h>
#include <vdr/remote.h>
#include <vdr/plugin.h>
#include <vdr/interface.h>

#include "menu.h"
#include "i18n.h"
#include "virtualconsole.h"
#include "virtualconsoles.h"



//#define SYMBOL_KEYBOARD 'K'
#define SYMBOL_BELL 'B'



#include "fontsmallfix.c"
#include "symbols/bell.xpm"
#include "symbols/keyboard.xpm"



void FlushRemote() {

  eKeys Key;
  while ((Key = cRemote::Get(100)) != kNone)
    ;
}





void consDrawBoxChar(cBitmap& bitmap, int x, int y, int width, int height, const unsigned char ch, tColor foreColor, tColor backColor);


const tColor clrConsoleTransparent = 0xFFFFFFFF;


void WriteChar(cBitmap& bitmap, const cFont& font, int x, int y, const unsigned char ch, tColor foreColor, tColor backColor) {

  if (ch >= 32) {

    tIndex fg = bitmap.Index(foreColor);

    int w = font.Width('A');
    int h = font.Height();

    if (backColor != clrConsoleTransparent)
      bitmap.DrawRectangle(x, y, x + w - 1, y + h - 1, backColor);

    const cFont::tCharData* CharData = font.CharData(ch);

    // Center the char within its cell
    x += (w - (int)CharData->width) / 2;
    if (w > (int)CharData->width)
      w = (int)CharData->width;

    for (int row = 0; row < h; ++row) {

      cFont::tPixelData PixelData = CharData->lines[row];
//@@TEST
      PixelData >>= 1;
      for (int col = w; col-- > 0;) {

        if (PixelData & 1) {
          bitmap.SetIndex(x + col, y + row, fg);

          // Try to fix flickering
//          if (*bitmap.Data(x + col, y + row - 1) != fg)
//            bitmap.SetIndex(x + col, y + row + 1, fg);
        }

        PixelData >>= 1;
      }
    }

  } else {

    // Draw a special pseudo graphics char.
    // These are mapped to the lower range (under char 32 - the space).
    // This works because that range is only used for control chars...

    // Get the cell size with the help of a typical char.
    int width = font.Width('A');
    int height = font.Height();

    consDrawBoxChar(bitmap, x, y, width, height, ch, foreColor, backColor);
  }
}



void consDrawLine(cBitmap& bitmap, int x1, int y1, int x2, int y2, tColor color) {

  // Optimization: a horizontal line
  if (y1 == y2) {
    if (x1 <= x2) {
      for (int x = x1; x <= x2; ++x)
        bitmap.DrawPixel(x, y1, color);
    } else {
      for (int x = x2; x <= x1; ++x)
        bitmap.DrawPixel(x, y1, color);
    }
    return;
  }

  // Optimization: a vertical line
  if (x1 == x2) {
    if (y1 <= y2) {
      for (int y = y1; y <= y2; ++y)
        bitmap.DrawPixel(x1, y, color);
    } else {
      for (int y = y2; y <= y1; ++y)
        bitmap.DrawPixel(x1, y, color);
    }
    return;
  }


  // Draw always from top to bottom
  if (y1 > y2) {
    int tmp = x1; x1 = x2; x2 = tmp;
    tmp = y1; y1 = y2; y2 = tmp;
  }

  int height = y2 - y1 + 1;
  if (x1 < x2) {
    // Draw from left to right
    int width = x2 - x1 + 1;
    float nrXperY = (float)width / height;
    if (nrXperY < 0.001) nrXperY = 0.0011;

    int x = x1;
    for (int y = y1; y < y2; ++y) {

      int xz = (int)(x1 + (y - y1 + 1) * nrXperY);
      for (; x < xz; ++x) {
        bitmap.DrawPixel(x, y, color);
      }
    }
    for (; x <= x2; ++x) {
      bitmap.DrawPixel(x, y2, color);
    }

  } else {
    // Draw from right to left
    int width = x1 - x2 + 1;
    float nrXperY = (float)width / height;
    if (nrXperY < 0.001) nrXperY = 0.001;

    int x = x1;
    for (int y = y1; y < y2; ++y) {

      int xz = (int)(x1 - (y - y1 + 1) * nrXperY);
      for (; x > xz; --x) {
        bitmap.DrawPixel(x, y, color);
      }
    }
    for (; x >= x2; --x) {
      bitmap.DrawPixel(x, y2, color);
    }
  }
}





// --- DrawBoxChar ----------------------------------------------------

// This code is taken from the xterm project.


/*
 * The grid is abitrary, enough resolution that nothing's lost in initialization.
 */
#define BOX_HIGH 60
#define BOX_WIDE 60

#define MID_HIGH (BOX_HIGH/2)
#define MID_WIDE (BOX_WIDE/2)

/*
 * ...since we'll scale the values anyway.
 */
#define SCALE_X(n) n = (n * (width - 1)) / (BOX_WIDE - 1)
#define SCALE_Y(n) n = (n * (height - 1)) / (BOX_HIGH - 1)

#define SEG(x0,y0,x1,y1) x0,y0, x1,y1




/*
 * Draw the given graphic character, if it is simple enough (i.e., a
 * line-drawing character).
 */
void consDrawBoxChar(cBitmap& bitmap, int x, int y, int width, int height, const unsigned char ch, tColor foreColor, tColor backColor) {

    /* *INDENT-OFF* */
    static const short diamond[] =
    {
	SEG(  MID_WIDE,	    BOX_HIGH/4, 3*BOX_WIDE/4,   MID_HIGH),
	SEG(3*BOX_WIDE/4,   MID_WIDE,	  MID_WIDE,   3*BOX_HIGH/4),
	SEG(  MID_WIDE,   3*BOX_HIGH/4,	  BOX_WIDE/4,   MID_HIGH),
	SEG(  BOX_WIDE/4,   MID_HIGH,	  MID_WIDE,	BOX_HIGH/4),
	SEG(  MID_WIDE,	    BOX_HIGH/3, 2*BOX_WIDE/3,   MID_HIGH),
	SEG(2*BOX_WIDE/3,   MID_WIDE,	  MID_WIDE,   2*BOX_HIGH/3),
	SEG(  MID_WIDE,   2*BOX_HIGH/3,	  BOX_WIDE/3,   MID_HIGH),
	SEG(  BOX_WIDE/3,   MID_HIGH,	  MID_WIDE,	BOX_HIGH/3),
	SEG(  BOX_WIDE/4,   MID_HIGH,	3*BOX_WIDE/4,   MID_HIGH),
	SEG(  MID_WIDE,     BOX_HIGH/4,	  MID_WIDE,   3*BOX_HIGH/4),
	-1
    }, degrees[] =
    {
	SEG(  MID_WIDE,	    BOX_HIGH/4, 2*BOX_WIDE/3, 3*BOX_HIGH/8),
	SEG(2*BOX_WIDE/3, 3*BOX_HIGH/8,	  MID_WIDE,	MID_HIGH),
	SEG(  MID_WIDE,	    MID_HIGH,	  BOX_WIDE/3, 3*BOX_HIGH/8),
	SEG(  BOX_WIDE/3, 3*BOX_HIGH/8,	  MID_WIDE,	BOX_HIGH/4),
	-1
    }, lower_right_corner[] =
    {
	SEG(  0,	    MID_HIGH,	  MID_WIDE,	MID_HIGH),
	SEG(  MID_WIDE,	    MID_HIGH,	  MID_WIDE,	0),
	-1
    }, upper_right_corner[] =
    {
	SEG(  0,	    MID_HIGH,	  MID_WIDE,	MID_HIGH),
	SEG( MID_WIDE,	    MID_HIGH,	  MID_WIDE,	BOX_HIGH),
	-1
    }, upper_left_corner[] =
    {
	SEG(  MID_WIDE,	    MID_HIGH,	  BOX_WIDE,	MID_HIGH),
	SEG(  MID_WIDE,	    MID_HIGH,	  MID_WIDE,	BOX_HIGH),
	-1
    }, lower_left_corner[] =
    {
	SEG(  MID_WIDE,	    0,		  MID_WIDE,	MID_HIGH),
	SEG(  MID_WIDE,	    MID_WIDE,	  BOX_WIDE,	MID_HIGH),
	-1
    }, cross[] =
    {
	SEG(  0,	    MID_HIGH,	  BOX_WIDE,	MID_HIGH),
	SEG(  MID_WIDE,	    0,		  MID_WIDE,	BOX_HIGH),
	-1
    }, scan_line_1[] =
    {
	SEG(  0,	    0,		  BOX_WIDE,	0),
	-1
    }, scan_line_3[] =
    {
	SEG(  0,	    BOX_HIGH/4,	  BOX_WIDE,	BOX_HIGH/4),
	-1
    }, scan_line_7[] =
    {
	SEG( 0,		    MID_HIGH,	  BOX_WIDE,	MID_HIGH),
	-1
    }, scan_line_9[] =
    {
	SEG(  0,	    3*BOX_HIGH/4, BOX_WIDE, 3 * BOX_HIGH / 4),
	-1
    }, horizontal_line[] =
    {
	SEG(  0,	    BOX_HIGH,	  BOX_WIDE,	BOX_HIGH),
	-1
    }, left_tee[] =
    {
	SEG(  MID_WIDE,	    0,		  MID_WIDE,	BOX_HIGH),
	SEG(  MID_WIDE,	    MID_HIGH,	  BOX_WIDE,	MID_HIGH),
	-1
    }, right_tee[] =
    {
	SEG(  MID_WIDE,	    0, MID_WIDE,		BOX_HIGH),
	SEG(  MID_WIDE,	    MID_HIGH,	  0,		MID_HIGH),
	-1
    }, bottom_tee[] =
    {
	SEG(  0,	    MID_HIGH,	  BOX_WIDE,	MID_HIGH),
	SEG(  MID_WIDE,	    0,		  MID_WIDE,	MID_HIGH),
	-1
    }, top_tee[] =
    {
	SEG(  0,	    MID_HIGH,	  BOX_WIDE,	MID_HIGH),
	SEG(  MID_WIDE,	    MID_HIGH,	  MID_WIDE,	BOX_HIGH),
	-1
    }, vertical_line[] =
    {
	SEG(  MID_WIDE,	    0,		  MID_WIDE,	BOX_HIGH),
	-1
    }, less_than_or_equal[] =
    {
	SEG(5*BOX_WIDE/6,   BOX_HIGH/6,	  BOX_WIDE/5,	MID_HIGH),
	SEG(5*BOX_WIDE/6, 5*BOX_HIGH/6,	  BOX_WIDE/5,	MID_HIGH),
	SEG(  BOX_WIDE/6, 5*BOX_HIGH/6, 5*BOX_WIDE/6, 5*BOX_HIGH/6),
	-1
    }, greater_than_or_equal[] =
    {
	SEG(  BOX_WIDE/6,  BOX_HIGH /6, 5*BOX_WIDE/6,   MID_HIGH),
	SEG(  BOX_WIDE/6, 5*BOX_HIGH/6, 5*BOX_WIDE/6,   MID_HIGH),
	SEG(  BOX_WIDE/6, 5*BOX_HIGH/6, 5*BOX_WIDE/6, 5*BOX_HIGH/6),
	-1
    };
    /* *INDENT-ON* */

    static const short *lines[] =
    {
	0,			/* 00 */
	diamond,		/* 01 */
	0,			/* 02 */
	0,			/* 03 */
	0,			/* 04 */
	0,			/* 05 */
	0,			/* 06 */
	degrees,		/* 07 */
	0,			/* 08 */
	0,			/* 09 */
	0,			/* 0A */
	lower_right_corner,	/* 0B */
	upper_right_corner,	/* 0C */
	upper_left_corner,	/* 0D */
	lower_left_corner,	/* 0E */
	cross,			/* 0F */
	scan_line_1,		/* 10 */
	scan_line_3,		/* 11 */
	scan_line_7,		/* 12 */
	scan_line_9,		/* 13 */
	horizontal_line,	/* 14 */
	left_tee,		/* 15 */
	right_tee,		/* 16 */
	bottom_tee,		/* 17 */
	top_tee,		/* 18 */
	vertical_line,		/* 19 */
	less_than_or_equal,	/* 1A */
	greater_than_or_equal,	/* 1B */
	0,			/* 1C */
	0,			/* 1D */
	0,			/* 1E */
	0,			/* 1F */
    };

    if (backColor != clrConsoleTransparent)
      bitmap.DrawRectangle(x, y, x + width - 1, y + height - 1, backColor);

    const short *p;
    if (ch < (int) (sizeof(lines) / sizeof(lines[0]))
	&& (p = lines[ch]) != 0) {

	int coord[4], n = 0;
	while (*p >= 0) {

	    coord[n++] = *p++;
	    if (n == 4) {
		SCALE_X(coord[0]);
		SCALE_Y(coord[1]);
		SCALE_X(coord[2]);
		SCALE_Y(coord[3]);

		consDrawLine(bitmap, x + coord[0], y + coord[1], x + coord[2], y + coord[3], foreColor);
                // Repeat the line for flicker freeing
		consDrawLine(bitmap, x + coord[0], y + coord[1] + 1, x + coord[2], y + coord[3] + 1, foreColor);
		n = 0;
	    }
	}
    }
}





// --- cMenuEditComboItem ----------------------------------------------------

cMenuEditComboItem::cMenuEditComboItem(const char *Name, int *pValue, sMenuEditComboItemData *data, int dataCount)
: cMenuEditIntItem(Name, &_index, 0, dataCount - 1),
  _index(0),
  _pValue(pValue)
{
  _data = (sMenuEditComboItemData*) malloc(dataCount * sizeof(sMenuEditComboItemData));
  assert(_data);
  _dataCount = dataCount;

  for (int i = 0; i < dataCount; ++i) {
    _data[i].Key   = data[i].Key;
    _data[i].Value = strdup(data[i].Value);

   if (*pValue == data[i].Key)
     _index = i;
  }

  Set();
}



cMenuEditComboItem::~cMenuEditComboItem() {

  if (_data) {
    for (int i = 0; i < _dataCount; ++i) {
      free((void*) _data[i].Value);
    }
    free(_data);
  }
}



void cMenuEditComboItem::Set() {

  *_pValue = _data[_index].Key;

  char buf[20];
  snprintf(buf, sizeof(buf), "%2i - %s", _index, _data[_index].Value);
  SetValue(buf);
}





// --- cMenuConsoleSetup --------------------------------------------------------

#define ARRAYSIZE(array) (sizeof(array) / sizeof(array[0]))

cMenuConsoleSetup::cMenuConsoleSetup()
: data(config)
{
  sMenuEditComboItemData colors[] = {
      { -2, tr("Transparent")},
      { -1, tr("Background") },
      {  0, tr("Black")      },
    //{  1, tr("Red")        },
    //{  2, tr("Green")      },
    //{  3, tr("Yellow")     }, 
    //{  4, tr("Blue")       },
    //{  5, tr("Magenta")    },
      {  6, tr("Cyan")       },
      {  7, tr("White")      }
    };

  sMenuEditComboItemData availableFonts[] = {
      {  0, tr("Normal")     },
      {  1, tr("Small")      },
      {  2, tr("Extra small")}
    };


  AddNewCategory(tr("Look"));
  Add(new cMenuEditComboItem( tr("Normal text color"),             &data.TextColor,     colors, ARRAYSIZE( colors ) ));
  Add(new cMenuEditComboItem( tr("Bold text color"),               &data.BoldTextColor, colors, ARRAYSIZE( colors ) ));
  Add(new cMenuEditComboItem( tr("Background color"),              &data.TextBackColor, colors, ARRAYSIZE( colors ) ));
  Add(new cMenuEditComboItem( tr("Font"),                          &data.Font,  availableFonts, ARRAYSIZE( availableFonts ) ));

  AddNewCategory( tr("Behaviour") );
  Add(new cMenuEditBoolItem(  tr("Automatic enter keyboard mode"), &data.AutoEnterKeyboardMode));
  Add(new cMenuEditIntItem(   tr("Bell timeout (s)"),              &data.BellTimeout, 0, 15 ));
  Add(new cMenuEditIntItem(   tr("Show info bar (s)"),             &data.InfobarTimeout, 0, 10 ));
  Add(new cMenuEditIntItem(   tr("Cursor blink rate (100 ms)"),    &data.BlinkRate, 1, 20 ));
#ifdef HAVE_PREVENT_SHUTDOWN
  Add(new cMenuEditBoolItem(  tr("Prevent shutdown"),              &data.PreventShutdown));
#endif

  SetCurrent(Get(1));
}



void cMenuConsoleSetup::AddNewCategory(const char* title) {

  char *buffer = NULL;
  asprintf(&buffer, "--- %s ----------------------------------------------------------------", title);
  assert(buffer);
  cOsdItem* item = new cOsdItem(buffer);
  free(buffer);
  assert(item);

  item->SetSelectable(false);

  Add(item);
}



void cMenuConsoleSetup::Store() {

  config = data;

  SetupStore( "TextColor",             data.TextColor );
  SetupStore( "BoldTextColor",         data.BoldTextColor );
  SetupStore( "TextBackColor",         data.TextBackColor );
  SetupStore( "Font",                  data.Font );

  SetupStore( "AutoEnterKeyboardMode", data.AutoEnterKeyboardMode );
  SetupStore( "BellTimeout",           data.BellTimeout );
  SetupStore( "InfobarTimeout",        data.InfobarTimeout );
  SetupStore( "PreventShutdown",       data.PreventShutdown );
  SetupStore( "BlinkRate",             data.BlinkRate );
}





// --- cConsDialog -------------------------------------------------------------

class cConsDialog : public cOsdObject {
protected:
  enum {
    MAXSAVEREGIONS = 2
  };

protected:
  cOsd* _pOsd;
  char* _title;
  char* _message;
  char* _red, *_green, *_yellow, *_blue;
  cBitmap* _pSavedRegion[MAXSAVEREGIONS];
  time_t _closeAt;

protected:
  void SaveRegion(int nr, int x1, int y1, int x2, int y2);
  void RestoreRegion(int nr);

public:
  cConsDialog(cOsd* pOsd);
  virtual ~cConsDialog();

  virtual void Show();
  virtual eOSState ProcessKey(eKeys Key);

  void SetTitle(const char* title);
  const char* Title() {return _title;}

  void SetMessage(const char* message);
  const char* Message() {return _message;}

  void SetButtons(const char* red, const char* green, const char* yellow, const char* blue);

  void SetAutoClose(bool autoClose);
};



cConsDialog::cConsDialog(cOsd* pOsd)
: cOsdObject(false),
  _pOsd(pOsd),
  _title(NULL), _message(NULL),
  _red(NULL), _green(NULL), _yellow(NULL), _blue(NULL),
  _closeAt((time_t)INT_MAX)
{
  for (int i = MAXSAVEREGIONS - 1; i >= 0; --i)
    _pSavedRegion[i] = NULL;

  assert(pOsd);
}



cConsDialog::~cConsDialog() {

  for (int i = MAXSAVEREGIONS - 1; i >= 0; --i)
    RestoreRegion(i);

  free(_title);
  free(_message);
  free(_red);
  free(_green);
  free(_yellow);
  free(_blue);
}



void cConsDialog::SaveRegion(int nr, int x1, int y1, int x2, int y2) {

  if (nr < 0 || nr >= MAXSAVEREGIONS || ! _pOsd)
    return;

  cBitmap*& pSavedRegion = _pSavedRegion[nr];

  delete pSavedRegion;
  pSavedRegion = new cBitmap(x2 - x1 + 1, y2 - y1 + 1, 8, x1, y1);

  if (! pSavedRegion)
    return;

  cBitmap* pSourceBitmap;
  for (int i = 0; (pSourceBitmap = _pOsd->GetBitmap(i)) != NULL; ++i)
    pSavedRegion->DrawBitmap(pSourceBitmap->X0(), pSourceBitmap->Y0(), *pSourceBitmap);
}



void cConsDialog::RestoreRegion(int nr) {

  if (nr < 0 || nr >= MAXSAVEREGIONS || ! _pOsd)
    return;

  cBitmap*& pSavedRegion = _pSavedRegion[nr];

  if (pSavedRegion) {
    _pOsd->DrawBitmap(pSavedRegion->X0(), pSavedRegion->Y0(), *pSavedRegion);
    delete pSavedRegion;
    pSavedRegion = NULL;
  }
}



void cConsDialog::Show() {

  if (! _pOsd)
    return;

  // Program dependant constants
  const int height = 100;
  const int border = 3;
  const int borderWidth = 1;
  const tColor borderColor = clrWhite;
  const tColor titleColor = clrCyan;
  const tColor messageColor = clrWhite;
  const tColor bgColor = clrBlack;


  // Setting dependant constants
  const int left = 0;
  const int top = 0;
  const int screenWidth = Setup.OSDWidth;
  const int screenHeight = Setup.OSDHeight;
  const int width = screenWidth / 5 * 4;

  const cFont* pFont = cFont::GetFont(fontOsd);


  // Resulting constants
  const int x = left + (screenWidth - width) / 2;
  const int y = top + (screenHeight - 100) / 2 + 1;


  // Restore possible already saved region
  for (int i = MAXSAVEREGIONS - 1; i >= 0; --i)
    RestoreRegion(i);


  // Save regions that will be overdrawn
  SaveRegion(0, x, y, x + width - 1, y + height - 1);
  SaveRegion(1, left, top + screenHeight - pFont->Height() - 1, left + screenWidth - 1, top + screenHeight - 1);


  // Show dialog
  // -----------
  // Draw background and border
  _pOsd->DrawRectangle(x, y, x + width - 1, y + height - 1, bgColor);
  _pOsd->DrawRectangle(x + border, y + border, x + width - border - 1, y + border + borderWidth, borderColor);
  _pOsd->DrawRectangle(x + border, y + height - border - borderWidth - 1, x + width - border - 1, y + height - border - 1, borderColor);
  _pOsd->DrawRectangle(x + border, y + border, x + border + borderWidth, y + height - border - 1, borderColor);
  _pOsd->DrawRectangle(x + width - border - borderWidth - 1, y + border, x + width - border - 1, y + height - border - 1, borderColor);

  // Draw title
  _pOsd->DrawText(x + 20, y + 50 - (pFont->Height() / 2), _title, titleColor, bgColor, pFont, width - 20 - 20, pFont->Height(), taCenter);

  // Draw message
  if (_message) {

    char *p = strchr(_message, '\t');
    if (p) {
      // If there is a TAB in the message then split it up:
      *p = 0;
      // the first part will be on the left side,...
      _pOsd->DrawText(x + 20,        y + height - pFont->Height() - 10, _message, messageColor, bgColor, pFont, width / 2 - 20, pFont->Height(), taLeft);
      // ...the second part on the right side.
      _pOsd->DrawText(x + width / 2, y + height - pFont->Height() - 10, p + 1,    messageColor, bgColor, pFont, width / 2 - 20, pFont->Height(), taRight);
      *p = '\t';

    } else {
      // Else the hole message is displayed centered.
      _pOsd->DrawText(x + 20, y + height - pFont->Height() - 10, _message, messageColor, bgColor, pFont, width - 20 - 20, pFont->Height(), taCenter);
    }
  }

  // Draw buttons
  if (_red || _green || _yellow || _blue) {

    int butWidth = screenWidth / 4;
    int butY = top + screenHeight - pFont->Height();

    _pOsd->DrawText(left + butWidth * 0, butY, _red,    clrWhite, _red    ? clrRed    : bgColor, pFont, butWidth, pFont->Height(), taCenter);
    _pOsd->DrawText(left + butWidth * 1, butY, _green,  clrBlack, _green  ? clrGreen  : bgColor, pFont, butWidth, pFont->Height(), taCenter);
    _pOsd->DrawText(left + butWidth * 2, butY, _yellow, clrBlack, _yellow ? clrYellow : bgColor, pFont, butWidth, pFont->Height(), taCenter);
    _pOsd->DrawText(left + butWidth * 3, butY, _blue,   clrWhite, _blue   ? clrBlue   : bgColor, pFont, butWidth, pFont->Height(), taCenter);
  }

  _pOsd->Flush();
}



eOSState cConsDialog::ProcessKey(eKeys Key) {

  switch (BASICKEY(Key)) {

  case kOk:      return osBack;
  case kBack:    return osBack;

  case kRed:
  case kGreen:
  case kYellow:
  case kBlue:
  case kLeft:
  case kUp:
  case kRight:
  case kDown:
                 return osUser1;

  case kNone:
    if (time(NULL) >= _closeAt)
      return osBack;
    return osContinue;

  default:       return osContinue;
  }
}



void cConsDialog::SetTitle(const char* title) {
  _title = strdup(title);
}



void cConsDialog::SetMessage(const char* message) {
  _message = strdup(message);
}



void cConsDialog::SetButtons(const char* red, const char* green, const char* yellow, const char* blue) {

  _red    = red    ? strdup(red)    : NULL;
  _green  = green  ? strdup(green)  : NULL;
  _yellow = yellow ? strdup(yellow) : NULL;
  _blue   = blue   ? strdup(blue)   : NULL;
}



void cConsDialog::SetAutoClose(bool autoClose) {

  if (autoClose)
    _closeAt = time(NULL) + config.InfobarTimeout;
  else
    _closeAt = (time_t)INT_MAX;
//    _closeAt = time(NULL) + 5; // 5 seconds like the VDR channel info?
}





// --- cMenuConsole -------------------------------------------------------------

cMenuConsole::cMenuConsole(int consoleNr)
: cOsdObject          (true), // NeedsFastResponse
  _consoleNr          (consoleNr),
  _pFont              (NULL),
  _pBitmap            (NULL),
  _pOsd               (NULL),
  _pConsole           (NULL),
  _inputActivated     (false),
  _pSubMenu           (NULL),
  _nextBlinkTime      (0),
  _timeReleaseCapture (INT_MAX)
{

  // Calculate new size
  _pixelW = Setup.OSDWidth;
  _pixelH = Setup.OSDHeight;


  _selectedFont = config.Font;
  if (_selectedFont == 1) {

    // Small
    _pFont = cFont::GetFont(fontSml);
    _charW = _pFont->Width('A');
    _charH = _pFont->Height('A');

  } else if (_selectedFont == 2) {

    // Extra small
    // The space between the chars can shrink, so more chars can go to the screen.
    _pFont = new cFont(consFontSmallFix);
    _charW = _pFont->Width('A') - 1;
    _charH = _pFont->Height('A') - 1;

  } else {

    // Normal
    _pFont = cFont::GetFont(fontFix);
    _charW = _pFont->Width('A');
    _charH = _pFont->Height('A');
  }

  _charsW = (_pixelW / _charW);
  _charsH = (_pixelH / _charH);

  //fprintf(stderr, "OSD x=%i, OSD y=%i, Pixel w=%i, Pixel h=%i, char w=%i, char h=%i, chars W=%i, chars H=%i\n", W, H, _pixelW, _pixelH, _charW, _charH, _charsW, _charsH);

  _pBitmap = new cBitmap(_pixelW, _pixelH, 2);
}



cMenuConsole::~cMenuConsole() {

  _pConsole->getScreen().WantRefreshEvent(false);

  // resetting console
  if (_inputActivated)
    ReleaseKeyboard();

  if (_selectedFont == 2)
    delete _pFont;

  delete _pBitmap;
  delete _pOsd;
}



void cMenuConsole::CaptureKeyboard() {

  if (_inputActivated)
    return;

  _inputActivated = true;
  cKbdRemote::SetRawMode(true);
  gl_pConsConsoles->activateInputForTerminal();
}



void cMenuConsole::ReleaseKeyboard() {

  if (! _inputActivated)
    return;

  // resetting console
  gl_pConsConsoles->deactivateInputForTerminal();
  cKbdRemote::SetRawMode(false);
  _inputActivated = false;
}



tColor ConScreenGetColor(int AnsiColor) {

  tColor color;

  switch (AnsiColor) {
  case -2:  color = clrTransparent;  break;
  case -1:  color = clrGray50;       break;

  case 0:   color = clrBlack;        break;
  case 1:   //color = clrRed;          break;
  case 2:   //color = clrGreen;        break;
  case 3:   //color = clrYellow;       break;
  case 5:   //color = clrMagenta;      break;
  case 6:   color = clrCyan;         break;
  case 4:   //color = clrBlue;         break;
  case 7:
  default:  color = clrWhite;        break;
  }

  return color;
}



tColor ConScreenGetBackColor(int AnsiColor) {

  tColor color;

  switch (AnsiColor) {
  case -2:  color = clrTransparent;  break;

  case 4:   color = ConScreenGetBackColor(config.TextBackColor);         break;
  case -1:  color = clrGray50;       break;

  case 0:   color = clrBlack;        break;
  case 1:   //color = clrRed;          break;
  case 2:   //color = clrGreen;        break;
  case 3:   //color = clrYellow;       break;
//  case 4:   //color = clrBlue;         break;
  case 5:   //color = clrMagenta;      break;
  case 6:   color = clrCyan;         break;
  case 7:
  default:  color = clrWhite;        break;
  }

  return color;
}



void ConScreenDetermineColors(const sConsCanvasChar& ch, tColor& foreColor, tColor& backColor, bool blink) {

  // preset colors
  foreColor = ConScreenGetColor(config.TextColor);
  backColor = ConScreenGetBackColor(config.TextBackColor);


  // overwrite colors
  if (ch.useFore)
    foreColor = ConScreenGetColor(ch.foreColor);

  if (ch.useBack)
    backColor = ConScreenGetBackColor(ch.backColor);


  // interpret attributes
  if (ch.Bold) {
    foreColor = ConScreenGetColor(config.BoldTextColor);
//    backColor = ConScreenGetColor(config.TextBackColor);
  }


  // prevent unvisible colors
  if (foreColor == backColor && ch.foreColor != ch.backColor) {
    if (foreColor != ConScreenGetBackColor(config.TextBackColor))
      backColor = ConScreenGetBackColor(config.TextBackColor);
    else
      backColor = ConScreenGetBackColor(config.TextColor);
  }


  if (ch.Inverted) {
    tColor tmpColor = foreColor;
    foreColor = backColor;
    backColor = tmpColor;
  }

  if (ch.Concealed || ch.Blink && ! blink)
    foreColor = backColor;
}



void cMenuConsole::Show() {

  if (! _pOsd)
    _pOsd = cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop);

  if (_pOsd) {

    // First try to get the hole screen in full color depth
    tArea areas1[] = {{0, 0, _pixelW - 1, _pixelH - 1, 4}};
    eOsdError err = _pOsd->SetAreas(areas1, 1);
    if (err != oeOk) {

      // The OSD can't handle this large screen - split it up
      const cFont* pFont = cFont::GetFont(fontOsd);
      int lineHeight = pFont->Height();

      tArea areas2[] = { { 0, 0, 0 + _pixelW - 1, 0 + _pixelH - lineHeight - 1, 2 },
                         { 0, 0 + _pixelH - lineHeight, 0 + _pixelW - 1, 0 + _pixelH - 1, 4 }
                       };

      err = _pOsd->SetAreas(areas2, 2);
      if (err != oeOk) {
        delete(_pOsd);
        _pOsd = NULL;
        return;
      }
    }
  }

  // Open the sppecified console
  Open(_consoleNr);
}



void cMenuConsole::Open(int consoleNr) {

  // Deactive old console
  if (_pConsole)
    _pConsole->getScreen().WantRefreshEvent(false);


  FlushRemote();


  // Open new console
  _consoleNr      = consoleNr;
  _blink          = true; _blinkOld = false;
  _inputState     = 0;
  _toRing         = 0;

  _pConsole = &gl_pConsConsoles->getItem(consoleNr);


  // It can be that the OSD size has changed
  _pConsole->setTerminalSize(_charsW, _charsH, _pixelW, _pixelH);

  _pConsole->getScreen().WantRefreshEvent(true);

  if (! _inputActivated && config.AutoEnterKeyboardMode && _pConsole->IsOpen()) {
    CaptureKeyboard();
  }


  if (config.InfobarTimeout) {

    Display(false); // flush = false
    DisplayStateInfo(true); // autoClose = true
  } else {

    Display(true); // flush = true
  }
}



void cMenuConsole::Display(bool flush) {

  if (! _pOsd || ! _pBitmap || ! _pConsole)
    return;

  tColor screenBackColor = ConScreenGetColor(config.TextBackColor);

//  _pBitmap->DrawRectangle(0, 0, _pixelW - 1, _pixelH - 1, screenBackColor);
  _pBitmap->DrawRectangle(_pixelW - _charsW * _charW, 0, _pixelW - 1, _pixelH - 1, screenBackColor);
  _pBitmap->DrawRectangle(0, _pixelH - _charsH * _charH, _pixelW - 1, _pixelH - 1, screenBackColor);


  // the screen shouldn't be changed while we are painting
  cMutexLock l(_pConsole->getScreen().getMutex());

  cConsTerminalEmulation& screen = _pConsole->getScreen();

  for (int row = 0; row < _charsH; ++row) {

    const sConsCanvasChar* screenRow = screen.getCanvas()[row];
    for (int col = 0; col < _charsW; ++col) {

      sConsCanvasChar &cell = (sConsCanvasChar&)screenRow[col];

      tColor foreColor, backColor;
      ConScreenDetermineColors(cell, foreColor, backColor, _blink);

      // print char
      WriteChar(*_pBitmap, *_pFont, col * _charW, row * _charH, cell.ch, foreColor, backColor);

      if (cell.Bold) {
        WriteChar(*_pBitmap, *_pFont, col * _charW + 1, row * _charH, cell.ch, foreColor, clrConsoleTransparent);
      }

      // Draw if underlined
      if (cell.Underscore) {

        consDrawLine(*_pBitmap, col * _charW, row * _charH + _charH - 1, col * _charW + _charW - 1, row * _charH + _charH - 1, foreColor);
        // Repeat the line for flicker freeing ;-)
        consDrawLine(*_pBitmap, col * _charW, row * _charH + _charH - 2, col * _charW + _charW - 1, row * _charH + _charH - 2, foreColor);
      }
    }
  }


  // let the cursor blink
  bool cursorVisible = (_blink && _pConsole->IsOpen() && _pConsole->getScreen().getCursorVisible());

  // show cursor
  if (cursorVisible) {

    int col = screen.getCursorX(), row = screen.getCursorY();
    if (row < _charsH) { 

      // the cursor can't get out of the screen
      if (col >= _charsW)
        col = _charsW - 1;

      sConsCanvasChar& cell = (sConsCanvasChar&) screen.getCanvas()[row][col];

      tColor foreColor, backColor;
      ConScreenDetermineColors(cell, foreColor, backColor, true);


      // use the colors inverse!
      WriteChar(*_pBitmap, *_pFont, col * _charW, row * _charH, cell.ch, backColor, foreColor);


      // Alternative cursor
//      if ( 0 ) {
//        int colorIndex = _pBitmap->Index(ConScreenGetColor(config.TextColor));
//        for (int x = col * _charW + 1; x <= col * _charW + 2; ++x) {
//          for (int y = row * _charH; y < row * _charH + _charH; ++y) {
//            _pBitmap->SetIndex(x, y, colorIndex);
//          }
//        }
//      }
    }
  }


  if (_toRing && ! _blink) {
    cBitmap bell(consBell_xpm);
    _pBitmap->DrawBitmap(_pixelW - 30, 0, bell, clrWhite, screenBackColor);
  }

  if (_inputState || (! _inputActivated && ! _blink)) {
    cBitmap keyboard(consKeyboard_xpm);
    _pBitmap->DrawBitmap(_pixelW - 60, 0, keyboard, clrWhite, screenBackColor);
  }



  // Draw the Bitmap to the screen
  // -----------------------------
  _pOsd->DrawBitmap(0, 0, *_pBitmap);


  _blinkOld = _blink;

  screen.Refreshed();

  // Decrease the display latency
  if (flush)
    _pOsd->Flush();
}



void cMenuConsole::AddSubMenu(cOsdObject* pSubMenu) {

  if (_pSubMenu)
    CloseSubMenu();

  _pSubMenu = pSubMenu;

  if (_pSubMenu)
    _pSubMenu->Show();
}



eOSState cMenuConsole::CloseSubMenu() {

  if (_pSubMenu) {

    delete _pSubMenu;
    _pSubMenu = NULL;
//    _pOsd->Flush();
  }

  return osContinue;
}



eOSState cMenuConsole::ProcessKey(eKeys Key) {

  eOSState state = osUnknown;

  if (_pSubMenu)
    state = _pSubMenu->ProcessKey(Key);


  switch (state) {

  case osBack:
    CloseSubMenu();
    if (_pOsd) _pOsd->Flush();
    return osContinue;

  case osUser1:
    state = CloseSubMenu();
    ProcessKey(Key);
    break;

  case osUnknown:

    switch (BASICKEY(Key)) {

    case kKbd:{     uint64 buf = cKbdRemote::MapFuncToCode(KEYKBD(Key));
                    WriteToConsole(buf);

                    // On every key press, we show the cursor immediately
                    // so the user can see where it stayes.
                    _blink = true;

                    // if the console were closed then release the captured keyboard
                    if (_inputActivated && ! _pConsole->IsOpen()) {
                      ReleaseKeyboard();
                      state = osUser1;    // refresh the title bar
                    }

                    break;
    }
    case kNone:
                    // Handle blinking elements
                    if ((int)cTimeMs::Now() >= _nextBlinkTime) {

                      _nextBlinkTime = cTimeMs::Now() + config.BlinkRate * 100;
                      _blink = !_blink;
                    }

                    // Handle keyboard releaseing
                    if ((int)cTimeMs::Now() >= _timeReleaseCapture) {
                      _timeReleaseCapture = INT_MAX;

                      // Time elapsed -> repost ESC...
                      unsigned char tmp = ESC;
                      _pConsole->Write(&tmp, 1);

                      // ...reset state machine
                      _inputState = 0;
                    }
                    // Fall through
    case kRefresh:{
                    if (_pConsole->getScreen().ToRefresh() || _blink != _blinkOld) {
                      Display(true); // flush = true
                    }

                    // if the console were closed then release the captured keyboard
                    if (_inputActivated && ! _pConsole->IsOpen()) {
                      ReleaseKeyboard();
                      state = osUser1;    // refresh the title bar
                    }

                    if (_pConsole->getScreen().ToRing()) {
                      _pConsole->getScreen().BellSeen();

                      if (config.BellTimeout) {
                        _toRing = cTimeMs::Now() + config.BellTimeout * 1000;
                        state = osUser1;
                      }
                    } else if (_toRing && (int)cTimeMs::Now() > _toRing) {
                      _toRing = 0;
                      state = osUser1;
                    }

                    // We have to shutdown a terminated process
                    if (! _pConsole->IsOpen())
                      _pConsole->HasClosed(true);

                    break;
    }

    case kOk:{
                    DisplayStateInfo(false);
                    break;
    }

    case kLeft:{    // Switch to previous console
                    if (_consoleNr > 0) {
                      gl_pConsConsoles->WantAllRefreshEvents(false);
                      Open(_consoleNr - 1);
                    }
                    return osContinue;
    }

    case kRight:{   // Switch to next console
                    if (_consoleNr < gl_pConsConsoles->getCount() - 1) {
                      gl_pConsConsoles->WantAllRefreshEvents(false);
                      Open(_consoleNr + 1);
                    }
                    return osContinue;
    }

    case kRed:{     // Create a new console
                    int newIndex = gl_pConsConsoles->CreateConsole();
                    if (newIndex >= 0) {
                      gl_pConsConsoles->WantAllRefreshEvents(false);
                      Open(newIndex);
                    }
                    return osContinue;
    }

    case kGreen:{   // Open commands list
                    gl_pConsConsoles->SetSelectConsoleNr(_consoleNr);
                    // Use the trick with our own key macro to redisplay the console list.
                    cRemote::PutMacro((eKeys)consoleOwnerMacro);
                    cRemote::Put((eKeys)kGreen);
                    return osContinue;
    }

    case kYellow:   // Enter keyboard mode
                    if (! _inputActivated && _pConsole->IsOpen()) {
                      CaptureKeyboard();
                    } else if (_inputActivated) {
                      ReleaseKeyboard();
                    }
                    return osContinue;

    case kBlue:{    // Terminate or close console
                    if (TerminateConsole()) {
                      gl_pConsConsoles->WantAllRefreshEvents(false);
                      return osContinue;
                    }
                    return osContinue;
    }

    case kBack:{    // Switch back to console list
                    gl_pConsConsoles->SetSelectConsoleNr(_consoleNr);
                    // Use the trick with our own key macro to redisplay the console list.
                    cRemote::PutMacro((eKeys)(consoleOwnerMacro));
                    return osContinue;
    }

    default:        state = osUnknown;
    }
    break;

  default: break;
  }

  return state;
}



bool cMenuConsole::TerminateConsole() {

  if (! _pConsole->IsOpen() || Confirm(tr("Terminate console?"))) {

    Status(tr("Terminate console..."));
    _pConsole->Close();
    gl_pConsConsoles->Remove(_pConsole);
    Status(NULL);

    // If there are other consoles...
    int consoleNr = _consoleNr;
    if (consoleNr >= gl_pConsConsoles->getCount())
      consoleNr = gl_pConsConsoles->getCount() - 1;

    if (consoleNr >= 0) {

      // ...open next console
      Open(consoleNr);

    } else {

      // ... else open console list
      gl_pConsConsoles->SetOpenConsoleNr(-1);
      gl_pConsConsoles->SetSelectConsoleNr(-1);
      // Use the trick with our own key macro to redisplay the console list.
      cRemote::PutMacro((eKeys)consoleOwnerMacro);
    }
    return true;
  }
  return false;
}



void cMenuConsole::DisplayStateInfo(bool autoClose) {

  cConsDialog* pSubMenu = new cConsDialog(_pOsd);

  pSubMenu->SetAutoClose(autoClose);
  char buffer[120];
  sprintf(buffer, "%d -  %s", _consoleNr + 1, _pConsole->getTitle());
  pSubMenu->SetTitle(buffer);

  sprintf(buffer, "%s\t%s", 
    _consoleNr > 0 ? "<<" : "",
    _consoleNr < gl_pConsConsoles->getCount() - 1 ? ">>" : "");
  pSubMenu->SetMessage(buffer);

  pSubMenu->SetButtons( tr("New"),
    gl_ConsoleCommands.Count() > 0 ? tr("Commands") : NULL,
    _inputActivated ? tr("Leave keyboard") : tr("Enter keyboard"),
    _pConsole->IsOpen() ? tr("Terminate") : tr("Close"));

  AddSubMenu(pSubMenu);
}



void cMenuConsole::Status(const char* message) {

  if (message) {

    cConsDialog* pSubMenu = new cConsDialog(_pOsd);

    pSubMenu->SetTitle(message);

    AddSubMenu(pSubMenu);

  } else {
    CloseSubMenu();
  }
}



bool cMenuConsole::Confirm(const char* message) {

  cConsDialog* pSubMenu = new cConsDialog(_pOsd);

  pSubMenu->SetTitle(message);

  pSubMenu->Show();
//  cStatus::MsgOsdStatusMessage(s);

  FlushRemote();
  eKeys k = Interface->Wait(Setup.OSDMessageTime);

  delete pSubMenu;

  return (k == kOk);
}



void cMenuConsole::WriteToConsole(const uint64& code) {

  unsigned char* data = (unsigned char*)(void*)&code;

  for (int i = 7; i >= 0; --i) {

    if (data[i] > 0) {

      switch (_inputState) {

        // Normal state
        case 0: {

          // esc detected. Ask for exit
          if (data[i] == ESC) {

            _timeReleaseCapture = cTimeMs::Now() + 1000;
            _inputState = 1;

          } else
            _pConsole->Write(data + i, 1);

          break;
        }
        // asked for exit
        case 1: {

          if (data[i] == ESC) {
            // Exit capture mode
            ReleaseKeyboard();           // Release, so the user can use RC keys

          } else {

            // post esc + new char
            unsigned char tmp = ESC;
            _pConsole->Write(&tmp, 1);
            _pConsole->Write(data + i, 1);
          }
          _timeReleaseCapture = INT_MAX;
          _inputState = 0;
          break;
        }
        default: assert( false );// ERROR: Shit happened
      }
    }
  }
}





// --- cMenuConsoleListItem -----------------------------------------------------

class cMenuConsoleListItem : public cOsdItem
{
private:
  int _Nr;

public:
  cMenuConsoleListItem(int Nr);
  virtual void Set();
};





cMenuConsoleListItem::cMenuConsoleListItem(int Nr)
: _Nr(Nr)
{
  Set();
}



void cMenuConsoleListItem::Set() {

    cConsVirtualConsole& console = gl_pConsConsoles->getItem(_Nr);

    bool closed = ! console.IsOpen();
    bool bell   =   console.getScreen().ToRing();

    char buf[40];

//#ifdef DEBUG_OSD

    snprintf(buf, sizeof(buf), "%c %s%s%s%s%c",
                  _Nr < 9 ? _Nr + '1' : ' ',
                  console.getTitle(),
                  closed || bell ? " - "         : "",
                  closed         ? tr("stopped") : "",
                  closed && bell ? " "           : "",
                  bell           ? SYMBOL_BELL   : ' ' );

//#else
/*
    snprintf(buf, sizeof(buf), "%c %s%s%s",
                  _Nr < 9 ? _Nr + '1' : ' ',
                  console.getTitle(),
                  closed ? " - "         : "",
                  closed ? tr("stopped") : ""  );
*/
//#endif

//    Interface->WriteText(0, Offset + 2, buf, FgColor, BgColor);
    SetText(buf);


//#ifndef DEBUG_OSD
/*
    // Symbol for bell signal
    if (bell) {

      cFontConsole font(ConsoleFontSym);

      cBitmap bitmap(20, cOsd::LineHeight(), 1);
      bitmap.Fill(0, 0, 19, cOsd::LineHeight() - 1, BgColor);

      WriteChar(bitmap, font, 0, 0, 34, FgColor, BgColor);

      Interface->SetBitmap(cOsd::CellWidth() * (Interface->Width() - 6), cOsd::LineHeight() * (Offset + 2), bitmap);
    }
*/
//#endif

}





// --- cMenuConsoleList ---------------------------------------------------------


cMenuConsoleList::cMenuConsoleList()
: cOsdMenu(tr("Consoles"))
{
  Set();
  gl_pConsConsoles->WantAllRefreshEvents(true);
}



cMenuConsoleList::~cMenuConsoleList() {

  gl_pConsConsoles->WantAllRefreshEvents(false);
}



void cMenuConsoleList::Set() {

  int oldCur = Current();

  Clear();
  SetHasHotkeys();

  { cThreadLock l(gl_pConsConsoles);

    for (int i = 0; i < gl_pConsConsoles->getCount(); ++i) {
      Add(new cMenuConsoleListItem(i));
    }
  }



  // Look if there is a entry to be selected
  if (gl_pConsConsoles->SelectConsoleNr() >= 0) {

    oldCur = gl_pConsConsoles->SelectConsoleNr();
    gl_pConsConsoles->SetSelectConsoleNr(-1);
  }

  // Try to set the last used item  
  SetCurrent(Get(oldCur = max(min(oldCur, Count() - 1), 0)));

  UpdateHelp();
}



void cMenuConsoleList::UpdateHelp() {

  SetHelp( tr("New"),
           gl_ConsoleCommands.Count() > 0 ? tr("Commands") : NULL,
           NULL, 
           gl_pConsConsoles->getCount() > 0 && Current() >= 0 ?
             gl_pConsConsoles->getItem(Current()).IsOpen() ?
               tr("Terminate") : tr("Close") : NULL );
}



bool cMenuConsoleList::TerminateConsole() {

  if (Current() >= 0) {

    assert(Current() < gl_pConsConsoles->getCount());

    cConsVirtualConsole& console = gl_pConsConsoles->getItem(Current());
    if (! console.IsOpen() || Interface->Confirm(tr("Terminate console?"))) {

      Skins.Message(mtStatus, tr("Terminate console..."));
      Skins.Flush();
      console.Close();
      gl_pConsConsoles->Remove(&console);
      Skins.Message(mtStatus, NULL);
      Set();
      return true;
    }
  }
  return false;
}



eOSState cMenuConsoleList::ProcessKey(eKeys Key) {

  eOSState state = cOsdMenu::ProcessKey(Key);

  switch (state) {

    case osUnknown: {

      switch (Key) {
        case kOk:      if (Current() >= 0) {
                         gl_pConsConsoles->WantAllRefreshEvents(false);
                         gl_pConsConsoles->getItem(Current()).getScreen().BellSeen();
                         gl_pConsConsoles->SetOpenConsoleNr(Current());
                         return osPlugin;
                       }
                       return osContinue;

        case kRed:{    int newIndex = gl_pConsConsoles->CreateConsole();
                       if (newIndex >= 0) {
                         gl_pConsConsoles->WantAllRefreshEvents(false);
                         gl_pConsConsoles->SetOpenConsoleNr(newIndex);
                         return osPlugin;
                       }
                       return osContinue;
        }
        case kGreen:   if (gl_ConsoleCommands.Count() > 0) {
                         gl_pConsConsoles->WantAllRefreshEvents(false);
                         return AddSubMenu(new cMenuConsoleCommands());
                       }
                       return osContinue;

        case kBlue:    gl_pConsConsoles->WantAllRefreshEvents(false);
                       if (TerminateConsole())
                         Display();
                       gl_pConsConsoles->WantAllRefreshEvents(true);
                       return osContinue;

        case kRefresh: // A console has changed its state -> refresh the list
                       Set();
                       Display();
                       return osContinue;

        default:       state = osContinue;
      }
    }
    default: break;
  }

  if (Key != kNone && ! HasSubMenu())
    UpdateHelp();

  return state;
}





// --- cMenuConsoleCommands --------------------------------------------------

cMenuConsoleCommands::cMenuConsoleCommands(void)
: cOsdMenu(tr("Commands"))
{
  SetHasHotkeys();

  int i = 0;
  cConsoleCommand *pCommand;

  while ((pCommand = gl_ConsoleCommands.Get(i)) != NULL) {
    Add(new cOsdItem(hk(pCommand->Title())));
    i++;
  }
}



eOSState cMenuConsoleCommands::Execute() {

  cConsoleCommand *pCommand = gl_ConsoleCommands.Get(Current());
  if (pCommand) {

     int newIndex = gl_pConsConsoles->CreateCommand(pCommand->Title(), pCommand->Command());
     if (newIndex >= 0) {

       gl_pConsConsoles->getItem(newIndex).getScreen().BellSeen();
       gl_pConsConsoles->SetOpenConsoleNr(newIndex);
       return osPlugin;
     }
  }
  return osContinue;
}



eOSState cMenuConsoleCommands::ProcessKey(eKeys Key) {

  eOSState state = cOsdMenu::ProcessKey(Key);

  if (state == osUnknown) {
     switch (Key) {
       case kOk:  return Execute();
       default:   break;
     }
  }
  return state;
}


