mirror of
https://github.com/VDR4Arch/vdr.git
synced 2023-10-10 13:36:52 +02:00
650 lines
14 KiB
C
650 lines
14 KiB
C
/*
|
|
* osd.c: Abstract On Screen Display layer
|
|
*
|
|
* See the main source file 'vdr.c' for copyright information and
|
|
* how to reach the author.
|
|
*
|
|
* $Id: osd.c 1.36 2002/10/13 10:31:28 kls Exp $
|
|
*/
|
|
|
|
#include "osd.h"
|
|
#include <string.h>
|
|
#include "device.h"
|
|
#include "i18n.h"
|
|
#include "status.h"
|
|
|
|
// --- cOsd ------------------------------------------------------------------
|
|
|
|
#ifdef DEBUG_OSD
|
|
WINDOW *cOsd::window = NULL;
|
|
int cOsd::colorPairs[MaxColorPairs] = { 0 };
|
|
#else
|
|
cOsdBase *cOsd::osd = NULL;
|
|
#endif
|
|
int cOsd::cols = 0;
|
|
int cOsd::rows = 0;
|
|
|
|
void cOsd::Initialize(void)
|
|
{
|
|
#if defined(DEBUG_OSD) || defined(REMOTE_KBD)
|
|
initscr();
|
|
keypad(stdscr, true);
|
|
nonl();
|
|
cbreak();
|
|
noecho();
|
|
timeout(10);
|
|
#endif
|
|
#if defined(DEBUG_OSD)
|
|
start_color();
|
|
leaveok(stdscr, true);
|
|
#endif
|
|
}
|
|
|
|
void cOsd::Shutdown(void)
|
|
{
|
|
Close();
|
|
#if defined(DEBUG_OSD) || defined(REMOTE_KBD)
|
|
endwin();
|
|
#endif
|
|
}
|
|
|
|
#ifdef DEBUG_OSD
|
|
void cOsd::SetColor(eDvbColor colorFg, eDvbColor colorBg)
|
|
{
|
|
int color = (colorBg << 16) | colorFg | 0x80000000;
|
|
for (int i = 0; i < MaxColorPairs; i++) {
|
|
if (!colorPairs[i]) {
|
|
colorPairs[i] = color;
|
|
init_pair(i + 1, colorFg, colorBg);
|
|
wattrset(window, COLOR_PAIR(i + 1));
|
|
break;
|
|
}
|
|
else if (color == colorPairs[i]) {
|
|
wattrset(window, COLOR_PAIR(i + 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
cOsdBase *cOsd::OpenRaw(int x, int y)
|
|
{
|
|
#ifdef DEBUG_OSD
|
|
return NULL;
|
|
#else
|
|
return osd ? NULL : cDevice::PrimaryDevice()->NewOsd(x, y);
|
|
#endif
|
|
}
|
|
|
|
void cOsd::Open(int w, int h)
|
|
{
|
|
int d = (h < 0) ? Setup.OSDheight + h : 0;
|
|
h = abs(h);
|
|
cols = w;
|
|
rows = h;
|
|
#ifdef DEBUG_OSD
|
|
window = subwin(stdscr, h, w, d, (Setup.OSDwidth - w) / 2);
|
|
syncok(window, true);
|
|
#define B2C(b) (((b) * 1000) / 255)
|
|
#define SETCOLOR(n, r, g, b, o) init_color(n, B2C(r), B2C(g), B2C(b))
|
|
//XXX
|
|
SETCOLOR(clrBackground, 0x00, 0x00, 0x00, 127); // background 50% gray
|
|
SETCOLOR(clrBlack, 0x00, 0x00, 0x00, 255);
|
|
SETCOLOR(clrRed, 0xFC, 0x14, 0x14, 255);
|
|
SETCOLOR(clrGreen, 0x24, 0xFC, 0x24, 255);
|
|
SETCOLOR(clrYellow, 0xFC, 0xC0, 0x24, 255);
|
|
SETCOLOR(clrBlue, 0x00, 0x00, 0xFC, 255);
|
|
SETCOLOR(clrCyan, 0x00, 0xFC, 0xFC, 255);
|
|
SETCOLOR(clrMagenta, 0xB0, 0x00, 0xFC, 255);
|
|
SETCOLOR(clrWhite, 0xFC, 0xFC, 0xFC, 255);
|
|
#else
|
|
w *= charWidth;
|
|
h *= lineHeight;
|
|
d *= lineHeight;
|
|
int x = (720 - w + charWidth) / 2; //TODO PAL vs. NTSC???
|
|
int y = (576 - Setup.OSDheight * lineHeight) / 2 + d;
|
|
//XXX
|
|
osd = OpenRaw(x, y);
|
|
//XXX TODO this should be transferred to the places where the individual windows are requested (there's too much detailed knowledge here!)
|
|
if (h / lineHeight == 5) { //XXX channel display
|
|
osd->Create(0, 0, w, h, 4);
|
|
}
|
|
else if (h / lineHeight == 1) { //XXX info display
|
|
osd->Create(0, 0, w, h, 4);
|
|
}
|
|
else if (d == 0) { //XXX full menu
|
|
osd->Create(0, 0, w, lineHeight, 2);
|
|
osd->Create(0, lineHeight, w, (Setup.OSDheight - 3) * lineHeight, 2);
|
|
osd->AddColor(clrBackground);
|
|
osd->AddColor(clrCyan);
|
|
osd->AddColor(clrWhite);
|
|
osd->AddColor(clrBlack);
|
|
osd->Create(0, (Setup.OSDheight - 2) * lineHeight, w, 2 * lineHeight, 4);
|
|
}
|
|
else { //XXX progress display
|
|
/*XXX
|
|
osd->Create(0, 0, w, lineHeight, 1);
|
|
osd->Create(0, lineHeight, w, lineHeight, 2, false);
|
|
osd->Create(0, 2 * lineHeight, w, lineHeight, 1);
|
|
XXX*///XXX some pixels are not drawn correctly with lower bpp values
|
|
osd->Create(0, 0, w, h, 4);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void cOsd::Close(void)
|
|
{
|
|
#ifdef DEBUG_OSD
|
|
if (window) {
|
|
delwin(window);
|
|
window = 0;
|
|
}
|
|
#else
|
|
delete osd;
|
|
osd = NULL;
|
|
#endif
|
|
}
|
|
|
|
void cOsd::Clear(void)
|
|
{
|
|
#ifdef DEBUG_OSD
|
|
SetColor(clrBackground, clrBackground);
|
|
Fill(0, 0, cols, rows, clrBackground);
|
|
#else
|
|
osd->Clear();
|
|
#endif
|
|
}
|
|
|
|
void cOsd::Fill(int x, int y, int w, int h, eDvbColor color)
|
|
{
|
|
if (x < 0) x = cols + x;
|
|
if (y < 0) y = rows + y;
|
|
#ifdef DEBUG_OSD
|
|
SetColor(color, color);
|
|
for (int r = 0; r < h; r++) {
|
|
wmove(window, y + r, x); // ncurses wants 'y' before 'x'!
|
|
whline(window, ' ', w);
|
|
}
|
|
wsyncup(window); // shouldn't be necessary because of 'syncok()', but w/o it doesn't work
|
|
#else
|
|
osd->Fill(x * charWidth, y * lineHeight, (x + w) * charWidth - 1, (y + h) * lineHeight - 1, color);
|
|
#endif
|
|
}
|
|
|
|
void cOsd::SetBitmap(int x, int y, const cBitmap &Bitmap)
|
|
{
|
|
#ifndef DEBUG_OSD
|
|
osd->SetBitmap(x, y, Bitmap);
|
|
#endif
|
|
}
|
|
|
|
void cOsd::ClrEol(int x, int y, eDvbColor color)
|
|
{
|
|
Fill(x, y, cols - x, 1, color);
|
|
}
|
|
|
|
int cOsd::CellWidth(void)
|
|
{
|
|
#ifdef DEBUG_OSD
|
|
return 1;
|
|
#else
|
|
return charWidth;
|
|
#endif
|
|
}
|
|
|
|
int cOsd::LineHeight(void)
|
|
{
|
|
#ifdef DEBUG_OSD
|
|
return 1;
|
|
#else
|
|
return lineHeight;
|
|
#endif
|
|
}
|
|
|
|
int cOsd::Width(unsigned char c)
|
|
{
|
|
#ifdef DEBUG_OSD
|
|
return 1;
|
|
#else
|
|
return osd->Width(c);
|
|
#endif
|
|
}
|
|
|
|
int cOsd::WidthInCells(const char *s)
|
|
{
|
|
#ifdef DEBUG_OSD
|
|
return strlen(s);
|
|
#else
|
|
return (osd->Width(s) + charWidth - 1) / charWidth;
|
|
#endif
|
|
}
|
|
|
|
eDvbFont cOsd::SetFont(eDvbFont Font)
|
|
{
|
|
#ifdef DEBUG_OSD
|
|
return Font;
|
|
#else
|
|
return osd->SetFont(Font);
|
|
#endif
|
|
}
|
|
|
|
void cOsd::Text(int x, int y, const char *s, eDvbColor colorFg, eDvbColor colorBg)
|
|
{
|
|
if (x < 0) x = cols + x;
|
|
if (y < 0) y = rows + y;
|
|
#ifdef DEBUG_OSD
|
|
SetColor(colorFg, colorBg);
|
|
wmove(window, y, x); // ncurses wants 'y' before 'x'!
|
|
waddnstr(window, s, cols - x);
|
|
#else
|
|
osd->Text(x * charWidth, y * lineHeight, s, colorFg, colorBg);
|
|
#endif
|
|
}
|
|
|
|
void cOsd::Flush(void)
|
|
{
|
|
#ifdef DEBUG_OSD
|
|
refresh();
|
|
#else
|
|
if (osd)
|
|
osd->Flush();
|
|
#endif
|
|
}
|
|
|
|
// --- cOsdItem --------------------------------------------------------------
|
|
|
|
cOsdItem::cOsdItem(eOSState State)
|
|
{
|
|
text = NULL;
|
|
offset = -1;
|
|
state = State;
|
|
fresh = false;
|
|
userColor = false;
|
|
fgColor = clrWhite;
|
|
bgColor = clrBackground;
|
|
}
|
|
|
|
cOsdItem::cOsdItem(const char *Text, eOSState State)
|
|
{
|
|
text = NULL;
|
|
offset = -1;
|
|
state = State;
|
|
fresh = false;
|
|
userColor = false;
|
|
fgColor = clrWhite;
|
|
bgColor = clrBackground;
|
|
SetText(Text);
|
|
}
|
|
|
|
cOsdItem::~cOsdItem()
|
|
{
|
|
free(text);
|
|
}
|
|
|
|
void cOsdItem::SetText(const char *Text, bool Copy)
|
|
{
|
|
free(text);
|
|
text = Copy ? strdup(Text) : (char *)Text; // text assumes ownership!
|
|
}
|
|
|
|
void cOsdItem::SetColor(eDvbColor FgColor, eDvbColor BgColor)
|
|
{
|
|
userColor = true;
|
|
fgColor = FgColor;
|
|
bgColor = BgColor;
|
|
}
|
|
|
|
void cOsdItem::Display(int Offset, eDvbColor FgColor, eDvbColor BgColor)
|
|
{
|
|
if (Offset < 0) {
|
|
FgColor = clrBlack;
|
|
BgColor = clrCyan;
|
|
}
|
|
fresh |= Offset >= 0;
|
|
if (Offset >= 0)
|
|
offset = Offset;
|
|
if (offset >= 0)
|
|
Interface->WriteText(0, offset + 2, text, userColor ? fgColor : FgColor, userColor ? bgColor : BgColor);
|
|
}
|
|
|
|
eOSState cOsdItem::ProcessKey(eKeys Key)
|
|
{
|
|
return Key == kOk ? state : osUnknown;
|
|
}
|
|
|
|
// --- cOsdMenu --------------------------------------------------------------
|
|
|
|
cOsdMenu::cOsdMenu(const char *Title, int c0, int c1, int c2, int c3, int c4)
|
|
{
|
|
digit = 0;
|
|
hasHotkeys = false;
|
|
visible = false;
|
|
title = NULL;
|
|
SetTitle(Title);
|
|
cols[0] = c0;
|
|
cols[1] = c1;
|
|
cols[2] = c2;
|
|
cols[3] = c3;
|
|
cols[4] = c4;
|
|
first = 0;
|
|
current = marked = -1;
|
|
subMenu = NULL;
|
|
helpRed = helpGreen = helpYellow = helpBlue = NULL;
|
|
status = NULL;
|
|
Interface->Open();
|
|
}
|
|
|
|
cOsdMenu::~cOsdMenu()
|
|
{
|
|
free(title);
|
|
delete subMenu;
|
|
free(status);
|
|
Interface->Clear();
|
|
Interface->Close();
|
|
}
|
|
|
|
const char *cOsdMenu::hk(const char *s)
|
|
{
|
|
static char buffer[64];
|
|
if (s && hasHotkeys) {
|
|
if (digit == 0 && '1' <= *s && *s <= '9' && *(s + 1) == ' ')
|
|
digit = -1; // prevents automatic hotkeys - input already has them
|
|
if (digit >= 0) {
|
|
digit++;
|
|
snprintf(buffer, sizeof(buffer), " %c %s", (digit < 10) ? '0' + digit : ' ' , s);
|
|
s = buffer;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
void cOsdMenu::SetHasHotkeys(void)
|
|
{
|
|
hasHotkeys = true;
|
|
digit = 0;
|
|
}
|
|
|
|
void cOsdMenu::SetStatus(const char *s)
|
|
{
|
|
free(status);
|
|
status = s ? strdup(s) : NULL;
|
|
if (visible)
|
|
Interface->Status(status);
|
|
}
|
|
|
|
void cOsdMenu::SetTitle(const char *Title, bool ShowDate)
|
|
{
|
|
free(title);
|
|
if (ShowDate)
|
|
asprintf(&title, "%s\t%s", Title, DayDateTime(time(NULL)));
|
|
else
|
|
title = strdup(Title);
|
|
}
|
|
|
|
void cOsdMenu::SetHelp(const char *Red, const char *Green, const char *Yellow, const char *Blue)
|
|
{
|
|
// strings are NOT copied - must be constants!!!
|
|
helpRed = Red;
|
|
helpGreen = Green;
|
|
helpYellow = Yellow;
|
|
helpBlue = Blue;
|
|
if (visible)
|
|
Interface->Help(helpRed, helpGreen, helpYellow, helpBlue);
|
|
}
|
|
|
|
void cOsdMenu::Del(int Index)
|
|
{
|
|
cList<cOsdItem>::Del(Get(Index));
|
|
if (current == Count())
|
|
current--;
|
|
if (Index == first && first > 0)
|
|
first--;
|
|
}
|
|
|
|
void cOsdMenu::Add(cOsdItem *Item, bool Current, cOsdItem *After)
|
|
{
|
|
cList<cOsdItem>::Add(Item, After);
|
|
if (Current)
|
|
current = Item->Index();
|
|
}
|
|
|
|
void cOsdMenu::Ins(cOsdItem *Item, bool Current, cOsdItem *Before)
|
|
{
|
|
cList<cOsdItem>::Ins(Item, Before);
|
|
if (Current)
|
|
current = Item->Index();
|
|
}
|
|
|
|
void cOsdMenu::Display(void)
|
|
{
|
|
if (subMenu) {
|
|
subMenu->Display();
|
|
return;
|
|
}
|
|
visible = true;
|
|
Interface->Clear();
|
|
Interface->SetCols(cols);
|
|
Interface->Title(title);
|
|
Interface->Help(helpRed, helpGreen, helpYellow, helpBlue);
|
|
int count = Count();
|
|
if (count > 0) {
|
|
if (current < 0)
|
|
current = 0; // just for safety - there HAS to be a current item!
|
|
int n = 0;
|
|
if (current - first >= MAXOSDITEMS) {
|
|
first = current - MAXOSDITEMS / 2;
|
|
if (first + MAXOSDITEMS > count)
|
|
first = count - MAXOSDITEMS;
|
|
if (first < 0)
|
|
first = 0;
|
|
}
|
|
for (int i = first; i < count; i++) {
|
|
cOsdItem *item = Get(i);
|
|
if (item) {
|
|
item->Display(i - first, i == current ? clrBlack : clrWhite, i == current ? clrCyan : clrBackground);
|
|
if (i == current)
|
|
cStatus::MsgOsdCurrentItem(item->Text());
|
|
}
|
|
if (++n == MAXOSDITEMS) //TODO get this from Interface!!!
|
|
break;
|
|
}
|
|
}
|
|
if (!isempty(status))
|
|
Interface->Status(status);
|
|
}
|
|
|
|
void cOsdMenu::SetCurrent(cOsdItem *Item)
|
|
{
|
|
current = Item ? Item->Index() : -1;
|
|
}
|
|
|
|
void cOsdMenu::RefreshCurrent(void)
|
|
{
|
|
cOsdItem *item = Get(current);
|
|
if (item)
|
|
item->Set();
|
|
}
|
|
|
|
void cOsdMenu::DisplayCurrent(bool Current)
|
|
{
|
|
cOsdItem *item = Get(current);
|
|
if (item) {
|
|
item->Display(current - first, Current ? clrBlack : clrWhite, Current ? clrCyan : clrBackground);
|
|
if (Current)
|
|
cStatus::MsgOsdCurrentItem(item->Text());
|
|
}
|
|
}
|
|
|
|
void cOsdMenu::Clear(void)
|
|
{
|
|
first = 0;
|
|
current = marked = -1;
|
|
cList<cOsdItem>::Clear();
|
|
}
|
|
|
|
bool cOsdMenu::SpecialItem(int idx)
|
|
{
|
|
cOsdItem *item = Get(idx);
|
|
return item && item->HasUserColor();
|
|
}
|
|
|
|
void cOsdMenu::CursorUp(void)
|
|
{
|
|
if (current > 0) {
|
|
int tmpCurrent = current;
|
|
while (--tmpCurrent >= 0 && SpecialItem(tmpCurrent));
|
|
if (tmpCurrent < 0)
|
|
return;
|
|
if (tmpCurrent >= first)
|
|
DisplayCurrent(false);
|
|
current = tmpCurrent;
|
|
if (current < first) {
|
|
first = first > MAXOSDITEMS - 1 ? first - (MAXOSDITEMS - 1) : 0;
|
|
if (Setup.MenuScrollPage)
|
|
current = SpecialItem(first) ? first + 1 : first;
|
|
Display();
|
|
}
|
|
else
|
|
DisplayCurrent(true);
|
|
}
|
|
}
|
|
|
|
void cOsdMenu::CursorDown(void)
|
|
{
|
|
int last = Count() - 1;
|
|
int lastOnScreen = first + MAXOSDITEMS - 1;
|
|
|
|
if (current < last) {
|
|
int tmpCurrent = current;
|
|
while (++tmpCurrent <= last && SpecialItem(tmpCurrent));
|
|
if (tmpCurrent > last)
|
|
return;
|
|
if (tmpCurrent <= lastOnScreen)
|
|
DisplayCurrent(false);
|
|
current = tmpCurrent;
|
|
if (current > lastOnScreen) {
|
|
first += MAXOSDITEMS - 1;
|
|
lastOnScreen = first + MAXOSDITEMS - 1;
|
|
if (lastOnScreen > last) {
|
|
first = last - (MAXOSDITEMS - 1);
|
|
lastOnScreen = last;
|
|
}
|
|
if (Setup.MenuScrollPage)
|
|
current = SpecialItem(lastOnScreen) ? lastOnScreen - 1 : lastOnScreen;
|
|
Display();
|
|
}
|
|
else
|
|
DisplayCurrent(true);
|
|
}
|
|
}
|
|
|
|
void cOsdMenu::PageUp(void)
|
|
{
|
|
if (Count() <= MAXOSDITEMS)
|
|
return;
|
|
current -= MAXOSDITEMS;
|
|
first -= MAXOSDITEMS;
|
|
if (first < 0)
|
|
first = current = 0;
|
|
if (SpecialItem(current)) {
|
|
current -= (current > 0) ? 1 : -1;
|
|
first = min(first, current - 1);
|
|
}
|
|
Display();
|
|
DisplayCurrent(true);
|
|
}
|
|
|
|
void cOsdMenu::PageDown(void)
|
|
{
|
|
if (Count() <= MAXOSDITEMS)
|
|
return;
|
|
current += MAXOSDITEMS;
|
|
first += MAXOSDITEMS;
|
|
if (current > Count() - 1) {
|
|
current = Count() - 1;
|
|
first = Count() - MAXOSDITEMS;
|
|
}
|
|
if (SpecialItem(current)) {
|
|
current += (current < Count() - 1) ? 1 : -1;
|
|
first = max(first, current - MAXOSDITEMS);
|
|
}
|
|
Display();
|
|
DisplayCurrent(true);
|
|
}
|
|
|
|
void cOsdMenu::Mark(void)
|
|
{
|
|
if (Count() && marked < 0) {
|
|
marked = current;
|
|
SetStatus(tr("Up/Dn for new location - OK to move"));
|
|
}
|
|
}
|
|
|
|
eOSState cOsdMenu::HotKey(eKeys Key)
|
|
{
|
|
for (cOsdItem *item = First(); item; item = Next(item)) {
|
|
const char *s = item->Text();
|
|
if (s && (s = skipspace(s)) != NULL) {
|
|
if (*s == Key - k1 + '1') {
|
|
current = item->Index();
|
|
return ProcessKey(kOk);
|
|
}
|
|
}
|
|
}
|
|
return osContinue;
|
|
}
|
|
|
|
eOSState cOsdMenu::AddSubMenu(cOsdMenu *SubMenu)
|
|
{
|
|
delete subMenu;
|
|
subMenu = SubMenu;
|
|
subMenu->Display();
|
|
return osContinue; // convenience return value (see cMenuMain)
|
|
}
|
|
|
|
eOSState cOsdMenu::ProcessKey(eKeys Key)
|
|
{
|
|
if (subMenu) {
|
|
eOSState state = subMenu->ProcessKey(Key);
|
|
if (state == osBack) {
|
|
delete subMenu;
|
|
subMenu = NULL;
|
|
RefreshCurrent();
|
|
Display();
|
|
state = osContinue;
|
|
}
|
|
return state;
|
|
}
|
|
|
|
cOsdItem *item = Get(current);
|
|
if (marked < 0 && item) {
|
|
eOSState state = item->ProcessKey(Key);
|
|
if (state != osUnknown)
|
|
return state;
|
|
}
|
|
switch (Key) {
|
|
case k1...k9: return hasHotkeys ? HotKey(Key) : osUnknown;
|
|
case kUp|k_Repeat:
|
|
case kUp: CursorUp(); break;
|
|
case kDown|k_Repeat:
|
|
case kDown: CursorDown(); break;
|
|
case kLeft|k_Repeat:
|
|
case kLeft: PageUp(); break;
|
|
case kRight|k_Repeat:
|
|
case kRight: PageDown(); break;
|
|
case kBack: return osBack;
|
|
case kOk: if (marked >= 0) {
|
|
SetStatus(NULL);
|
|
if (marked != current)
|
|
Move(marked, current);
|
|
marked = -1;
|
|
break;
|
|
}
|
|
// else run into default
|
|
default: if (marked < 0)
|
|
return osUnknown;
|
|
}
|
|
return osContinue;
|
|
}
|
|
|