/* NeutrinoSkin.c
 *
 * This file is part of vdr-skinneutrino
 * Copyright (C) 2007 Sascha Wessel <sawe@users.sf.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <map>
#include <list>
#include <vector>
#include <string>
#include <algorithm>

#include <stdlib.h>
#include <stdio.h>
#include <iconv.h>
#include <time.h>
#include <sys/time.h>
#include <regex.h>
#include <vdr/plugin.h>
#include <vdr/menu.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H

#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
#define __builtin_expect(x, expected_value) (x)
#endif

#define likely(x) __builtin_expect((x), 1)
#define unlikely(x) __builtin_expect((x), 0)

namespace NeutrinoSkin {

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

const char *VERSION        = "0.0.0";
const char *DESCRIPTION    = "Neutrino Skin";
const char *MAINMENUENTRY  = NULL;

cTheme Theme;

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

#include "symbols/teletext.xpm"
#include "symbols/radio.xpm"
#include "symbols/audio.xpm"
#include "symbols/dolbydigital.xpm"
#include "symbols/encrypted.xpm"
#include "symbols/recording.xpm"

#include "symbols/volume.xpm"
#include "symbols/volume_mute.xpm"

#include "fonts/micron.h"
#include "fonts/micron_bold.h"
#include "fonts/micron_italic.h"

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

// FIXME remove
#define ScrollWidth  5
//THEME_CLR(Theme, clrBackground,             clrGray50);
THEME_CLR(Theme, clrButtonRedFg,            clrWhite);
THEME_CLR(Theme, clrButtonRedBg,            clrRed);
THEME_CLR(Theme, clrButtonGreenFg,          clrBlack);
THEME_CLR(Theme, clrButtonGreenBg,          clrGreen);
THEME_CLR(Theme, clrButtonYellowFg,         clrBlack);
THEME_CLR(Theme, clrButtonYellowBg,         clrYellow);
THEME_CLR(Theme, clrButtonBlueFg,           clrWhite);
THEME_CLR(Theme, clrButtonBlueBg,           clrBlue);
THEME_CLR(Theme, clrMessageStatusFg,        clrBlack);
THEME_CLR(Theme, clrMessageStatusBg,        clrCyan);
THEME_CLR(Theme, clrMessageInfoFg,          clrBlack);
THEME_CLR(Theme, clrMessageInfoBg,          clrGreen);
THEME_CLR(Theme, clrMessageWarningFg,       clrBlack);
THEME_CLR(Theme, clrMessageWarningBg,       clrYellow);
THEME_CLR(Theme, clrMessageErrorFg,         clrWhite);
THEME_CLR(Theme, clrMessageErrorBg,         clrRed);
THEME_CLR(Theme, clrVolumePrompt,           clrGreen);
THEME_CLR(Theme, clrVolumeBarUpper,         clrWhite);
THEME_CLR(Theme, clrVolumeBarLower,         clrGreen);
//THEME_CLR(Theme, clrChannelName,            clrWhite);
//THEME_CLR(Theme, clrChannelDate,            clrWhite);
//THEME_CLR(Theme, clrChannelEpgTimeFg,       clrWhite);
//THEME_CLR(Theme, clrChannelEpgTimeBg,       clrRed);
//THEME_CLR(Theme, clrChannelEpgTitle,        clrCyan);
//THEME_CLR(Theme, clrChannelEpgShortText,    clrYellow);
//THEME_CLR(Theme, clrMenuTitleFg,            clrBlack);
//THEME_CLR(Theme, clrMenuTitleBg,            clrCyan);
//THEME_CLR(Theme, clrMenuDate,               clrBlack);
//THEME_CLR(Theme, clrMenuItemCurrentFg,      clrBlack);
//THEME_CLR(Theme, clrMenuItemCurrentBg,      clrCyan);
//THEME_CLR(Theme, clrMenuItemSelectable,     clrWhite);
//THEME_CLR(Theme, clrMenuItemNonSelectable,  clrCyan);
THEME_CLR(Theme, clrMenuEventTime,          clrWhite);
THEME_CLR(Theme, clrMenuEventVpsFg,         clrBlack);
THEME_CLR(Theme, clrMenuEventVpsBg,         clrWhite);
THEME_CLR(Theme, clrMenuEventTitle,         clrCyan);
THEME_CLR(Theme, clrMenuEventShortText,     clrWhite);
THEME_CLR(Theme, clrMenuEventDescription,   clrCyan);
THEME_CLR(Theme, clrMenuScrollbarTotal,     clrWhite);
THEME_CLR(Theme, clrMenuScrollbarShown,     clrCyan);
THEME_CLR(Theme, clrMenuText,               clrWhite);
THEME_CLR(Theme, clrReplayTitle,            clrWhite);
THEME_CLR(Theme, clrReplayCurrent,          clrWhite);
THEME_CLR(Theme, clrReplayTotal,            clrWhite);
THEME_CLR(Theme, clrReplayModeJump,         clrWhite);
THEME_CLR(Theme, clrReplayProgressSeen,     clrGreen);
THEME_CLR(Theme, clrReplayProgressRest,     clrWhite);
THEME_CLR(Theme, clrReplayProgressSelected, clrRed);
THEME_CLR(Theme, clrReplayProgressMark,     clrBlack);
THEME_CLR(Theme, clrReplayProgressCurrent,  clrRed);

/******************************************************************************
 *
 * colors
 *
 * dbox2/apps/tuxbox/neutrino/src/neutrino.cpp
 * dbox2/apps/tuxbox/neutrino/src/system/settings.h
 * dbox2/apps/tuxbox/neutrino/src/gui/color.cpp
 *
 ******************************************************************************/

#define NEUTRINO2ARGB(a, r, g, b) ( \
		((255 - (((a) * 255) / 100)) << 24) | \
		((((r) * 255) / 100) << 16) | \
		((((g) * 255) / 100) <<  8) | \
		((((b) * 255) / 100)      ) )

enum {
	clrNeutrinoMenuHead			= NEUTRINO2ARGB(0x00, 0x00, 0x0A, 0x19),
	clrNeutrinoMenuHeadText			= NEUTRINO2ARGB(0x00, 0x5f, 0x46, 0x00),
	clrNeutrinoMenuContent			= NEUTRINO2ARGB(0x14, 0x00, 0x0f, 0x23),
	clrNeutrinoMenuContentText		= NEUTRINO2ARGB(0x00, 0x64, 0x64, 0x64),
	clrNeutrinoMenuContentDark		= NEUTRINO2ARGB(0x14, 0x00, 0x09, 0x15), // clrNeutrinoContent * 0.6
	clrNeutrinoMenuContentSelected		= NEUTRINO2ARGB(0x14, 0x19, 0x37, 0x64),
	clrNeutrinoMenuContentSelectedText	= NEUTRINO2ARGB(0x00, 0x00, 0x00, 0x00),
	clrNeutrinoMenuContentInactive		= NEUTRINO2ARGB(0x14, 0x00, 0x0f, 0x23),
	clrNeutrinoMenuContentInactiveText	= NEUTRINO2ARGB(0x00,   55,   70,   85),
	clrNeutrinoInfobar			= NEUTRINO2ARGB(0x14, 0x00, 0x0e, 0x23),

	clrNeutrinoInfobarText			= NEUTRINO2ARGB(0x00, 0x64, 0x64, 0x64),
	clrNeutrinoInfobarShadowPlus7		= clrNeutrinoInfobarText,
	clrNeutrinoInfobarShadowPlus6		= NEUTRINO2ARGB(   3,   85,   86,   88),
	clrNeutrinoInfobarShadowPlus5		= NEUTRINO2ARGB(   6,   71,   73,   75),
	clrNeutrinoInfobarShadowPlus4		= NEUTRINO2ARGB(   9,   57,   59,   63),
	clrNeutrinoInfobarShadowPlus3		= NEUTRINO2ARGB(  11,   43,   46,   51),
	clrNeutrinoInfobarShadowPlus2		= NEUTRINO2ARGB(  14,   29,   32,   39),
	clrNeutrinoInfobarShadowPlus1		= NEUTRINO2ARGB(  17,   14,   19,   26),
	clrNeutrinoInfobarShadow		= NEUTRINO2ARGB(0x14, 0x00, 0x05, 0x0e), // clrNeutrinoInfobar * 0.4
};

THEME_CLR(Theme, clrButtonRed,			0xff880000);
THEME_CLR(Theme, clrButtonGreen,		0xff008800);
THEME_CLR(Theme, clrButtonYellow,		0xff888800);
THEME_CLR(Theme, clrButtonBlue,			0xff000088);

THEME_CLR(Theme, clrChannelShadow,		clrNeutrinoInfobarShadow);
THEME_CLR(Theme, clrChannelNumberFg,		clrNeutrinoInfobarText);
THEME_CLR(Theme, clrChannelNumberBg,		clrNeutrinoInfobar);
THEME_CLR(Theme, clrChannelNumberShadow,	clrNeutrinoInfobarShadow);
//THEME_CLR(Theme, clrChannelNumberLogo1,		clrNeutrinoInfobarShadowPlus2); // FIXME
//THEME_CLR(Theme, clrChannelNumberLogo2,		clrNeutrinoInfobarShadowPlus4);
THEME_CLR(Theme, clrChannelNumberLogo,		clrNeutrinoInfobarShadowPlus4);
THEME_CLR(Theme, clrChannelNameFg,		clrNeutrinoInfobarText);
THEME_CLR(Theme, clrChannelNameBg,		clrNeutrinoInfobar);
THEME_CLR(Theme, clrChannelEventFg,		clrNeutrinoInfobarText);
THEME_CLR(Theme, clrChannelEventBg,		clrNeutrinoInfobar);
THEME_CLR(Theme, clrChannelInfoFg,		clrNeutrinoInfobarText);
THEME_CLR(Theme, clrChannelInfoBg,		clrNeutrinoInfobarShadowPlus1);
THEME_CLR(Theme, clrChannelTimebarFg,		clrNeutrinoInfobarShadowPlus7);
THEME_CLR(Theme, clrChannelTimebarBg,		clrNeutrinoInfobarShadowPlus3);
THEME_CLR(Theme, clrChannelTimebarShadow,	clrNeutrinoInfobarShadow);

THEME_CLR(Theme, clrChannelSymbolOn,		0xffffff00);
THEME_CLR(Theme, clrChannelSymbolOff,		0xff505050); // FIXME 0xffa0a0a0);

THEME_CLR(Theme, clrMenuTitleFg,		clrNeutrinoMenuHeadText);
THEME_CLR(Theme, clrMenuTitleBg,		clrNeutrinoMenuHead);

THEME_CLR(Theme, clrMenuItemFg,			clrNeutrinoMenuContentText);
THEME_CLR(Theme, clrMenuItemBg,			clrNeutrinoMenuContent);
THEME_CLR(Theme, clrMenuItemCurrentFg,		clrNeutrinoMenuContentSelectedText);
THEME_CLR(Theme, clrMenuItemCurrentBg,		clrNeutrinoMenuContentSelected);
//THEME_CLR(Theme, clrMenuItemSelectable,     clrWhite);
//THEME_CLR(Theme, clrMenuItemNonSelectable,  clrCyan);

THEME_CLR(Theme, clrMenuButtonsFg,		clrNeutrinoMenuContentText);
THEME_CLR(Theme, clrMenuButtonsBg,		clrNeutrinoMenuHead);

THEME_CLR(Theme, clrVolumeFg,			clrNeutrinoInfobarText);
THEME_CLR(Theme, clrVolumeBg,			clrNeutrinoInfobar);
THEME_CLR(Theme, clrVolumeBarFg,		clrNeutrinoInfobarShadowPlus3); // FIXME ?
THEME_CLR(Theme, clrVolumeBarBg,		clrNeutrinoInfobarShadowPlus1); // FIXME ?


THEME_CLR(Theme, clrMessageFg,			clrNeutrinoMenuHeadText);
THEME_CLR(Theme, clrMessageBg,			clrNeutrinoMenuHead);

THEME_CLR(Theme, clrBackground,             clrNeutrinoMenuContent); // TODO remove

/******************************************************************************
 *
 * some inline functions
 *
 ******************************************************************************/

inline int max(int a, int b) {
	return std::max(a, b);
}

inline int min(int a, int b) {
	return std::min(a, b);
}

inline int max(int a, int b, int c) {
	return std::max(std::max(a, b), c);
}

inline int min(int a, int b, int c) {
	return std::min(std::min(a, b), c);
}

inline int bitsPerPixel(int NumColors) {
	for (int bpp = 2; bpp <= 8; bpp <<= 1)
		if (NumColors <= (1 << bpp))
			return bpp;
	return 8;
/*
	if (unlikely(NumColors < 3))
		return 2;
	int bpp = 1;
	NumColors >>= bpp;
	do {
		// only 1 2 4 8 ... bpp allowed
		NumColors >>= bpp;
		bpp += bpp;
	} while (NumColors > 0);
	return bpp;
*/
}

inline const char *osdError2string(eOsdError error) {
	switch (error) {
		case oeOk:
			return "oeOk";
		case oeTooManyAreas:
			return "oeTooManyAreas";
		case oeTooManyColors:
			return "oeTooManyColors";
		case oeBppNotSupported:
			return "oeBppNotSupported";
		case oeAreasOverlap:
			return "oeAreasOverlap";
		case oeWrongAlignment:
			return "oeWrongAlignment";
		case oeOutOfMemory:
			return "oeOutOfMemory";
		case oeWrongAreaSize:
			return "oeWrongAreaSize";
		case oeUnknown:
			return "oeUnknown";
		default:
			return "Unknown";
	}
}

inline const char *keyMacro2string(eKeys key) {
	const cKeyMacro *macro = KeyMacros.Get(key);
	if (unlikely(macro == NULL))
		return NULL;

	const char *plugin = macro->Plugin();
	if (plugin != NULL)
		return tr(plugin);

	if (unlikely(macro->NumKeys() < 2))
		return NULL;

	const eKeys *keys = macro->Macro();
	if (unlikely(keys == NULL))
		return NULL;
	else
		return tr(cKey::ToString(keys[1]));
	// TODO keys[2], keys[3], ...
}

inline tColor fadeColor(tColor Color1, int Fade1, tColor Color2, int Fade2) {
	int A1 = (Color1 >> 24) & 0xff;
	int R1 = (Color1 >> 16) & 0xff;
	int G1 = (Color1 >>  8) & 0xff;
	int B1 = (Color1      ) & 0xff;

	int A2 = (Color2 >> 24) & 0xff;
	int R2 = (Color2 >> 16) & 0xff;
	int G2 = (Color2 >>  8) & 0xff;
	int B2 = (Color2      ) & 0xff;

	int sum = Fade1 + Fade2;
	int a = A1 * Fade1 / sum + A2 * Fade2 / sum;
	int r = R1 * Fade1 / sum + R2 * Fade2 / sum;
	int g = G1 * Fade1 / sum + G2 * Fade2 / sum;
	int b = B1 * Fade1 / sum + B2 * Fade2 / sum;

	return (a << 24) | (r << 16) | (g << 8) | b;
}

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

// TODO remove / use string objects instead
struct strCmp {
	bool operator()(const char *s1, const char *s2) const {
		return strcmp(s1, s2) < 0;
	}
};

/******************************************************************************
 *
 * cClock
 *
 ******************************************************************************/

class cClock {
	private:
		int error, line;
		const char *function;
		struct timeval begin;
		int TimevalSub(struct timeval *result, struct timeval *x, struct timeval *y);
	public:
		cClock(int Line, const char *Function);
		~cClock();
		void Print(const char *String);
};

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

cClock::cClock(int Line, const char *Function) {
	this->function = Function;
	this->line = Line;
	error = gettimeofday(&begin, NULL);
}

cClock::~cClock() {
	Print("Finished");
}

void cClock::Print(const char *String) {
	struct timeval end, diff;

	error += gettimeofday(&end, NULL);
	TimevalSub(&diff, &end, &begin);

	// show only values > 0.05 s
	if (diff.tv_sec < 1 && diff.tv_usec < 50000)
		return;

	if (likely(!error))
		isyslog("Neutrino: Clock: %s: %u.%06us [%04d] %s", String, (unsigned)diff.tv_sec, (unsigned)diff.tv_usec, line, function);
}

int cClock::TimevalSub(struct timeval *result, struct timeval *x, struct timeval *y) {
	if (x->tv_usec < y->tv_usec) {
		int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
		y->tv_usec -= 1000000 * nsec;
		y->tv_sec += nsec;
	}
	if (x->tv_usec - y->tv_usec > 1000000) {
		int nsec = (y->tv_usec - x->tv_usec) / 1000000;
		y->tv_usec += 1000000 * nsec;
		y->tv_sec -= nsec;
	}
	result->tv_sec = x->tv_sec - y->tv_sec;
	result->tv_usec = x->tv_usec - y->tv_usec;
	return x->tv_sec < y->tv_sec;
}

//#define CLOCK
//#define CLOCK cClock Clock = cClock(__LINE__, __FUNCTION__);
#define CLOCK cClock Clock = cClock(__LINE__, __PRETTY_FUNCTION__);

/******************************************************************************
 *
 * cFreeTypeFace
 *
 ******************************************************************************/

class cFreeTypeFace {
	friend class cFreeTypeLibrary;
	private:
		FT_Face face;
		wchar_t utfBuff[256];
		std::map<int,int> heightCache;
		std::map<int,int> bottomCache;
		void Geometry(int Size);
	protected:
		cFreeTypeFace(FT_Face Face);
	public:
		~cFreeTypeFace(void);
		wchar_t Utf(int c);
		int Width(int Size, const char *s);
		int Height(int Size);
		int Bottom(int Size);
		cBitmap *Bitmap(const char *s, tColor ColorFg, tColor ColorBg, int Size, int Bpp, int Width = 0, int Height = 0, eTextAlignment Alignment = taDefault);
};

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

cFreeTypeFace::cFreeTypeFace(FT_Face Face) { CLOCK
	face = Face;

	// FIXME iconv stuff only needed once -> cFreeTypeLibrary
	iconv_t cd = iconv_open("WCHAR_T", "ISO8859-15"); // FIXME
	if (unlikely(cd == (iconv_t)-1)) {
		isyslog("Neutrino: Iconv: encoding not supported");
		return;
	}
	for (int i = 0; i < 256; i++) {
		char c = i;
		size_t inLen = 1, outLen = sizeof(wchar_t);
		char *inBuff = (char *) &c, *outBuff = (char *) &utfBuff[i];
		size_t count = iconv(cd, &inBuff, &inLen, &outBuff, &outLen);
		if (unlikely(count < 0))
			utfBuff[i] = 0;
	}
}

cFreeTypeFace::~cFreeTypeFace(void) { CLOCK
	FT_Done_Face(face);
}

wchar_t cFreeTypeFace::Utf(int c) { CLOCK
	c &= 0xff;
	wchar_t w = utfBuff[c];
	if (likely(w != 0))
		return w;
	else
		return c;
}

int cFreeTypeFace::Width(int Size, const char *s) { CLOCK
	FT_Error error = FT_Set_Char_Size(face, 0, Size * 64, 0, 0);
	if (unlikely(error)) {
		isyslog("Neutrino: FreeType: setting character size failed");
		return 0;
	}

	FT_GlyphSlot slot = face->glyph;
	FT_Bool useKerning = FT_HAS_KERNING(face);
	FT_UInt previous = 0;
	int width = 0;
	int len = strlen(s);
	for (int n = 0; n < len; n++) {
		FT_UInt glyphIndex = FT_Get_Char_Index(face, Utf(s[n]));
		if (likely(useKerning && previous && glyphIndex)) {
			FT_Vector delta;
			FT_Get_Kerning(face, previous, glyphIndex, FT_KERNING_DEFAULT, &delta);
			width += delta.x >> 6;
		}
		FT_Error error = FT_Load_Glyph(face, glyphIndex, FT_LOAD_DEFAULT);
		if (unlikely(error))
			continue;
		width += slot->advance.x >> 6;
		previous = glyphIndex;
	}
	return width;
}

void cFreeTypeFace::Geometry(int Size) { CLOCK
	FT_Error error = FT_Set_Char_Size(face, 0, Size * 64, 0, 0);
	if (unlikely(error)) {
		isyslog("Neutrino: FreeType: setting character size failed");
		return;
	}

	FT_BBox bbox;
	bbox.yMin = 32000;
	bbox.yMax = - 32000;
	for (int n = 0; n < 256; n++) {
		FT_UInt glyphIndex = FT_Get_Char_Index(face, Utf(n));
		error = FT_Load_Glyph(face, glyphIndex, FT_LOAD_DEFAULT);
		if (unlikely(error))
			continue;
		FT_Glyph glyph;
		error = FT_Get_Glyph(face->glyph, &glyph);
		if (unlikely(error))
			continue;
		FT_BBox glyphBbox;
		FT_Glyph_Get_CBox(glyph, ft_glyph_bbox_pixels, &glyphBbox);

		if (unlikely(glyphBbox.yMin < bbox.yMin))
			bbox.yMin = glyphBbox.yMin;
		if (unlikely(glyphBbox.yMax > bbox.yMax))
			bbox.yMax = glyphBbox.yMax;
	}

	if (unlikely(bbox.yMin > bbox.yMax))
		return;

	int height = bbox.yMax - bbox.yMin;
	int bottom = bbox.yMax;

	if (likely(heightCache.find(Size) == heightCache.end()))
		heightCache[Size] = height;
	if (likely(bottomCache.find(Size) == bottomCache.end()))
		bottomCache[Size] = bottom;
}

int cFreeTypeFace::Height(int Size) { CLOCK
	std::map<int,int>::iterator i = heightCache.find(Size);
	if (likely(i != heightCache.end()))
		return i->second;
	Geometry(Size);
	i = heightCache.find(Size);
	if (likely(i != heightCache.end()))
		return i->second;
	return 0;
}

int cFreeTypeFace::Bottom(int Size) { CLOCK
	std::map<int,int>::iterator i = bottomCache.find(Size);
	if (likely(i != bottomCache.end()))
		return i->second;
	Geometry(Size);
	i = bottomCache.find(Size);
	if (likely(i != bottomCache.end()))
		return i->second;
	return 0;
}

cBitmap *cFreeTypeFace::Bitmap(const char *s, tColor ColorFg, tColor ColorBg, int Size, int Bpp, int Width, int Height, eTextAlignment Alignment) { CLOCK
	// TODO Bpp == 1
	FT_Error error = FT_Set_Char_Size(face, 0, Size * 64, 0, 0);
	if (unlikely(error))
		isyslog("Neutrino: FreeType: setting character size failed");
	if (unlikely(Bpp > 8))
		return NULL;

	int MAX_GLYPHS = 1024;
	FT_GlyphSlot slot = face->glyph;
	FT_Bool useKerning = FT_HAS_KERNING(face);
	FT_UInt previous = 0;
	FT_UInt numGlyphs = 0;
	FT_Glyph glyphs[MAX_GLYPHS];
	FT_Vector pos[MAX_GLYPHS];
	int w = 0;
	int len = strlen(s);
	if (unlikely(MAX_GLYPHS < len))
		len = MAX_GLYPHS;
	for (int n = 0; n < len; n++) {
		FT_UInt glyphIndex = FT_Get_Char_Index(face, Utf(s[n]));
		if (likely(useKerning && previous && glyphIndex)) {
			FT_Vector delta;
			FT_Get_Kerning(face, previous, glyphIndex, FT_KERNING_DEFAULT, &delta);
			w += delta.x >> 6;
		}
		pos[numGlyphs].x = w;

		error = FT_Load_Glyph(face, glyphIndex, FT_LOAD_DEFAULT);
		if (unlikely(error))
			continue;

		error = FT_Get_Glyph(face->glyph, &glyphs[numGlyphs]);
		if (unlikely(error))
			continue;

		w += slot->advance.x >> 6;
		previous = glyphIndex;
		numGlyphs++;
	}

	if (likely(Height == 0))
		Height = this->Height(Size);

	if (likely(Width == 0))
		Width = w;

	cBitmap *bitmap = new cBitmap(Width, Height, Bpp);
	if (unlikely(bitmap == NULL))
		return NULL;

	// set palette, 0 = ColorBg, maxIndex = ColorFg
	int maxIndex = (1 << Bpp) - 1;
	for (int index = 0; index <= maxIndex; index++) {
		// FIXME use fadeColor(...)

		int fgA = (ColorFg >> 24) & 0xff;
		int fgR = (ColorFg >> 16) & 0xff;
		int fgG = (ColorFg >>  8) & 0xff;
		int fgB = (ColorFg      ) & 0xff;

		int bgA = (ColorBg >> 24) & 0xff;
		int bgR = (ColorBg >> 16) & 0xff;
		int bgG = (ColorBg >>  8) & 0xff;
		int bgB = (ColorBg      ) & 0xff;

		int j = maxIndex - index;
		int a = fgA * index / maxIndex + bgA * j / maxIndex;
		int r = fgR * index / maxIndex + bgR * j / maxIndex;
		int g = fgG * index / maxIndex + bgG * j / maxIndex;
		int b = fgB * index / maxIndex + bgB * j / maxIndex;

		tColor color = (a << 24) | (r << 16) | (g << 8) | b;
		bitmap->SetColor(index, color);
	}

	int startX = 0;
	int startY = 0;
	if (unlikely(Alignment & taBottom))
		startY = Height - this->Height(Size);
	else if (!(Alignment & taTop))
		startY = (Height - this->Height(Size)) / 2;
	if (unlikely(Alignment & taRight))
		startX = Width - w;
	else if (!(Alignment & taLeft))
		startX = (Width - w) / 2;

	int bottom = this->Bottom(Size);
	for (unsigned n = 0; n < numGlyphs; n++) {
		FT_Glyph image = glyphs[n];
		FT_Vector pen;
		pen.x = (startX + pos[n].x) * 64;
		pen.y = (-startY) * 64;

		error = FT_Glyph_To_Bitmap(&image, FT_RENDER_MODE_NORMAL, &pen, true);
		if (unlikely(error))
			continue;

		FT_BitmapGlyph bit = (FT_BitmapGlyph) image;

		int x = bit->left;
		int y = bottom - bit->top;
		int b = 8 - Bpp;

		for (int i = x, p = 0; i < x + bit->bitmap.width; i++, p++)
			for (int j = y, q = 0; j < y + bit->bitmap.rows; j++, q++)
				bitmap->SetIndex(i, j, bit->bitmap.buffer[q * bit->bitmap.width + p] >> b);

		FT_Done_Glyph(image);
	}
	return bitmap;
}

/******************************************************************************
 *
 * cFreeTypeLibrary
 *
 ******************************************************************************/

class cFreeTypeLibrary {
	private:
		FT_Library library;
		FT_Error error;
	public:
		cFreeTypeLibrary(void);
		~cFreeTypeLibrary(void);
		cFreeTypeFace *LoadFace(const char *filename);
		cFreeTypeFace *LoadFaceFromMemory(const unsigned char *data, long size);
};

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

cFreeTypeLibrary::cFreeTypeLibrary(void) { CLOCK
	error = FT_Init_FreeType(&library);
	if (unlikely(error))
		isyslog("Neutrino: FreeType: library object initialization failed");
}

cFreeTypeLibrary::~cFreeTypeLibrary(void) { CLOCK
	error = FT_Done_FreeType(library);
	if (unlikely(error))
		isyslog("Neutrino: FreeType: library object destruction failed");
}

cFreeTypeFace *cFreeTypeLibrary::LoadFace(const char *filename) { CLOCK
	if (unlikely(error))
		return NULL;

	FT_Face face;
	error = FT_New_Face(library, filename, 0, &face);

	if (unlikely(error == FT_Err_Unknown_File_Format)) {
		isyslog("Neutrino: FreeType: the font file could be opened and read, but it appears that its font format is unsupported: %s", filename);
		return NULL;
	} else if (unlikely(error)) {
		isyslog("Neutrino: FreeType: the font file could not be opened or read or is broken: %s", filename);
		return NULL;
	}

	error = FT_Select_Charmap(face, FT_ENCODING_UNICODE);
	if (unlikely(error))
		isyslog("Neutrino: FreeType: selecting encoding failed");

	return new cFreeTypeFace(face);
}

cFreeTypeFace *cFreeTypeLibrary::LoadFaceFromMemory(const unsigned char *data, long size) { CLOCK
	if (unlikely(error))
		return NULL;

	FT_Face face;
	error = FT_New_Memory_Face(library, data, size, 0, &face);

	if (unlikely(error)) {
		isyslog("Neutrino: FreeType: loading builtin font failed");
		return NULL;
	}

	error = FT_Select_Charmap(face, FT_ENCODING_UNICODE);
	if (unlikely(error))
		isyslog("Neutrino: FreeType: selecting encoding failed");

	return new cFreeTypeFace(face);
}

/******************************************************************************
 *
 * cLogoConfigFile
 *
 ******************************************************************************/

class cLogoConfigFile {
	private:
		int errnum;
		struct cLine {
			regex_t *regexp;
			std::vector<const char *> *fileNames;
		};
		std::vector<cLine *> lines;
	public:
		cLogoConfigFile(const char *ConfigFileName);
		~cLogoConfigFile(void);
		const char *Error(void);
		int LogoFileNames(const char *Name, std::vector<const char *> *FileNames);
};

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

cLogoConfigFile::cLogoConfigFile(const char *ConfigFileName) { CLOCK
	if (unlikely(ConfigFileName == NULL))
		return;

	errnum = 0;
	FILE *f = fopen(ConfigFileName, "r");

	if (unlikely(f == NULL)) {
		errnum = errno;
		return;
	}

	int n;
	size_t len;
	char *buffer = NULL;
	while ((n = getline(&buffer, &len, f)) > 0) {
		if (unlikely(buffer[0] != '^'))
			continue;

		// find regexp
		int m = n;
		do {
			m--;
		} while (m > 0 && buffer[m] != '$');
		if (unlikely(m == 0))
			continue;
		m++;
		if (buffer[m] != 0) {
			buffer[m] = 0;
			m++;
		}

		// regcomp
		regex_t *regexp = new regex_t;
		if (unlikely(regcomp(regexp, buffer, REG_EXTENDED | REG_NOSUB | REG_NEWLINE) != 0)) {
			delete regexp;
			continue;
		}

		// read fileNames
		std::vector<const char *> *fileNames = new std::vector<const char *>();
		char *fileName = strtok(buffer + m, " \t\r\n");
		while (fileName != NULL) {
			char *fileNameCopy = new char[strlen(fileName) + 1];
			strcpy(fileNameCopy, fileName);
			fileNames->push_back(fileNameCopy);
			fileName = strtok(NULL, " \t\r\n");
		}
		if (unlikely(fileNames->size() < 1)) {
			regfree(regexp);
			delete regexp;
			delete fileNames;
			continue;
		}

		// push line
		cLine *l = new cLine();
		l->regexp = regexp;
		l->fileNames = fileNames;
		lines.push_back(l);
	}

	free(buffer);
	fclose(f);
}

cLogoConfigFile::~cLogoConfigFile(void) { CLOCK
	for (unsigned i = 0; i < lines.size(); i++) {
		cLine *line = lines[i];
		regfree(line->regexp);
		for (unsigned j = 0; j < line->fileNames->size(); j++)
			delete line->fileNames->at(j);
		delete line->regexp;
		delete line->fileNames;
		delete line;
	}
}

const char *cLogoConfigFile::Error(void) { CLOCK
	if (likely(errnum == 0))
		return NULL;
	else
		return strerror(errnum);
}

int cLogoConfigFile::LogoFileNames(const char *Name, std::vector<const char *> *FileNames) { CLOCK
	if (unlikely(FileNames == NULL))
		return 0;
	if (unlikely(Name == NULL))
		return 0;

	int count = FileNames->size();;
	for (unsigned i = 0; i < lines.size(); i++) {
		cLine *line = lines[i];
		if (likely(regexec(line->regexp, Name, 0, NULL, 0) == 0))
			for (unsigned i = 0; i < line->fileNames->size(); i++)
				FileNames->push_back(line->fileNames->at(i));
	}
	return FileNames->size() - count;
}

/******************************************************************************
 *
 * cBitmapCache
 *
 ******************************************************************************/

class cBitmapCache {
	private:
		unsigned size;
		std::list<const char *> fileNames;
		std::map<const char *,cBitmap *, strCmp> bitmapCache;
		void RemoveBitmap(void);
	public:
		cBitmapCache(unsigned Size = 128);
		~cBitmapCache(void);
		void SetSize(unsigned Size);
		cBitmap *Bitmap(const char *fileName);
};

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

cBitmapCache::cBitmapCache(unsigned Size) { CLOCK
	size = max(Size, 1);
}

cBitmapCache::~cBitmapCache(void) { CLOCK
	while (fileNames.size() > 0)
		RemoveBitmap();
}

void cBitmapCache::RemoveBitmap(void) { CLOCK
	const char *fileName = fileNames.front();
	std::map<const char *,cBitmap *>::iterator i = bitmapCache.find(fileName);
	if (likely(i != bitmapCache.end())) {
		delete i->second; // Bitmap
		bitmapCache.erase(i);
	}
	fileNames.pop_front();
	delete fileName;
}

void cBitmapCache::SetSize(unsigned Size) { CLOCK
	size = max(Size, 1);
	while (fileNames.size() > size)
		RemoveBitmap();
}

cBitmap *cBitmapCache::Bitmap(const char *fileName) { CLOCK
	if (unlikely(fileName == NULL))
		return NULL;

	// try cache
	std::map<const char *,cBitmap *>::iterator i = bitmapCache.find(fileName);
	if (likely(i != bitmapCache.end()))
		return i->second;

	// load bitmap from disk
	cBitmap *bitmap = new cBitmap(1, 1, 1);
	if (unlikely(!bitmap->LoadXpm(fileName))) {
		delete bitmap;
		return NULL;
	}

	// add to cache
	char *fileNameCopy = new char[strlen(fileName) + 1];
	strcpy(fileNameCopy, fileName);

	fileNames.push_back(fileNameCopy);
	bitmapCache[fileNameCopy] = bitmap;

	if (fileNames.size() > size)
		RemoveBitmap();

	isyslog("Neutrino: BitmapCache: %u/%u: Added: %s", fileNames.size(), size, fileName);

	return bitmap;
}

/******************************************************************************
 *
 * cLogoCache
 *
 ******************************************************************************/

class cLogoCache {
	private:
		unsigned size;
		char *logoDirectory;
		char *logoSuffix;
		cBitmapCache *bitmapCache;
		cLogoConfigFile *configFile;
		std::map<const char *,const char *, strCmp> nameCache;
		std::list<const char *> nameList;
		void RemoveLogo(void);
		void RemoveLogo(const char *Name);
		void ClearCache(void);
	public:
		cLogoCache(cBitmapCache *BitmapCache, cLogoConfigFile *ConfigFile, const char *LogoDirectory, const char *LogoSuffix, unsigned Size = 8192);
		~cLogoCache(void);
		void SetBitmapCache(cBitmapCache *BitmapCache);
		void SetConfigFile(cLogoConfigFile *ConfigFile);
		cBitmap *Bitmap(const char *Name);
};

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

cLogoCache::cLogoCache(cBitmapCache *BitmapCache, cLogoConfigFile *ConfigFile, const char *LogoDirectory, const char *LogoSuffix, unsigned Size) { CLOCK
	bitmapCache = BitmapCache;
	configFile = ConfigFile;
	size = Size;

	logoDirectory = new char[strlen(LogoDirectory) + 1];
	strcpy(logoDirectory, LogoDirectory);
	logoSuffix = new char[strlen(LogoSuffix) + 1];
	strcpy(logoSuffix, LogoSuffix);
}

cLogoCache::~cLogoCache(void) { CLOCK
	ClearCache();
	delete logoDirectory;
	delete logoSuffix;
}

void cLogoCache::RemoveLogo(void) { CLOCK
	const char *name = nameList.front();
	std::map<const char *,const char *>::iterator i = nameCache.find(name);
	if (likely(i != nameCache.end())) {
		delete i->second;
		nameCache.erase(i);
	}
	nameList.pop_front();
	delete name;
}

void cLogoCache::RemoveLogo(const char *Name) { CLOCK
	nameList.remove(Name);
	std::map<const char *,const char *>::iterator i = nameCache.find(Name);
	if (likely(i != nameCache.end())) {
		delete i->first;
		delete i->second;
		nameCache.erase(i);
	}
}

void cLogoCache::ClearCache(void) { CLOCK
	while (nameList.size() > 0)
		RemoveLogo();
}

void cLogoCache::SetBitmapCache(cBitmapCache *BitmapCache) { CLOCK
	bitmapCache = BitmapCache;
	ClearCache();
}

void cLogoCache::SetConfigFile(cLogoConfigFile *ConfigFile) { CLOCK
	configFile = ConfigFile;
	ClearCache();
}

cBitmap *cLogoCache::Bitmap(const char *Name) { CLOCK
	if (unlikely(Name == NULL))
		return NULL;

	cBitmap *bitmap = NULL;

	// try cache
	std::map<const char *,const char *>::iterator i = nameCache.find(Name);
	if (i != nameCache.end()) {
		char *fileName;
		asprintf(&fileName, "%s/%s%s", logoDirectory, i->second, logoSuffix);
		bitmap = bitmapCache->Bitmap(fileName);
		free(fileName);
		if (unlikely(bitmap == NULL))
			RemoveLogo(Name);
	}

	if (bitmap == NULL) {
		std::vector<const char *> fileNames;
		configFile->LogoFileNames(Name, &fileNames);
		for (unsigned i = 0; i < fileNames.size(); i++) {
			char *fileName;
			asprintf(&fileName, "%s/%s%s", logoDirectory, fileNames[i], logoSuffix);
			bitmap = bitmapCache->Bitmap(fileName);
			free(fileName);
			if (bitmap == NULL)
				continue;
			// add to cache
			char *nameCopy = new char[strlen(Name) + 1];
			strcpy(nameCopy, Name);
			char *fileNameCopy = new char[strlen(fileNames[i]) + 1];
			strcpy(fileNameCopy, fileNames[i]);
			nameList.push_back(nameCopy);
			nameCache[nameCopy] = fileNameCopy;

			if (nameList.size() > size)
				RemoveLogo();
			break;
		}
	}

	return bitmap;
}

/******************************************************************************
 *
 * cNeutrinoSkinSetup
 *
 ******************************************************************************/

struct cNeutrinoSkinSetup {
	const char *Name;

	int MaxBpg;

	// SetupPage
	int Gap;
	bool ShowChannelLogos;
	bool ShowChannelNumbers;

	// Caches
	cBitmapCache *BitmapCache;
	cLogoConfigFile *ChannelLogoConfigFile;
	cLogoCache *ChannelLogoCache;

	// Fonts
	cFreeTypeLibrary *FontLibrary;
	cFreeTypeFace *Font;
	cFreeTypeFace *FontBold;
	cFreeTypeFace *FontItalic;

	// Channel
	int ChannelShadow;
	int ChannelNumberShadow;

	int SizeChannelNumber;
	int SizeChannelName;
	int SizeChannelTime;
	int SizeChannelEvent;
	int SizeChannelInfo;

	cFreeTypeFace *FontChannelNumber;
	cFreeTypeFace *FontChannelName;
	cFreeTypeFace *FontChannelTime;
	cFreeTypeFace *FontChannelEvent;
	cFreeTypeFace *FontChannelInfo;

	// Menu
	bool MenuItemsMultipleColors;

	int SizeMenuTitle;
	int SizeMenuItem;
	int SizeMenuMessage;
	int SizeMenuButtons;

	cFreeTypeFace *FontMenuTitle;
	cFreeTypeFace *FontMenuItem;
	cFreeTypeFace *FontMenuMessage;
	cFreeTypeFace *FontMenuButtons;

	// Message
	int SizeMessage;

	cFreeTypeFace *FontMessage;

	cNeutrinoSkinSetup() { CLOCK
		Name = NULL;

		// Caches
		BitmapCache = NULL;
		ChannelLogoConfigFile = NULL;
		ChannelLogoCache = NULL;

		// Fonts
		FontLibrary = new cFreeTypeLibrary();
		Font = NULL;
		FontBold = NULL;
		FontItalic = NULL;

		MaxBpg = 3;
		Gap = 4;
		ShowChannelLogos = false;
		ShowChannelNumbers = false;

		// Channel
		ChannelShadow = 6;
		ChannelNumberShadow = 10;

		SizeChannelNumber = 50;
		SizeChannelName = 30;
		SizeChannelTime = 30;
		SizeChannelEvent = 20;
		SizeChannelInfo = 14;

		// Menu
		MenuItemsMultipleColors = false;
		SizeMenuTitle = 30;
		SizeMenuItem = 20;
		SizeMenuMessage = 20;
		SizeMenuButtons = 16; //FIXME

		// Message
		SizeMessage = 30;
	}

	void SetName(const char *Name) { CLOCK
		this->Name = Name;
	}

	void LoadBitmapCache(void) { CLOCK
		if (BitmapCache != NULL)
			delete BitmapCache;

		BitmapCache = new cBitmapCache(128);

		if (ChannelLogoCache != NULL)
			ChannelLogoCache->SetBitmapCache(BitmapCache);
	}

	void LoadChannelLogoConfigFile(const char *FileName) {
		if (ChannelLogoConfigFile != NULL)
			delete ChannelLogoConfigFile;

		ChannelLogoConfigFile = new cLogoConfigFile(FileName);
		if (ChannelLogoConfigFile->Error() != NULL) {
			isyslog("Neutrino: ChannelLogoConfigFile: %s: %s", ChannelLogoConfigFile->Error(), FileName);
			delete ChannelLogoConfigFile;
			ChannelLogoConfigFile = NULL;
		} else {
			isyslog("Neutrino: ChannelLogoConfigFile: Using: %s", FileName);
		}

		if (ChannelLogoCache != NULL)
			ChannelLogoCache->SetConfigFile(ChannelLogoConfigFile);
	}

	void LoadChannelLogoConfigFile(void) { CLOCK
		if (ChannelLogoConfigFile != NULL) {
			delete ChannelLogoConfigFile;
			ChannelLogoConfigFile = NULL;
		}

		std::string confFileName = cPlugin::ConfigDirectory(Name);
		confFileName += "/channellogos.conf";
		LoadChannelLogoConfigFile(confFileName.c_str());
		if (ChannelLogoConfigFile != NULL)
			return;

		confFileName = cPlugin::ConfigDirectory();
		confFileName += "/channellogos.conf";
		LoadChannelLogoConfigFile(confFileName.c_str());
		if (ChannelLogoConfigFile != NULL)
			return;

		isyslog("Neutrino: No Channel Logos available!");
	}

	void LoadChannelLogoCache(void) { CLOCK
		if (unlikely(ChannelLogoConfigFile == NULL))
			return;
		if (unlikely(BitmapCache == NULL))
			return;
		if (ChannelLogoCache != NULL)
			delete ChannelLogoCache;

		std::string logoDirName = cPlugin::ConfigDirectory(Name);
		logoDirName += "/logos";
		ChannelLogoCache = new cLogoCache(BitmapCache, ChannelLogoConfigFile, logoDirName.c_str(), ".xpm");

		// fill cache
//		for (unsigned i = Channels.MaxNumber(); i > 0; i--) {
//			cChannel *channel =Channels.GetByNumber(i);
//			if (channel != NULL)
//				ChannelLogoCache->Bitmap(channel->Name());
//		}
	}

	void LoadFonts(void) { CLOCK
		Font = FontLibrary->LoadFaceFromMemory(micron_ttf, sizeof(micron_ttf));
		FontBold = FontLibrary->LoadFaceFromMemory(micron_bold_ttf, sizeof(micron_bold_ttf));
		FontItalic = FontLibrary->LoadFaceFromMemory(micron_italic_ttf, sizeof(micron_italic_ttf));

		FontChannelNumber = FontBold;
		FontChannelName = FontBold;
		FontChannelTime = FontBold;
		FontChannelEvent = Font;
		FontChannelInfo = Font;

		FontMenuTitle = FontBold;
		FontMenuItem = FontBold;
		FontMenuMessage = Font;
		FontMenuButtons = Font;

		FontMessage = FontBold;
	}

	bool Parse(const char *Name, const char *Value) { CLOCK
		if (!strcasecmp(Name, "ShowChannelLogos")) ShowChannelLogos = atoi(Value);
		else if (!strcasecmp(Name, "ShowChannelNumbers")) ShowChannelNumbers = atoi(Value);
		// TODO
		else
			return false;
		return true;
	}

} SkinSetup;

/******************************************************************************
 *
 * cNeutrinoSkinSetupPage
 *
 ******************************************************************************/

class cNeutrinoSkinSetupPage : public cMenuSetupPage {
	private:
		int showChannelLogos;
		int showChannelNumbers;
	protected:
		virtual void Store(void);
	public:
		cNeutrinoSkinSetupPage(void);
};

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

cNeutrinoSkinSetupPage::cNeutrinoSkinSetupPage(void) { CLOCK
	showChannelLogos = SkinSetup.ShowChannelLogos;
	showChannelNumbers = SkinSetup.ShowChannelNumbers;
	Add(new cMenuEditBoolItem(tr("Show Channel Logos"), &showChannelLogos));
	Add(new cMenuEditBoolItem(tr("Show Channel Numbers"), &showChannelNumbers));
	// TODO
}

void cNeutrinoSkinSetupPage::Store(void) { CLOCK
	SetupStore("ShowChannelLogos", SkinSetup.ShowChannelLogos = showChannelLogos);
	SetupStore("ShowChannelNumbers", SkinSetup.ShowChannelNumbers = showChannelNumbers);
	// TODO
}

/******************************************************************************
 *
 * cNeutrinoSkinDisplay
 *
 ******************************************************************************/

class cNeutrinoSkinDisplay {
	private:
		cOsd *osd;
		int bpg; // bits per gradient
	protected:
		enum eNeutrinoAreaAlign {
			leftAlign,
			rightAlign
		};
		struct tNeutrinoArea {
			int x1, y1, x2, y2;
			int NumColors, NumColorGradients;
			enum eNeutrinoAreaAlign Align;
		};
		void SetOsd(cOsd *Osd);
		int Width(void);
		int Height(void);
		eOsdError SetAreas(const tArea *Areas, int NumAreas);
		eOsdError SetNeutrinoAreas(const tNeutrinoArea *Areas, int NumAreas);
		void DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg = 0, tColor ColorBg = 0, bool ReplacePalette = false, bool Overlay = false);
		void DrawLogo(int x, int y, cBitmap &Bitmap, tColor Color, tColor ColorFg, tColor ColorBg);
		void DrawText(int x, int y, const char * s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width = 0, int Height = 0, int Alignment = taDefault);
		void DrawFreeType(int x, int y, const char *s, tColor FgColor, tColor BgColor, cFreeTypeFace *Face, int Size, int Width = 0, int Height = 0, eTextAlignment Alignment = taDefault);
		void DrawRectangle(int x1, int y1, int x2, int y2, tColor Color);
		void DrawEllipse(int x1, int y1, int x2, int y2, tColor Color, int Quadrants = 0);
		void DrawButtons(int x1, int y1, int x2, int y2, tColor FgColor, tColor BgColor, cFreeTypeFace *Face, int Size, const char *Red, const char *Green, const char *Yellow, const char *Blue);
		void Flush(void);
	public:
		cNeutrinoSkinDisplay(void);
		~cNeutrinoSkinDisplay(void);
};

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

cNeutrinoSkinDisplay::cNeutrinoSkinDisplay(void) { CLOCK
	bpg = 1;
}

cNeutrinoSkinDisplay::~cNeutrinoSkinDisplay(void) { CLOCK
	if (likely(osd != NULL))
		delete osd;
}

void cNeutrinoSkinDisplay::SetOsd(cOsd *Osd) { CLOCK
	osd = Osd;
}

int cNeutrinoSkinDisplay::Width(void) { CLOCK
	if (likely(osd != NULL))
		return osd->Width();
	else
		return 0;
}

int cNeutrinoSkinDisplay::Height(void) { CLOCK
	if (likely(osd != NULL))
		return osd->Height();
	else
		return 0;
}

eOsdError cNeutrinoSkinDisplay::SetAreas(const tArea *Areas, int NumAreas) { CLOCK
	bpg = 1;
	if (likely(osd != NULL))
		return osd->SetAreas(Areas, NumAreas);
	else
		return oeUnknown;
}

eOsdError  cNeutrinoSkinDisplay::SetNeutrinoAreas(const tNeutrinoArea *Areas, int NumAreas) { CLOCK
	if (unlikely(osd == NULL))
		return oeUnknown;

	// calculate overall area
	tArea oneArea[1];
	oneArea[0].x1 = Areas[0].x1;
	oneArea[0].y1 = Areas[0].y1;
	oneArea[0].x2 = Areas[0].x2;
	oneArea[0].y2 = Areas[0].y2;
	for (int i = 1; i < NumAreas; i++) {
		oneArea[0].x1 = min(oneArea[0].x1, Areas[i].x1, Areas[i].x2);
		oneArea[0].y1 = min(oneArea[0].y1, Areas[i].y1, Areas[i].y2);
		oneArea[0].x2 = max(oneArea[0].x2, Areas[i].x1, Areas[i].x2);
		oneArea[0].y2 = max(oneArea[0].y2, Areas[i].y1, Areas[i].y2);
	}

	// calculate bits for gradients
	for (int bits = SkinSetup.MaxBpg; bits > 0; bits--) {
		// try one area
		int numColors = 0;
		for (int i = 0; i < NumAreas; i++)
			numColors += Areas[i].NumColors + Areas[i].NumColorGradients * ((1 << bits) - 1);
		oneArea[0].bpp = bitsPerPixel(numColors);
		//isyslog("Neutrino: COLOR: index=0 bpp=%d numColors=%d", oneArea[0].bpp, numColors);
		eOsdError error = osd->CanHandleAreas(oneArea, 1);
		if (error == oeOk) {
			isyslog("Neutrino: SetNeutrinoAreas: using %d bits per gradient and 1 area", bits);
			bpg = bits;
			return osd->SetAreas(oneArea, 1);
		} else
			isyslog("Neutrino: SetNeutrinoAreas: CanHandleAreas for %d bpg and 1 area returned %d: %s", bits, error, osdError2string(error));

		tArea tmpAreas[NumAreas];
		for (int i = 0; i < NumAreas; i++) {
			tmpAreas[i].x1 = Areas[i].x1;
			tmpAreas[i].y1 = Areas[i].y1;
			tmpAreas[i].x2 = Areas[i].x2;
			tmpAreas[i].y2 = Areas[i].y2;
			int w = Areas[i].x2 - Areas[i].x1 + 1;
			if (w % 4 != 0) {
				// alignment
				if (Areas[i].Align == leftAlign)
					tmpAreas[i].x1 -= w % 4;
				else
					tmpAreas[i].x2 += w % 4;
			}
			int numColors = Areas[i].NumColors + Areas[i].NumColorGradients * ((1 << bits) - 1);
			tmpAreas[i].bpp = bitsPerPixel(numColors);
			//isyslog("Neutrino: COLOR: index=%d bpp=%d numColors=%d", i, tmpAreas[i].bpp, numColors);
		}
		error = osd->CanHandleAreas(tmpAreas, NumAreas);
		if (error == oeOk) {
			isyslog("Neutrino: SetNeutrinoAreas: using %d bits for gradients and %d areas", bits, NumAreas);
			bpg = bits;
			return osd->SetAreas(tmpAreas, NumAreas);
		} else
			isyslog("Neutrino: SetNeutrinoAreas: CanHandleAreas for %d bpg and %d areas returned %d: %s", bits, NumAreas, error, osdError2string(error));
	}
	isyslog("Neutrino: SetNeutrinoAreas failed");
	return oeUnknown;
}

void cNeutrinoSkinDisplay::DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg, tColor ColorBg, bool ReplacePalette, bool Overlay) { CLOCK
	if (likely(osd != NULL))
		osd->DrawBitmap(x, y, Bitmap, ColorFg, ColorBg, ReplacePalette, Overlay);
}

void cNeutrinoSkinDisplay::DrawLogo(int x, int y, cBitmap &Bitmap, tColor Color, tColor ColorFg, tColor ColorBg) { CLOCK
	if (unlikely(osd == NULL))
		return;

	cPalette palette;
	palette.Replace(Bitmap);
/*
	// FIXME there might be invalid colors...
	int i_0 = Bitmap.Index(0xff000000);
	int i_1 = Bitmap.Index(0xff000033);
	int i_2 = Bitmap.Index(0xff000066);
	int i_3 = Bitmap.Index(0xff000099);
	int i_4 = Bitmap.Index(0xff0000cc);
	int i_5 = Bitmap.Index(0xff0000ff);
	int i_6 = Bitmap.Index(0xff3333ff);
	int i_7 = Bitmap.Index(0xff6666ff);
	int i_8 = Bitmap.Index(0xff9999ff);
	int i_9 = Bitmap.Index(0xffccccff);
	int i_a = Bitmap.Index(0xffffffff);
	int i_b = Bitmap.Index(0xffcccccc);
	int i_c = Bitmap.Index(0xff999999);
	int i_d = Bitmap.Index(0xff666666);
	int i_e = Bitmap.Index(0xff333333);
	int i_f = Bitmap.Index(0x00000000);

	Bitmap.SetColor(i_0, ColorBg);
	Bitmap.SetColor(i_1, fadeColor(ColorBg, 4, Color, 1));
	Bitmap.SetColor(i_2, fadeColor(ColorBg, 3, Color, 2));
	Bitmap.SetColor(i_3, fadeColor(ColorBg, 2, Color, 3));
	Bitmap.SetColor(i_4, fadeColor(ColorBg, 1, Color, 4));
	Bitmap.SetColor(i_5, Color);
	Bitmap.SetColor(i_6, fadeColor(Color, 4, ColorFg, 1));
	Bitmap.SetColor(i_7, fadeColor(Color, 3, ColorFg, 2));
	Bitmap.SetColor(i_8, fadeColor(Color, 2, ColorFg, 3));
	Bitmap.SetColor(i_9, fadeColor(Color, 1, ColorFg, 4));
	Bitmap.SetColor(i_a, ColorFg);
	Bitmap.SetColor(i_b, fadeColor(ColorFg, 4, ColorBg, 1));
	Bitmap.SetColor(i_c, fadeColor(ColorFg, 3, ColorBg, 2));
	Bitmap.SetColor(i_d, fadeColor(ColorFg, 2, ColorBg, 3));
	Bitmap.SetColor(i_e, fadeColor(ColorFg, 1, ColorBg, 4));
	Bitmap.SetColor(i_f, clrTransparent);
*/

	int numColors;
	const tColor * colors = Bitmap.Colors(numColors);
	for (int i = 0; i < numColors; i++) {
		// TODO should be calculated without switch
		switch (colors[i]) {
			case 0xff000000: Bitmap.SetColor(i, ColorBg); break;
			case 0xff000033: Bitmap.SetColor(i, fadeColor(ColorBg, 4, Color, 1)); break;
			case 0xff000066: Bitmap.SetColor(i, fadeColor(ColorBg, 3, Color, 2)); break;
			case 0xff000099: Bitmap.SetColor(i, fadeColor(ColorBg, 2, Color, 3)); break;
			case 0xff0000cc: Bitmap.SetColor(i, fadeColor(ColorBg, 1, Color, 4)); break;
			case 0xff0000ff: Bitmap.SetColor(i, Color); break;
			case 0xff3333ff: Bitmap.SetColor(i, fadeColor(Color, 4, ColorFg, 1)); break;
			case 0xff6666ff: Bitmap.SetColor(i, fadeColor(Color, 3, ColorFg, 2)); break;
			case 0xff9999ff: Bitmap.SetColor(i, fadeColor(Color, 2, ColorFg, 3)); break;
			case 0xffccccff: Bitmap.SetColor(i, fadeColor(Color, 1, ColorFg, 4)); break;
			case 0xffffffff: Bitmap.SetColor(i, ColorFg); break;
			case 0xffcccccc: Bitmap.SetColor(i, fadeColor(ColorFg, 4, ColorBg, 1)); break;
			case 0xff999999: Bitmap.SetColor(i, fadeColor(ColorFg, 3, ColorBg, 2)); break;
			case 0xff666666: Bitmap.SetColor(i, fadeColor(ColorFg, 2, ColorBg, 3)); break;
			case 0xff333333: Bitmap.SetColor(i, fadeColor(ColorFg, 1, ColorBg, 4)); break;
			case 0x00000000:
			default:	 Bitmap.SetColor(i, clrTransparent); break;
		}
	}

	DrawBitmap(x, y, Bitmap, 0, 0, false, false);

	Bitmap.Replace(palette);
}

void cNeutrinoSkinDisplay::DrawText(int x, int y, const char * s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width, int Height, int Alignment) { CLOCK
	if (likely(osd != NULL))
		osd->DrawText(x, y, s, ColorFg, ColorBg, Font, Width, Height, Alignment);
}

void cNeutrinoSkinDisplay::DrawFreeType(int x, int y, const char *s, tColor FgColor, tColor BgColor, cFreeTypeFace *Face, int Size, int Width, int Height, eTextAlignment Alignment) { CLOCK
	if (unlikely(osd == NULL))
		return;
	if (unlikely(Face == NULL))
		return;
	cBitmap *bitmap = Face->Bitmap(s, FgColor, BgColor, Size, bpg, Width, Height, Alignment);
	osd->DrawBitmap(x, y, *bitmap);
	delete bitmap;
}

void cNeutrinoSkinDisplay::DrawRectangle(int x1, int y1, int x2, int y2, tColor Color) { CLOCK
	if (likely(osd != NULL))
		osd->DrawRectangle(x1, y1, x2, y2, Color);
}

void cNeutrinoSkinDisplay::DrawEllipse(int x1, int y1, int x2, int y2, tColor Color, int Quadrants) { CLOCK
	if (likely(osd != NULL))
		osd->DrawEllipse(x1, y1, x2, y2, Color, Quadrants);
}

void cNeutrinoSkinDisplay::DrawButtons(int x1, int y1, int x2, int y2, tColor FgColor, tColor BgColor, cFreeTypeFace *Face, int Size, const char *Red, const char *Green, const char *Yellow, const char *Blue) { CLOCK
	if (unlikely(Face == 0))
		return;
	if (unlikely(x2 <= x1))
		return;
	if (unlikely(y2 <= y1))
		return;

	int width = (x2 - x1) / 4;
	int height = y2 - y1 + 1;

	int textH = min(Face->Height(Size), height - 2 * SkinSetup.Gap);
	int textW = width - 2 * SkinSetup.Gap - textH;

	int ellipseH = textH * 2 / 3;
	int ellipse1 = SkinSetup.Gap + (textH - ellipseH) / 2;
	int ellipse2 = ellipse1 + ellipseH;

	DrawRectangle(x1, y1, x2, y2, BgColor);

	if (likely(Red != NULL)) {
		DrawEllipse(x1 + ellipse1, y1 + ellipse1, x1 + ellipse2, y1 + ellipse2, Theme.Color(clrButtonRed));
		DrawFreeType(x1 + SkinSetup.Gap + textH, y1 + SkinSetup.Gap, Red, FgColor, BgColor, Face, Size, textW, textH);
	}
	if (likely(Green != NULL)) {
		DrawEllipse(x1 + width + ellipse1, y1 + ellipse1, x1 + width + ellipse2, y1 + ellipse2, Theme.Color(clrButtonGreen));
		DrawFreeType(x1 + SkinSetup.Gap + textH + width, y1 + SkinSetup.Gap, Green, FgColor, BgColor, Face, Size, textW, textH);
	}
	if (likely(Yellow != NULL)) {
		DrawEllipse(x1 + 2 * width + ellipse1, y1 + ellipse1, x1 + 2 * width + ellipse2, y1 + ellipse2, Theme.Color(clrButtonYellow));
		DrawFreeType(x1 + SkinSetup.Gap + textH + width * 2, y1 + SkinSetup.Gap, Yellow, FgColor, BgColor, Face, Size, textW, textH);
	}
	if (likely(Blue != NULL)) {
		DrawEllipse(x1 + 3 * width + ellipse1, y1 + ellipse1, x1 + 3 * width + ellipse2, y1 + ellipse2, Theme.Color(clrButtonBlue));
		DrawFreeType(x1 + SkinSetup.Gap + textH + width * 3, y1 + SkinSetup.Gap, Blue, FgColor, BgColor, Face, Size, textW, textH);
	}
}

void cNeutrinoSkinDisplay::Flush(void) { CLOCK
	if (likely(osd != NULL))
		osd->Flush();
}

/******************************************************************************
 *
 * cNeutrinoSkinDisplayChannel
 *
 ******************************************************************************/

class cNeutrinoSkinDisplayChannel : public cSkinDisplayChannel, public cNeutrinoSkinDisplay {
	private:
		int numberX0, numberY0, numberX1, numberY1;
		int nameX0, nameY0, nameX1, nameY1;
		int timeX0, timeY0, timeX1, timeY1;
		int eventPrX0, eventPrY0, eventPrX1, eventPrY1;
		int eventFoX0, eventFoY0, eventFoX1, eventFoY1;
		int infoX0, infoY0, infoX1, infoY1;
		int timebarX0, timebarY0, timebarX1, timebarY1;

		bool message; // TODO

		// for events
		time_t lastFlush;
		const cEvent *present;
		const cEvent *following;

		static cBitmap bmTeletext, bmRadio, bmAudio, bmDolbyDigital, bmEncrypted, bmRecording;
	public:
		cNeutrinoSkinDisplayChannel(bool WithInfo);
		virtual ~cNeutrinoSkinDisplayChannel(void);
		virtual void SetChannel(const cChannel *Channel, int Number);
		virtual void SetEvents(const cEvent *Present, const cEvent *Following);
		virtual void SetMessage(eMessageType Type, const char *Text);
		virtual void Flush(void);
};

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

cBitmap cNeutrinoSkinDisplayChannel::bmTeletext(teletext_xpm);
cBitmap cNeutrinoSkinDisplayChannel::bmRadio(radio_xpm);
cBitmap cNeutrinoSkinDisplayChannel::bmAudio(audio_xpm);
cBitmap cNeutrinoSkinDisplayChannel::bmDolbyDigital(dolbydigital_xpm);
cBitmap cNeutrinoSkinDisplayChannel::bmEncrypted(encrypted_xpm);
cBitmap cNeutrinoSkinDisplayChannel::bmRecording(recording_xpm);

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

/* Areas:
                                                     4
 +-----------+                                    +-----+
 |           |                                    +-----+
 |    0      +----------------------------------------------+
 |           |                   1                          +
 +--+--------+--------------------------------------------+-+
    |                                                     | |
    |                         2                           |6|
    |                                                     | |
    +-----------------------------------------------------+ |
    |                         3                           | |
    ++----------------------------------------------------+-+
     +------------------------------------------------------+
                              5
*/

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

cNeutrinoSkinDisplayChannel::cNeutrinoSkinDisplayChannel(bool WithInfo) { CLOCK
	lastFlush = 0;
	present = NULL;
	following = NULL;
	message = false;

	// for alignment
	int width = Setup.OSDWidth - 4 - 4;

	numberX0 = 4; // for alignment
	numberY0 = 0;
	numberX1 = max(SkinSetup.FontChannelNumber->Width(SkinSetup.SizeChannelNumber, "0000") + SkinSetup.Gap * 2, 128) - 1; // FIXME 128 = channel logo width
	numberY1 = max(SkinSetup.FontChannelNumber->Height(SkinSetup.SizeChannelNumber) + SkinSetup.Gap * 2, (numberX1 - numberX0) * 3 / 4) - 1;

	timeX1 = width - 1 - SkinSetup.ChannelShadow;
	timeX0 = timeX1 - SkinSetup.FontChannelTime->Width(SkinSetup.SizeChannelTime, "00:00") - SkinSetup.Gap * 2;
	timeY1 = numberY1 + SkinSetup.ChannelNumberShadow;
	timeY0 = timeY1 - max(SkinSetup.FontChannelTime->Height(SkinSetup.SizeChannelTime), SkinSetup.FontChannelName->Height(SkinSetup.SizeChannelName)) - SkinSetup.Gap * 2;

	nameX0 = numberX1 + SkinSetup.ChannelNumberShadow + 1;
	nameX1 = timeX0 - 1;
	nameY0 = timeY0;
	nameY1 = timeY1;

	eventPrY0 = nameY1 + 1;
	eventPrY1 = eventPrY0 + SkinSetup.FontChannelEvent->Height(SkinSetup.SizeChannelEvent) + SkinSetup.Gap * 2 - 1;
	eventPrX0 = nameY0;
	eventPrX1 = timeX1;

	eventFoX0 = eventPrX0;
	eventFoX1 = eventPrX1;
	eventFoY0 = eventPrY1 + 1;
	eventFoY1 = eventFoY0 + SkinSetup.FontChannelEvent->Height(SkinSetup.SizeChannelEvent) + SkinSetup.Gap * 2 - 1;

	infoX0 = eventPrX0;
	infoX1 = eventPrX1;
	infoY0 = eventFoY1 + 1;
	infoY1 = infoY0 + SkinSetup.FontBold->Height(SkinSetup.SizeChannelInfo) + SkinSetup.Gap * 2 - 1;

	timebarX1 = timeX1 - SkinSetup.Gap * 2 - 1;
	timebarX0 = timebarX1 - 100 - 4 + 1;
	timebarY1 = timeY0 - SkinSetup.Gap * 2 - 1;
	timebarY0 = timebarY1 - 20 - 4 + 1;

	SetOsd(cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop + (Setup.ChannelInfoPos ? 0 : Setup.OSDHeight - infoY1 + SkinSetup.ChannelShadow - 1)));

/*
	tNeutrinoArea Areas[] = {
		{ numberX0, numberY0, numberX1 + SkinSetup.ChannelNumberShadow, numberY1 + SkinSetup.ChannelNumberShadow, 3, 1, leftAlign },
		{ nameX0, nameY0, timeX1 + SkinSetup.ChannelShadow, timeY1, 2, 1, rightAlign },
		{ eventPrX0, eventPrY0, eventFoX1, eventFoY1, 1, 1, leftAlign },
		{ infoX0, infoY0, infoX1, infoY1, 7, 1, leftAlign },
		{ timebarX0, timebarY0, timebarX1, timebarY1, 4, 0, leftAlign },
		{ infoX0 + SkinSetup.ChannelShadow, infoY1 + 1, infoX1 + SkinSetup.ChannelShadow, infoY1 + SkinSetup.ChannelShadow, 1, 0, leftAlign },
		{ infoX1 + 1, timeY1 + 1, infoX1 + SkinSetup.ChannelShadow, infoY1, 1, 0, rightAlign }
	};
*/
/*
	tNeutrinoArea Areas[] = {
		{ numberX0, numberY0, numberX1 + SkinSetup.ChannelNumberShadow, numberY1 + SkinSetup.ChannelNumberShadow, 3, 1, leftAlign },
		{ nameX0, nameY0, timeX1 + SkinSetup.ChannelShadow, timeY1, 3, 1, rightAlign },
		{ eventPrX0, eventPrY0, eventFoX1 + SkinSetup.ChannelShadow, eventFoY1, 2, 1, leftAlign },
		{ infoX0, infoY0, infoX1 + SkinSetup.ChannelShadow, infoY1 + SkinSetup.ChannelShadow, 9, 1, leftAlign },
		{ timebarX0, timebarY0, timebarX1, timebarY1, 4, 0, leftAlign },
	};
*/
	tNeutrinoArea Areas[] = {
		{ numberX0, numberY0, timeX1 + SkinSetup.ChannelShadow, timeY1, 19, 1, leftAlign },
		{ eventPrX0, eventPrY0, eventFoX1 + SkinSetup.ChannelShadow, eventFoY1, 2, 1, leftAlign },
		{ infoX0, infoY0, infoX1 + SkinSetup.ChannelShadow, infoY1 + SkinSetup.ChannelShadow, 9, 1, leftAlign },
	};

/*

	tNeutrinoArea Areas[] = {
		{ numberX0, numberY0, timeX1 + SkinSetup.ChannelShadow, eventFoY1, 7, 1, leftAlign },
		{ infoX0, infoY0, infoX1 + SkinSetup.ChannelShadow, infoY1 + SkinSetup.ChannelShadow, 9, 1, leftAlign },
	};
*/
	SetNeutrinoAreas(Areas, sizeof(Areas) / sizeof(tNeutrinoArea));

	DrawRectangle(numberX1 + SkinSetup.ChannelNumberShadow + 1, numberY0, timeX1 + SkinSetup.ChannelShadow, timeY0 - 1, clrTransparent);
	DrawRectangle(numberX0, numberY1 + SkinSetup.ChannelNumberShadow + 1, infoX0 - 1, infoY1 + SkinSetup.ChannelShadow, clrTransparent);
}

cNeutrinoSkinDisplayChannel::~cNeutrinoSkinDisplayChannel() { CLOCK
}

void cNeutrinoSkinDisplayChannel::SetChannel(const cChannel *Channel, int Number) { CLOCK
	char strNumber[256];
	char strName[256];
	char strTime[256];
	time_t curTime;
	struct tm *locTime;
	cBitmap *logo = NULL;

	if (likely(Channel != NULL)) {
		if (unlikely(Channel->GroupSep())) {
			strNumber[0] = '\0';
			snprintf(strName, sizeof(strName), "%s", Channel->Name());
		} else {
			snprintf(strNumber, sizeof(strNumber), "%d%s", Channel->Number(), Number ? "-" : "");
			if (SkinSetup.ShowChannelNumbers)
				snprintf(strName, sizeof(strName), "%d %s", Channel->Number(), Channel->Name());
			else
				snprintf(strName, sizeof(strName), "%s", Channel->Name());
			if (SkinSetup.ShowChannelLogos && SkinSetup.ChannelLogoCache != NULL)
				logo = SkinSetup.ChannelLogoCache->Bitmap(Channel->Name());
		}
	} else if (likely(Number)) {
		snprintf(strNumber, sizeof(strNumber), "%d-", Number);
		strName[0] = '\0';
	} else {
		strNumber[0] = '\0';
		snprintf(strName, sizeof(strName), "%s", tr("*** Invalid Channel ***"));
	}

	curTime = time(0);
	locTime = localtime(&curTime);
	strftime(strTime, sizeof(strTime), "%H:%M", locTime);

	// Transparent
	DrawRectangle(numberX1 + SkinSetup.ChannelNumberShadow + 1, numberY0, timeX1 + SkinSetup.ChannelShadow, timeY0 - 1, clrTransparent);
	DrawRectangle(numberX0, numberY1 + SkinSetup.ChannelNumberShadow + 1, infoX0 - 1, infoY1 + SkinSetup.ChannelShadow, clrTransparent);

	// 0 - Number
	DrawRectangle(numberX0, numberY0, numberX1, numberY1, Theme.Color(clrChannelNumberBg));
	if (logo)
		//DrawLogo(numberX0, numberY0, *logo, Theme.Color(clrChannelNumberBg), Theme.Color(clrChannelNumberLogo2), Theme.Color(clrChannelNumberLogo1));
		DrawLogo(numberX0, numberY0, *logo, Theme.Color(clrChannelNumberBg), Theme.Color(clrChannelNumberLogo), Theme.Color(clrChannelNumberShadow));
	else
		DrawFreeType(numberX0 + SkinSetup.Gap, numberY0 + SkinSetup.Gap, strNumber, Theme.Color(clrChannelNumberFg), Theme.Color(clrChannelNumberBg), SkinSetup.FontChannelNumber, SkinSetup.SizeChannelNumber, numberX1 - numberX0 + 1 - SkinSetup.Gap * 2, numberY1 - numberY0 + 1 - SkinSetup.Gap * 2, taCenter);

	// 0 - NumberShadows
	DrawRectangle(numberX0, numberY1 + 1, numberX0 + SkinSetup.ChannelNumberShadow - 1, numberY1 + SkinSetup.ChannelNumberShadow - 1, clrTransparent);
	DrawRectangle(numberX0 + SkinSetup.ChannelNumberShadow, numberY1 + 1, numberX1 + SkinSetup.ChannelNumberShadow,  numberY1 + SkinSetup.ChannelNumberShadow, Theme.Color(clrChannelNumberShadow));
	DrawRectangle(numberX1 + 1, numberY0, numberX1 + SkinSetup.ChannelNumberShadow, numberY0 + SkinSetup.ChannelNumberShadow - 1, clrTransparent);
	DrawRectangle(numberX1 + 1, numberY0 + SkinSetup.ChannelNumberShadow, numberX1 + SkinSetup.ChannelNumberShadow, numberY1, Theme.Color(clrChannelNumberShadow));

	// 1 - Name
	DrawRectangle(nameX0, nameY0, nameX1, nameY1, Theme.Color(clrChannelNameBg));
	DrawFreeType(nameX0 + SkinSetup.Gap, nameY0 + SkinSetup.Gap, strName, Theme.Color(clrChannelNameFg), Theme.Color(clrChannelNameBg), SkinSetup.FontChannelNumber, SkinSetup.SizeChannelName, nameX1 - nameX0 - SkinSetup.Gap * 2 - 1);

	// 1 - Time
	DrawRectangle(timeX0, timeY0, timeX1, timeY1, Theme.Color(clrChannelNameBg));
	DrawFreeType(timeX0 + SkinSetup.Gap, timeY0 + SkinSetup.Gap, strTime, Theme.Color(clrChannelNameFg), Theme.Color(clrChannelNameBg), SkinSetup.FontChannelTime, SkinSetup.SizeChannelTime);

	// 2 - Events
	DrawRectangle(eventPrX0, eventPrY0, eventPrX1, eventPrY1, Theme.Color(clrChannelEventBg));
	DrawRectangle(eventFoX0, eventFoY0, eventFoX1, eventFoY1, Theme.Color(clrChannelEventBg));

	// 3 - Status
	// TODO: 3bpp bitmaps
	// TODO: colors
	DrawRectangle(infoX0, infoY0, infoX1, infoY1, Theme.Color(clrChannelInfoBg));
	int x = infoX1;
	int h = infoY1 - infoY0 + 1;
	x -= bmTeletext.Width() + SkinSetup.Gap;
	DrawBitmap(x, infoY0 + (h - bmTeletext.Height()) / 2, bmTeletext, Theme.Color(Channel->Tpid() ?  clrChannelSymbolOn : clrChannelSymbolOff), Theme.Color(clrChannelInfoBg));
	x -= bmAudio.Width() + SkinSetup.Gap;
	DrawBitmap(x, infoY0 + (h - bmAudio.Height()) / 2, bmAudio, Theme.Color(Channel->Apid(1) ? clrChannelSymbolOn : clrChannelSymbolOff), Theme.Color(clrChannelInfoBg));
	x -= bmDolbyDigital.Width() + SkinSetup.Gap;
	DrawBitmap(x, infoY0 + (h - bmDolbyDigital.Height()) / 2, bmDolbyDigital, Theme.Color(Channel->Dpid(0) ? clrChannelSymbolOn : clrChannelSymbolOff), Theme.Color(clrChannelInfoBg));
	x -= bmEncrypted.Width() + SkinSetup.Gap;
	DrawBitmap(x, infoY0 + (h - bmEncrypted.Height()) / 2, bmEncrypted, Theme.Color(Channel->Ca() ? clrChannelSymbolOn : clrChannelSymbolOff), Theme.Color(clrChannelInfoBg));
	x -= bmRecording.Width() + SkinSetup.Gap;
	DrawBitmap(x, infoY0 + (h - bmRecording.Height()) / 2, bmRecording, Theme.Color(cRecordControls::Active() ? clrChannelSymbolOn : clrChannelSymbolOff), Theme.Color(clrChannelInfoBg));
	x -= SkinSetup.Gap;
	// TODO: determine strings for buttons
	DrawButtons(infoX0, infoY0, x, infoY1, Theme.Color(clrChannelInfoFg), Theme.Color(clrChannelInfoBg), SkinSetup.FontChannelInfo, SkinSetup.SizeChannelInfo, keyMacro2string(kRed), keyMacro2string(kGreen), keyMacro2string(kYellow), keyMacro2string(kBlue));

	// 5&6 - Shadows
	DrawRectangle(infoX0, infoY1 + 1, infoX0 + SkinSetup.ChannelShadow - 1, infoY1 + SkinSetup.ChannelShadow, clrTransparent);
	DrawRectangle(infoX0 + SkinSetup.ChannelShadow, infoY1 + 1, infoX1 + SkinSetup.ChannelShadow, infoY1 + SkinSetup.ChannelShadow, Theme.Color(clrChannelShadow));
	DrawRectangle(infoX1 + 1, timeY0, infoX1 + SkinSetup.ChannelShadow, infoX1, clrTransparent);
	DrawRectangle(infoX1 + 1, timeY0 + SkinSetup.ChannelShadow, infoX1 + SkinSetup.ChannelShadow, infoY1 + SkinSetup.ChannelShadow, Theme.Color(clrChannelShadow));
}

void cNeutrinoSkinDisplayChannel::SetEvents(const cEvent *Present, const cEvent *Following) { CLOCK
	present = Present;
	following = Following;

	// 4 - clear Timebar
	DrawRectangle(timebarX0, timebarY0, timebarX1, timebarY1, clrTransparent);

	// Present
	DrawRectangle(eventPrX0, eventPrY0, eventPrX1, eventPrY1, Theme.Color(clrChannelEventBg));
	if (likely(Present != NULL)) {
		int duration = Present->Duration();
		time_t start = Present->StartTime();
		time_t now = time(NULL);
		char *buffer;
		if (likely(now > start && duration > 0)) {
			int seen = (now - start) / 60;
			int remain = duration / 60 - seen;
			asprintf(&buffer, "%d / %d min", seen, remain);

			// Timebar
			int x0 = timebarX0 + 2;
			int x1 = timebarX1 - 2;
			int y0 = timebarY0 + 2;
			int y1 = timebarY1 - 2;
			int percent = (now - start) * 100 / duration;
			if (unlikely(percent < 0))
				percent = 0;
			if (unlikely(percent > 100))
				percent = 100;
			DrawRectangle(timebarX0, timebarY0, timebarX1, timebarY1, Theme.Color(clrChannelTimebarShadow));
			DrawRectangle(x0, y0, x0 + percent - 1, y1, Theme.Color(clrChannelTimebarFg));
			DrawRectangle(x0 + percent, y0, x1, y1, Theme.Color(clrChannelTimebarBg));
		} else {
			asprintf(&buffer, "%d min", duration / 60);
		}
		int titleX0 = eventPrX0 + SkinSetup.FontChannelEvent->Width(SkinSetup.SizeChannelEvent, "00:00") + SkinSetup.Gap * 2;
		int titleX1 = eventPrX1 - SkinSetup.FontChannelEvent->Width(SkinSetup.SizeChannelEvent, buffer) - SkinSetup.Gap * 2;
		int titleW = titleX1 - titleX0 - SkinSetup.Gap * 2;
		DrawFreeType(eventPrX0 + SkinSetup.Gap, eventPrY0 + SkinSetup.Gap, Present->GetTimeString(), Theme.Color(clrChannelEventFg), Theme.Color(clrChannelEventBg), SkinSetup.FontChannelEvent, SkinSetup.SizeChannelEvent);
		DrawFreeType(titleX0 + SkinSetup.Gap, eventPrY0 + SkinSetup.Gap, Present->Title(), Theme.Color(clrChannelEventFg), Theme.Color(clrChannelEventBg), SkinSetup.FontChannelEvent, SkinSetup.SizeChannelEvent, titleW);
		DrawFreeType(titleX1 + 1 + SkinSetup.Gap, eventPrY0 + SkinSetup.Gap, buffer, Theme.Color(clrChannelEventFg), Theme.Color(clrChannelEventBg), SkinSetup.FontChannelEvent, SkinSetup.SizeChannelEvent);
		free(buffer);
	}

	// Following
	DrawRectangle(eventFoX0, eventFoY0, eventFoX1, eventFoY1, Theme.Color(clrChannelEventBg));
	if (likely(Following != NULL)) {
		char *buffer;
		asprintf(&buffer, "%d min", Following->Duration() / 60);
		int titleX0 = eventFoX0 + SkinSetup.FontChannelEvent->Width(SkinSetup.SizeChannelEvent, "00:00") + SkinSetup.Gap * 2;
		int titleX1 = eventFoX1 - SkinSetup.FontChannelEvent->Width(SkinSetup.SizeChannelEvent, buffer) - SkinSetup.Gap * 2;
		int titleW = titleX1 - titleX0 - SkinSetup.Gap * 2;

		DrawFreeType(eventFoX0 + SkinSetup.Gap, eventFoY0 + SkinSetup.Gap, Following->GetTimeString(), Theme.Color(clrChannelEventFg), Theme.Color(clrChannelEventBg), SkinSetup.FontChannelEvent, SkinSetup.SizeChannelEvent);
		DrawFreeType(titleX0 + SkinSetup.Gap, eventFoY0 + SkinSetup.Gap, Following->Title(), Theme.Color(clrChannelEventFg), Theme.Color(clrChannelEventBg), SkinSetup.FontChannelEvent, SkinSetup.SizeChannelEvent, titleW);
		DrawFreeType(titleX1 + 1 + SkinSetup.Gap, eventFoY0 + SkinSetup.Gap, buffer, Theme.Color(clrChannelEventFg), Theme.Color(clrChannelEventBg), SkinSetup.FontChannelEvent, SkinSetup.SizeChannelEvent);
		free(buffer);
	}
}

void cNeutrinoSkinDisplayChannel::SetMessage(eMessageType Type, const char *Text) { CLOCK
	// TODO
	if (Text) {
		//osd->SaveRegion(0, 0, osd->Width() - 1, lineHeight - 1);
		message = true;
	} else {
		//RestoreRegion();
		message = false;
	}
}

void cNeutrinoSkinDisplayChannel::Flush(void) { CLOCK
	time_t now = time(NULL);
	if (likely(now != lastFlush))
		SetEvents(present, following);
	lastFlush = now;

	time_t curTime;
	curTime = time(0);

	struct tm *locTime;
	locTime = localtime(&curTime);

	char strTime[256];
	strftime(strTime, sizeof(strTime), "%H:%M", locTime);

	DrawRectangle(timeX0, timeY0, timeX1, timeY1, Theme.Color(clrChannelNameBg));
	DrawFreeType(timeX0 + SkinSetup.Gap, timeY0 + SkinSetup.Gap, strTime, Theme.Color(clrChannelNameFg), Theme.Color(clrChannelNameBg), SkinSetup.FontBold, SkinSetup.SizeChannelTime);

	if (locTime->tm_sec % 2) {
		// Note: "%H.%M" doesn't work: Width(".") != Width(":");
		int delX0 = timeX0 + SkinSetup.Gap + SkinSetup.FontBold->Width(SkinSetup.SizeChannelTime, "00");
		int delX1 = delX0 + SkinSetup.FontBold->Width(SkinSetup.SizeChannelTime, ":") - 1;
		int delY0 = timeY0 + SkinSetup.Gap;
		int delY1 = delY0 + SkinSetup.FontBold->Height(SkinSetup.SizeChannelTime) / 2;
		DrawRectangle(delX0, delY0, delX1, delY1, Theme.Color(clrChannelNameBg));
	}

	cNeutrinoSkinDisplay::Flush();
}

/******************************************************************************
 *
 * cNeutrinoSkinDisplayMenu
 *
 ******************************************************************************/

class cNeutrinoSkinDisplayMenu : public cSkinDisplayMenu, public cNeutrinoSkinDisplay {
	private:
//		cOsd *osd;
		int x0, x1;
		int y0, y1, y2, y3, y4, y5;
		int lineHeight;
		void SetScrollbar(void);
	public:
		cNeutrinoSkinDisplayMenu(void);
		virtual ~cNeutrinoSkinDisplayMenu();
		virtual void Scroll(bool Up, bool Page);
		virtual int MaxItems(void);
		virtual void Clear(void);
		virtual void SetTitle(const char *Title);
		virtual void SetButtons(const char *Red, const char *Green = NULL, const char *Yellow = NULL, const char *Blue = NULL);
		virtual void SetMessage(eMessageType Type, const char *Text);
		virtual void SetItem(const char *Text, int Index, bool Current, bool Selectable);
		virtual void SetEvent(const cEvent *Event);
		virtual void SetRecording(const cRecording *Recording);
		virtual void SetText(const char *Text, bool FixedFont);
		virtual void Flush(void);
};

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

cNeutrinoSkinDisplayMenu::cNeutrinoSkinDisplayMenu(void) { CLOCK
	lineHeight = SkinSetup.FontMenuItem->Height(SkinSetup.SizeMenuItem);
	x0 = 0;
	x1 = Setup.OSDWidth;
	y0 = 0;
	y1 = SkinSetup.FontMenuTitle->Height(SkinSetup.SizeMenuTitle);;
	y2 = y1 + lineHeight;
	y5 = Setup.OSDHeight;
	y4 = y5 - SkinSetup.FontMenuButtons->Height(SkinSetup.SizeMenuButtons) - SkinSetup.Gap * 2;
	y3 = y4 - SkinSetup.FontMenuMessage->Height(SkinSetup.SizeMenuMessage);

	SetOsd(cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop));

	tNeutrinoArea Areas[] = {
		{ x0, y0, x1 - 1, y1 - 1, 1, 1, leftAlign },
		{ x0, y1, x1 - 1, y3 - 1, 1, 1, leftAlign },
		{ x0, y3, x1 - 1, y5 - 1, 5, 1, leftAlign }
	};

	SetNeutrinoAreas(Areas, sizeof(Areas) / sizeof(tNeutrinoArea));
}

cNeutrinoSkinDisplayMenu::~cNeutrinoSkinDisplayMenu() { CLOCK
}

void cNeutrinoSkinDisplayMenu::SetScrollbar(void) { CLOCK
  if (textScroller.CanScroll()) {
     int yt = textScroller.Top();
     int yb = yt + textScroller.Height();
     int st = yt;
     int sb = yb;
     int tt = st + (sb - st) * textScroller.Offset() / textScroller.Total();
     int tb = tt + (sb - st) * textScroller.Shown() / textScroller.Total();
     int xl = x1 - ScrollWidth;
     DrawRectangle(xl, st, x1 - 1, sb, Theme.Color(clrMenuScrollbarTotal));
     DrawRectangle(xl, tt, x1 - 1, tb, Theme.Color(clrMenuScrollbarShown));
     }
}

void cNeutrinoSkinDisplayMenu::Scroll(bool Up, bool Page) { CLOCK
  cSkinDisplayMenu::Scroll(Up, Page);
  SetScrollbar();
}

int cNeutrinoSkinDisplayMenu::MaxItems(void) { CLOCK
	return (y3 - y2) / lineHeight;
}

void cNeutrinoSkinDisplayMenu::Clear(void) { CLOCK
  textScroller.Reset();
  DrawRectangle(x0, y1, x1 - 1, y4 - 1, Theme.Color(clrBackground));
}

void cNeutrinoSkinDisplayMenu::SetTitle(const char *Title) { CLOCK
	DrawFreeType(x0, y0, Title, Theme.Color(clrMenuTitleFg), Theme.Color(clrMenuTitleBg), SkinSetup.FontMenuTitle, SkinSetup.SizeMenuTitle, x1 - x0 - 1);
}

void cNeutrinoSkinDisplayMenu::SetButtons(const char *Red, const char *Green, const char *Yellow, const char *Blue) { CLOCK
	DrawButtons(x0, y4, x1, y5 - 1, Theme.Color(clrMenuButtonsFg), Theme.Color(clrMenuButtonsBg), SkinSetup.FontMenuButtons, SkinSetup.SizeMenuButtons, Red, Green, Yellow, Blue);
}

void cNeutrinoSkinDisplayMenu::SetMessage(eMessageType Type, const char *Text) { CLOCK
  const cFont *font = cFont::GetFont(fontOsd);
  if (Text)
     DrawText(x0, y3, Text, Theme.Color(clrMessageStatusFg + 2 * Type), Theme.Color(clrMessageStatusBg + 2 * Type), font, x1 - x0, 0, taCenter);
  else
     DrawRectangle(x0, y3, x1 - 1, y4 - 1, Theme.Color(clrBackground));
}

void cNeutrinoSkinDisplayMenu::SetItem(const char *Text, int Index, bool Current, bool Selectable) { CLOCK
  int y = y2 + Index * lineHeight;
  tColor ColorFg, ColorBg;
  if (Current) {
     ColorFg = Theme.Color(clrMenuItemCurrentFg);
     ColorBg = Theme.Color(clrMenuItemCurrentBg);
     }
  else {
//     ColorFg = Theme.Color(Selectable ? clrMenuItemSelectable : clrMenuItemNonSelectable);
	ColorFg = Theme.Color(clrMenuItemFg);
     ColorBg = Theme.Color(clrBackground);
     }
  const cFont *font = cFont::GetFont(fontOsd);
  for (int i = 0; i < MaxTabs; i++) {
      const char *s = GetTabbedText(Text, i);
      if (s) {
         int xt = x0 + Tab(i);
         DrawText(xt, y, s, ColorFg, ColorBg, font, x1 - xt);
         }
      if (!Tab(i + 1))
         break;
      }
  SetEditableWidth(x1 - x0 - Tab(1));
}

void cNeutrinoSkinDisplayMenu::SetEvent(const cEvent *Event) { CLOCK
  if (!Event)
     return;
  const cFont *font = cFont::GetFont(fontOsd);
//  int xl = x0 + 10;
  int y = y2;
  cTextScroller ts;
  char t[32];
  snprintf(t, sizeof(t), "%s  %s - %s", *Event->GetDateString(), *Event->GetTimeString(), *Event->GetEndTimeString());
//  ts.Set(osd, xl, y, x1 - xl, y3 - y, t, font, Theme.Color(clrMenuEventTime), Theme.Color(clrBackground));
  if (Event->Vps() && Event->Vps() != Event->StartTime()) {
     char *buffer;
     asprintf(&buffer, " VPS: %s", *Event->GetVpsString());
     const cFont *font = cFont::GetFont(fontSml);
     DrawText(x1 - font->Width(buffer), y, buffer, Theme.Color(clrMenuEventVpsFg), Theme.Color(clrMenuEventVpsBg), font);
     free(buffer);
     }
  y += ts.Height();
  y += font->Height();
//  ts.Set(osd, xl, y, x1 - xl, y3 - y, Event->Title(), font, Theme.Color(clrMenuEventTitle), Theme.Color(clrBackground));
  y += ts.Height();
  if (!isempty(Event->ShortText())) {
//     const cFont *font = cFont::GetFont(fontSml);
//     ts.Set(osd, xl, y, x1 - xl, y3 - y, Event->ShortText(), font, Theme.Color(clrMenuEventShortText), Theme.Color(clrBackground));
     y += ts.Height();
     }
  y += font->Height();
  if (!isempty(Event->Description())) {
//     textScroller.Set(osd, xl, y, x1 - xl - 2 * ScrollWidth, y3 - y, Event->Description(), font, Theme.Color(clrMenuEventDescription), Theme.Color(clrBackground));
     SetScrollbar();
     }
}

void cNeutrinoSkinDisplayMenu::SetRecording(const cRecording *Recording) { CLOCK
#if VDRVERSNUM >= 10325
  if (!Recording)
     return;
  const cRecordingInfo *Info = Recording->Info();
  const cFont *font = cFont::GetFont(fontOsd);
//  int xl = x0 + 10;
  int y = y2;
  cTextScroller ts;
  char t[32];
  snprintf(t, sizeof(t), "%s  %s", *DateString(Recording->start), *TimeString(Recording->start));
//  ts.Set(osd, xl, y, x1 - xl, y3 - y, t, font, Theme.Color(clrMenuEventTime), Theme.Color(clrBackground));
  y += ts.Height();
  y += font->Height();
  const char *Title = Info->Title();
  if (isempty(Title))
     Title = Recording->Name();
//  ts.Set(osd, xl, y, x1 - xl, y3 - y, Title, font, Theme.Color(clrMenuEventTitle), Theme.Color(clrBackground));
  y += ts.Height();
  if (!isempty(Info->ShortText())) {
//     const cFont *font = cFont::GetFont(fontSml);
//     ts.Set(osd, xl, y, x1 - xl, y3 - y, Info->ShortText(), font, Theme.Color(clrMenuEventShortText), Theme.Color(clrBackground));
     y += ts.Height();
     }
  y += font->Height();
  if (!isempty(Info->Description())) {
//     textScroller.Set(osd, xl, y, x1 - xl - 2 * ScrollWidth, y3 - y, Info->Description(), font, Theme.Color(clrMenuEventDescription), Theme.Color(clrBackground));
     SetScrollbar();
     }
#else
  SetText(Recording->Summary(), false); //TODO
#endif
}

void cNeutrinoSkinDisplayMenu::SetText(const char *Text, bool FixedFont) { CLOCK
//  const cFont *font = cFont::GetFont(FixedFont ? fontFix : fontOsd);
//  textScroller.Set(osd, x0, y2, x1 - x0 - 2 * ScrollWidth, y3 - y2, Text, font, Theme.Color(clrMenuText), Theme.Color(clrBackground));
  SetScrollbar();
}

void cNeutrinoSkinDisplayMenu::Flush(void) { CLOCK
  cString date = DayDateTime();
  const cFont *font = cFont::GetFont(fontOsd);
  DrawText(x1 - font->Width(date) - 2, y0, date, Theme.Color(clrMenuTitleFg), Theme.Color(clrMenuTitleBg), font);
  cNeutrinoSkinDisplay::Flush();
}

/******************************************************************************
 *
 * cNeutrinoSkinDisplayReplay
 *
 ******************************************************************************/

class cNeutrinoSkinDisplayReplay : public cSkinDisplayReplay {
	private:
		cOsd *osd;
		int x0, x1;
		int y0, y1, y2, y3;
		int lastCurrentWidth;
		bool message;
	public:
		cNeutrinoSkinDisplayReplay(bool ModeOnly);
		virtual ~cNeutrinoSkinDisplayReplay();
		virtual void SetTitle(const char *Title);
		virtual void SetMode(bool Play, bool Forward, int Speed);
		virtual void SetProgress(int Current, int Total);
		virtual void SetCurrent(const char *Current);
		virtual void SetTotal(const char *Total);
		virtual void SetJump(const char *Jump);
		virtual void SetMessage(eMessageType Type, const char *Text);
		virtual void Flush(void);
};

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

cNeutrinoSkinDisplayReplay::cNeutrinoSkinDisplayReplay(bool ModeOnly) { CLOCK
  const cFont *font = cFont::GetFont(fontOsd);
  int lineHeight = font->Height();
  lastCurrentWidth = 0;
  message = false;
  x0 = 0;
  x1 = Setup.OSDWidth;
  y0 = 0;
  y1 = lineHeight;
  y2 = 2 * lineHeight;
  y3 = 3 * lineHeight;
  osd = cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop + Setup.OSDHeight - y3);
  tArea Areas[] = { { x0, y0, x1 - 1, y3 - 1, 4 } };
  osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
  osd->DrawRectangle(x0, y0, x1 - 1, y3 - 1, ModeOnly ? clrTransparent : Theme.Color(clrBackground));
}

cNeutrinoSkinDisplayReplay::~cNeutrinoSkinDisplayReplay() { CLOCK
  delete osd;
}

void cNeutrinoSkinDisplayReplay::SetTitle(const char *Title) { CLOCK
  osd->DrawText(x0, y0, Title, Theme.Color(clrReplayTitle), Theme.Color(clrBackground), cFont::GetFont(fontOsd), x1 - x0);
}

void cNeutrinoSkinDisplayReplay::SetMode(bool Play, bool Forward, int Speed) { CLOCK
  if (Setup.ShowReplayMode) {
     const char *Mode;
     if (Speed == -1) Mode = Play    ? "  >  " : " ||  ";
     else if (Play)   Mode = Forward ? " X>> " : " <<X ";
     else             Mode = Forward ? " X|> " : " <|X ";
     char buf[16];
     strn0cpy(buf, Mode, sizeof(buf));
     char *p = strchr(buf, 'X');
     if (p)
        *p = Speed > 0 ? '1' + Speed - 1 : ' ';
     SetJump(buf);
     }
}

void cNeutrinoSkinDisplayReplay::SetProgress(int Current, int Total) { CLOCK
  cProgressBar pb(x1 - x0, y2 - y1, Current, Total, marks, Theme.Color(clrReplayProgressSeen), Theme.Color(clrReplayProgressRest), Theme.Color(clrReplayProgressSelected), Theme.Color(clrReplayProgressMark), Theme.Color(clrReplayProgressCurrent));
  osd->DrawBitmap(x0, y1, pb);
}

void cNeutrinoSkinDisplayReplay::SetCurrent(const char *Current) { CLOCK
  const cFont *font = cFont::GetFont(fontOsd);
  int w = font->Width(Current);
  osd->DrawText(x0, y2, Current, Theme.Color(clrReplayCurrent), Theme.Color(clrBackground), font, lastCurrentWidth > w ? lastCurrentWidth : 0);
  lastCurrentWidth = w;
}

void cNeutrinoSkinDisplayReplay::SetTotal(const char *Total) { CLOCK
  const cFont *font = cFont::GetFont(fontOsd);
  osd->DrawText(x1 - font->Width(Total), y2, Total, Theme.Color(clrReplayTotal), Theme.Color(clrBackground), font);
}

void cNeutrinoSkinDisplayReplay::SetJump(const char *Jump) { CLOCK
  osd->DrawText(x0 + (x1 - x0) / 4, y2, Jump, Theme.Color(clrReplayModeJump), Theme.Color(clrBackground), cFont::GetFont(fontOsd), (x1 - x0) / 2, 0, taCenter);
}

void cNeutrinoSkinDisplayReplay::SetMessage(eMessageType Type, const char *Text) { CLOCK
  const cFont *font = cFont::GetFont(fontOsd);
  if (Text) {
     osd->SaveRegion(x0, y2, x1 - 1, y3 - 1);
     osd->DrawText(x0, y2, Text, Theme.Color(clrMessageStatusFg + 2 * Type), Theme.Color(clrMessageStatusBg + 2 * Type), font, x1 - x0, y3 - y2, taCenter);
     message = true;
     }
  else {
     osd->RestoreRegion();
     message = false;
     }
}

void cNeutrinoSkinDisplayReplay::Flush(void) { CLOCK
  osd->Flush();
}

/******************************************************************************
 *
 * cNeutrinoSkinDisplayVolume
 *
 ******************************************************************************/

class cNeutrinoSkinDisplayVolume : public cSkinDisplayVolume, public cNeutrinoSkinDisplay {
	private:
		cOsd *osd;
		int barWidth, barHeight, barBorder;
		int volumeX0, volumeY0, volumeX1, volumeY1;
		int barX0, barY0, barX1, barY1;
		static cBitmap bmVolume, bmVolumeMute;
	public:
		cNeutrinoSkinDisplayVolume(void);
		virtual ~cNeutrinoSkinDisplayVolume();
		virtual void SetVolume(int Current, int Total, bool Mute);
		virtual void Flush(void);
};

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

cBitmap cNeutrinoSkinDisplayVolume::bmVolume(volume_xpm);
cBitmap cNeutrinoSkinDisplayVolume::bmVolumeMute(volume_mute_xpm);

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

cNeutrinoSkinDisplayVolume::cNeutrinoSkinDisplayVolume(void) { CLOCK
	barWidth = 200;
	barHeight = 12;
	barBorder = 2;

	barX0 = (Setup.OSDWidth - barWidth) / 2;
	barX1 = barX0 + barWidth - 1;

	volumeX0 = barX0 - barBorder - SkinSetup.Gap - bmVolume.Width();
	volumeX1 = barX1 + barBorder;

	volumeY0 = 0;
	barY0 = volumeY0 + SkinSetup.Gap + barBorder;
	barY1 = barY0 + barHeight;
	volumeY1 = barY1 + barBorder + SkinSetup.Gap;

	SetOsd(cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop + (Setup.ChannelInfoPos ? 0 : Setup.OSDHeight - volumeY1)));

	int ellipseR = (volumeY1 - volumeY0 - 1) / 4;
	tNeutrinoArea Areas[] = {
		{ volumeX0 - ellipseR, volumeY0, volumeX1 + ellipseR, volumeY1, 5, 0, leftAlign },
	};

	SetNeutrinoAreas(Areas, sizeof(Areas) / sizeof(tNeutrinoArea));

	DrawRectangle(0, 0, Width() - 1, Height() - 1, clrTransparent);
}

cNeutrinoSkinDisplayVolume::~cNeutrinoSkinDisplayVolume() { CLOCK
}

void cNeutrinoSkinDisplayVolume::SetVolume(int Current, int Total, bool Mute) { CLOCK
	DrawRectangle(volumeX0, volumeY0, volumeX1, volumeY1, Theme.Color(clrVolumeBg));
	int ellipseR = (volumeY1 - volumeY0 - 1) / 4;
	DrawEllipse(volumeX0 - ellipseR, volumeY0, volumeX0 + ellipseR, volumeY1, Theme.Color(clrVolumeBg));
	DrawEllipse(volumeX1 - ellipseR, volumeY0, volumeX1 + ellipseR, volumeY1, Theme.Color(clrVolumeBg));

	int iconY0 = volumeY0 + SkinSetup.Gap + 1;
	if (!Mute)
		DrawBitmap(volumeX0, iconY0, bmVolume, Theme.Color(clrVolumeFg), Theme.Color(clrVolumeBg));
	else
		DrawBitmap(volumeX0, iconY0, bmVolumeMute, Theme.Color(clrVolumeFg), Theme.Color(clrVolumeBg));

	DrawRectangle(barX0 - barBorder, barY0 - barBorder, barX1 + barBorder, barY1 + barBorder, Theme.Color(clrVolumeFg));

	int p = (barWidth) * Current / Total;
	DrawRectangle(barX0, barY0, barX0 + p - 1, barY1, Theme.Color(clrVolumeBarFg));
	DrawRectangle(barX0 + p, barY0, barX1, barY1, Theme.Color(clrVolumeBarBg));
}

void cNeutrinoSkinDisplayVolume::Flush(void) { CLOCK
	cNeutrinoSkinDisplay::Flush();
}

/******************************************************************************
 *
 * cNeutrinoSkinDisplayTracks
 *
 ******************************************************************************/

class cNeutrinoSkinDisplayTracks : public cSkinDisplayTracks {
	private:
		cOsd *osd;
		int x0, x1;
		int y0, y1, y2;
		int lineHeight;
		int currentIndex;
		int offset;
		int numTracks;
		void SetItem(const char *Text, int Index, bool Current);
	public:
		cNeutrinoSkinDisplayTracks(const char *Title, int NumTracks, const char * const *Tracks);
		virtual ~cNeutrinoSkinDisplayTracks();
		virtual void SetTrack(int Index, const char * const *Tracks);
		virtual void SetAudioChannel(int AudioChannel) {}
		virtual void Flush(void);
		virtual int MaxItems(void);
	virtual void Clear(void);
};

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

cNeutrinoSkinDisplayTracks::cNeutrinoSkinDisplayTracks(const char *Title, int NumTracks, const char * const *Tracks) { CLOCK
  const cFont *font = cFont::GetFont(fontOsd);
  lineHeight = font->Height();
  currentIndex = -1;
  offset = 0;
  numTracks = NumTracks;
  int ItemsWidth = font->Width(Title);
  for (int i = 0; i < NumTracks; i++)
      ItemsWidth = max(ItemsWidth, font->Width(Tracks[i]));
  ItemsWidth += 10;
  x0 = 0;
  x1 = Setup.OSDWidth;
  int d = x1 - x0;
  if (d > ItemsWidth) {
     d = (d - ItemsWidth) & ~0x07; // must be multiple of 8
     x1 -= d;
     }
  y0 = 0;
  y1 = lineHeight;
  y2 = y1 + NumTracks * lineHeight;

  // -> ugly hack
  if (y2 > Setup.OSDHeight)
    y2 = Setup.OSDHeight;

  osd = cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop + Setup.OSDHeight - y2);
  tArea Areas[] = { { x0, y0, x1 - 1, y2 - 1, 4 } };
  osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
  osd->DrawText(x0, y0, Title, Theme.Color(clrMenuTitleFg), Theme.Color(clrMenuTitleBg), font, x1 - x0);

//  for (int i = 0; i < NumTracks; i++)
  int MaxTracks = MaxItems();
  Clear();
  if (MaxTracks >= (numTracks - offset)) MaxTracks = numTracks - offset;
  for (int i = offset; i < offset + MaxTracks; i++)

      SetItem(Tracks[i], i, false);
}

cNeutrinoSkinDisplayTracks::~cNeutrinoSkinDisplayTracks() { CLOCK
  delete osd;
}

int cNeutrinoSkinDisplayTracks::MaxItems(void) { CLOCK
  return (y2 - y1) / lineHeight;
}

void cNeutrinoSkinDisplayTracks::Clear(void) { CLOCK
  osd->DrawRectangle(x0, y1, x1 - 1, y2 - 1, Theme.Color(clrBackground));
}

void cNeutrinoSkinDisplayTracks::SetItem(const char *Text, int Index, bool Current) { CLOCK
  int y = y1 + Index * lineHeight - offset * lineHeight;
  tColor ColorFg, ColorBg;
  if (Current) {
     ColorFg = Theme.Color(clrMenuItemCurrentFg);
     ColorBg = Theme.Color(clrMenuItemCurrentBg);
     currentIndex = Index;
     }
  else {
//     ColorFg = Theme.Color(clrMenuItemSelectable);
	ColorFg = Theme.Color(clrMenuItemFg);
     ColorBg = Theme.Color(clrBackground);
     }
  const cFont *font = cFont::GetFont(fontOsd);
  osd->DrawText(x0, y, Text, ColorFg, ColorBg, font, x1 - x0);
}

void cNeutrinoSkinDisplayTracks::SetTrack(int Index, const char * const *Tracks) { CLOCK
  if (currentIndex >= 0) {
     int MaxTracks = MaxItems();
     if ((Index - offset) >= MaxTracks) {
	offset += MaxTracks;
  	Clear();
	if (MaxTracks >= (numTracks - offset)) MaxTracks = numTracks - offset;
	for (int i = offset; i < offset + MaxTracks; i++)
    	    SetItem(Tracks[i], i, false);
     }
     else if (Index < offset) {
	offset -= MaxTracks;
  	Clear();
	if (MaxTracks >= (numTracks - offset)) MaxTracks = numTracks - offset;
	for (int i = offset; i < offset + MaxTracks; i++)
    	    SetItem(Tracks[i], i, false);
     }
     else SetItem(Tracks[currentIndex], currentIndex, false);
  }
  else {
     int MaxTracks = MaxItems();
     if (Index > MaxTracks) {
	int temp = Index;
        while (temp > MaxTracks) {
	    offset += MaxTracks;
	    temp   -= MaxTracks;
	}
  	Clear();
	if (MaxTracks >= (numTracks - offset)) MaxTracks = numTracks - offset;
	for (int i = offset; i < offset + MaxTracks; i++)
    	    SetItem(Tracks[i], i, false);
     }
  }
  SetItem(Tracks[Index], Index, true);
}

void cNeutrinoSkinDisplayTracks::Flush(void) { CLOCK
	osd->Flush();
}

/******************************************************************************
 *
 * cNeutrinoSkinDisplayMessage
 *
 ******************************************************************************/

class cNeutrinoSkinDisplayMessage : public cSkinDisplayMessage, public cNeutrinoSkinDisplay {
	public:
		cNeutrinoSkinDisplayMessage(void);
		virtual ~cNeutrinoSkinDisplayMessage();
		virtual void SetMessage(eMessageType Type, const char *Text);
		virtual void Flush(void);
};

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

cNeutrinoSkinDisplayMessage::cNeutrinoSkinDisplayMessage(void) { CLOCK
	int height = SkinSetup.FontMessage->Height(SkinSetup.SizeMessage) + 2 * SkinSetup.Gap;
	SetOsd(cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop + Setup.OSDHeight - height));
	tArea Areas[] = { { 0, 0, Setup.OSDWidth - 1, height - 1, 4 } }; // TODO
	SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
}

cNeutrinoSkinDisplayMessage::~cNeutrinoSkinDisplayMessage() { CLOCK
}

void cNeutrinoSkinDisplayMessage::SetMessage(eMessageType Type, const char *Text) { CLOCK
	// TODO type / colors
	DrawRectangle(0, 0, Width() - 1, Height() - 1, Theme.Color(clrMessageBg));
	DrawFreeType(SkinSetup.Gap, SkinSetup.Gap, Text, Theme.Color(clrMessageFg), Theme.Color(clrMessageBg), SkinSetup.FontBold, SkinSetup.SizeMessage, Width() - SkinSetup.Gap * 2, 0, taCenter);
	// TODO calc width => left align or center align
}

void cNeutrinoSkinDisplayMessage::Flush(void) { CLOCK
	cNeutrinoSkinDisplay::Flush();
}

/******************************************************************************
 *
 * cNeutrinoSkin
 *
 ******************************************************************************/

class cNeutrinoSkin : public cSkin {
	public:
		cNeutrinoSkin(void);
		virtual const char *Description(void);
		virtual cSkinDisplayChannel *DisplayChannel(bool WithInfo);
		virtual cSkinDisplayMenu *DisplayMenu(void);
		virtual cSkinDisplayReplay *DisplayReplay(bool ModeOnly);
		virtual cSkinDisplayVolume *DisplayVolume(void);
		virtual cSkinDisplayTracks *DisplayTracks(const char *Title, int NumTracks, const char * const *Tracks);
		virtual cSkinDisplayMessage *DisplayMessage(void);
};

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

cNeutrinoSkin::cNeutrinoSkin(void) : cSkin("neutrinoskin", &NeutrinoSkin::Theme) {
}

const char *cNeutrinoSkin::Description(void) { CLOCK
	return "Neutrino";
}

cSkinDisplayChannel *cNeutrinoSkin::DisplayChannel(bool WithInfo) { CLOCK
	return new cNeutrinoSkinDisplayChannel(WithInfo);
}

cSkinDisplayMenu *cNeutrinoSkin::DisplayMenu(void) { CLOCK
	return new cNeutrinoSkinDisplayMenu();
}

cSkinDisplayReplay *cNeutrinoSkin::DisplayReplay(bool ModeOnly) { CLOCK
	return new cNeutrinoSkinDisplayReplay(ModeOnly);
}

cSkinDisplayVolume *cNeutrinoSkin::DisplayVolume(void) { CLOCK
	return new cNeutrinoSkinDisplayVolume();
}

cSkinDisplayTracks *cNeutrinoSkin::DisplayTracks(const char *Title, int NumTracks, const char * const *Tracks) { CLOCK
	return new cNeutrinoSkinDisplayTracks(Title, NumTracks, Tracks);
}

cSkinDisplayMessage *cNeutrinoSkin::DisplayMessage(void) { CLOCK
	return new cNeutrinoSkinDisplayMessage();
}

/******************************************************************************
 *
 * cPluginNeutrinoSkin
 *
 ******************************************************************************/

class cPluginNeutrinoSkin : public cPlugin {
	private:
		cNeutrinoSkin* Skin;
	public:
		cPluginNeutrinoSkin(void);
		virtual ~cPluginNeutrinoSkin(void);
		virtual const char *Version(void);
		virtual const char *Description(void);
		virtual const char *CommandLineHelp(void);
		virtual bool ProcessArgs(int argc, char *argv[]);
		virtual bool Initialize(void);
		virtual bool Start(void);
		virtual void Housekeeping(void);
		virtual const char *MainMenuEntry(void);
		virtual cOsdObject *MainMenuAction(void);
		virtual cMenuSetupPage *SetupMenu(void);
		virtual bool SetupParse(const char *Name, const char *Value);
};

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

cPluginNeutrinoSkin::cPluginNeutrinoSkin(void) { CLOCK
}

cPluginNeutrinoSkin::~cPluginNeutrinoSkin(void) { CLOCK
}

const char *cPluginNeutrinoSkin::Version(void) { CLOCK
	return VERSION;
}

const char *cPluginNeutrinoSkin::Description(void) { CLOCK
	return DESCRIPTION;
}

const char *cPluginNeutrinoSkin::CommandLineHelp(void) { CLOCK
	return NULL;
}

bool cPluginNeutrinoSkin::ProcessArgs(int argc, char *argv[]) { CLOCK
	return true;
}

bool cPluginNeutrinoSkin::Initialize(void) { CLOCK
	return true;
}

bool cPluginNeutrinoSkin::Start(void) { CLOCK
	SkinSetup.SetName(Name());
	SkinSetup.LoadFonts();
	SkinSetup.LoadBitmapCache();
	SkinSetup.LoadChannelLogoConfigFile();
	SkinSetup.LoadChannelLogoCache();
	Skin = new cNeutrinoSkin();
	return true;
}

void cPluginNeutrinoSkin::Housekeeping(void) { CLOCK
}

const char *cPluginNeutrinoSkin::MainMenuEntry(void) { CLOCK
	return MAINMENUENTRY;
}

cOsdObject *cPluginNeutrinoSkin::MainMenuAction(void) { CLOCK
	return NULL;
}

cMenuSetupPage *cPluginNeutrinoSkin::SetupMenu(void) { CLOCK
	return new cNeutrinoSkinSetupPage();
	//return NULL;
}

bool cPluginNeutrinoSkin::SetupParse(const char *Name, const char *Value) { CLOCK
	return SkinSetup.Parse(Name, Value);
}

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

VDRPLUGINCREATOR(cPluginNeutrinoSkin);

}

// vim:syntax=cpp tabstop=8 foldmethod=marker foldlevel=0 foldcolumn=1

