2000-02-19 13:36:48 +01:00
|
|
|
/*
|
|
|
|
* osd.c: Abstract On Screen Display layer
|
|
|
|
*
|
2000-04-24 09:46:05 +02:00
|
|
|
* See the main source file 'vdr.c' for copyright information and
|
2000-02-19 13:36:48 +01:00
|
|
|
* how to reach the author.
|
|
|
|
*
|
2004-06-12 14:04:01 +02:00
|
|
|
* $Id: osd.c 1.54 2004/06/12 13:59:12 kls Exp $
|
2000-02-19 13:36:48 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "osd.h"
|
2004-05-16 10:35:36 +02:00
|
|
|
#include <math.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/unistd.h>
|
|
|
|
#include "tools.h"
|
2000-02-19 13:36:48 +01:00
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
// --- cPalette --------------------------------------------------------------
|
2002-05-18 14:03:22 +02:00
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
cPalette::cPalette(int Bpp)
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
SetBpp(Bpp);
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cPalette::Reset(void)
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
numColors = 0;
|
|
|
|
modified = false;
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
int cPalette::Index(tColor Color)
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
for (int i = 0; i < numColors; i++) {
|
|
|
|
if (color[i] == Color)
|
|
|
|
return i;
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
2004-05-16 10:35:36 +02:00
|
|
|
if (numColors < maxColors) {
|
|
|
|
color[numColors++] = Color;
|
|
|
|
modified = true;
|
|
|
|
return numColors - 1;
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
2004-05-16 10:35:36 +02:00
|
|
|
dsyslog("too many different colors used in palette");
|
|
|
|
//TODO: return the index of the "closest" color?
|
|
|
|
return 0;
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cPalette::SetBpp(int Bpp)
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
bpp = Bpp;
|
|
|
|
maxColors = 1 << bpp;
|
|
|
|
Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void cPalette::SetColor(int Index, tColor Color)
|
|
|
|
{
|
|
|
|
if (Index < maxColors) {
|
|
|
|
if (numColors <= Index) {
|
|
|
|
numColors = Index + 1;
|
|
|
|
modified = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
modified |= color[Index] != Color;
|
|
|
|
color[Index] = Color;
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
const tColor *cPalette::Colors(int &NumColors)
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
NumColors = numColors;
|
|
|
|
return numColors ? color : NULL;
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cPalette::Take(const cPalette &Palette, tIndexes *Indexes, tColor ColorFg, tColor ColorBg)
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
for (int i = 0; i < Palette.numColors; i++) {
|
|
|
|
tColor Color = Palette.color[i];
|
|
|
|
if (ColorFg || ColorBg) {
|
|
|
|
switch (i) {
|
|
|
|
case 0: Color = ColorBg; break;
|
|
|
|
case 1: Color = ColorFg; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int n = Index(Color);
|
|
|
|
if (Indexes)
|
|
|
|
(*Indexes)[i] = n;
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
// --- cBitmap ---------------------------------------------------------------
|
|
|
|
|
|
|
|
cBitmap::cBitmap(int Width, int Height, int Bpp, int X0, int Y0)
|
|
|
|
:cPalette(Bpp)
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
bitmap = NULL;
|
|
|
|
x0 = X0;
|
|
|
|
y0 = Y0;
|
|
|
|
SetSize(Width, Height);
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
cBitmap::cBitmap(const char *FileName)
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
bitmap = NULL;
|
|
|
|
x0 = 0;
|
|
|
|
y0 = 0;
|
|
|
|
LoadXpm(FileName);
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
cBitmap::cBitmap(char *Xpm[])
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
bitmap = NULL;
|
|
|
|
x0 = 0;
|
|
|
|
y0 = 0;
|
|
|
|
SetXpm(Xpm);
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
cBitmap::~cBitmap()
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
free(bitmap);
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cBitmap::SetSize(int Width, int Height)
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
if (bitmap && Width == width && Height == height)
|
|
|
|
return;
|
|
|
|
width = Width;
|
|
|
|
height = Height;
|
|
|
|
free(bitmap);
|
|
|
|
bitmap = NULL;
|
|
|
|
dirtyX1 = 0;
|
|
|
|
dirtyY1 = 0;
|
|
|
|
dirtyX2 = width - 1;
|
|
|
|
dirtyY2 = height - 1;
|
|
|
|
if (width > 0 && height > 0) {
|
|
|
|
bitmap = MALLOC(tIndex, width * height);
|
|
|
|
if (bitmap)
|
|
|
|
memset(bitmap, 0x00, width * height);
|
|
|
|
else
|
|
|
|
esyslog("ERROR: can't allocate bitmap!");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
esyslog("ERROR: illegal bitmap parameters (%d, %d)!", width, height);
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
bool cBitmap::Contains(int x, int y) const
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
x -= x0;
|
|
|
|
y -= y0;
|
|
|
|
return 0 <= x && x < width && 0 <= y && y < height;
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-28 15:33:22 +02:00
|
|
|
bool cBitmap::Covers(int x1, int y1, int x2, int y2) const
|
|
|
|
{
|
|
|
|
x1 -= x0;
|
|
|
|
y1 -= y0;
|
|
|
|
x2 -= x0;
|
|
|
|
y2 -= y0;
|
|
|
|
return x1 <= 0 && y1 <= 0 && x2 >= width - 1 && y2 >= height - 1;
|
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
bool cBitmap::Intersects(int x1, int y1, int x2, int y2) const
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
x1 -= x0;
|
|
|
|
y1 -= y0;
|
|
|
|
x2 -= x0;
|
|
|
|
y2 -= y0;
|
|
|
|
return !(x2 < 0 || x1 >= width || y2 < 0 || y1 >= height);
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
bool cBitmap::Dirty(int &x1, int &y1, int &x2, int &y2)
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
if (dirtyX2 >= 0) {
|
|
|
|
x1 = dirtyX1;
|
|
|
|
y1 = dirtyY1;
|
|
|
|
x2 = dirtyX2;
|
|
|
|
y2 = dirtyY2;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void cBitmap::Clean(void)
|
|
|
|
{
|
|
|
|
dirtyX1 = width;
|
|
|
|
dirtyY1 = height;
|
|
|
|
dirtyX2 = -1;
|
|
|
|
dirtyY2 = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool cBitmap::LoadXpm(const char *FileName)
|
|
|
|
{
|
|
|
|
bool Result = false;
|
|
|
|
FILE *f = fopen(FileName, "r");
|
|
|
|
if (f) {
|
|
|
|
char **Xpm = NULL;
|
|
|
|
bool isXpm = false;
|
|
|
|
int lines = 0;
|
|
|
|
int index = 0;
|
|
|
|
char *s;
|
|
|
|
while ((s = readline(f)) != NULL) {
|
|
|
|
s = skipspace(s);
|
|
|
|
if (!isXpm) {
|
|
|
|
if (strcmp(s, "/* XPM */") != 0) {
|
|
|
|
esyslog("ERROR: invalid header in XPM file '%s'", FileName);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
isXpm = true;
|
|
|
|
}
|
|
|
|
else if (*s++ == '"') {
|
|
|
|
if (!lines) {
|
|
|
|
int w, h, n, c;
|
|
|
|
if (4 != sscanf(s, "%d %d %d %d", &w, &h, &n, &c)) {
|
|
|
|
esyslog("ERROR: faulty 'values' line in XPM file '%s'", FileName);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
lines = h + n + 1;
|
|
|
|
Xpm = MALLOC(char *, lines);
|
|
|
|
}
|
|
|
|
char *q = strchr(s, '"');
|
|
|
|
if (!q) {
|
|
|
|
esyslog("ERROR: missing quotes in XPM file '%s'", FileName);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
*q = 0;
|
|
|
|
if (index < lines)
|
|
|
|
Xpm[index++] = strdup(s);
|
|
|
|
else {
|
|
|
|
esyslog("ERROR: too many lines in XPM file '%s'", FileName);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (index == lines)
|
|
|
|
Result = SetXpm(Xpm);
|
|
|
|
else
|
|
|
|
esyslog("ERROR: too few lines in XPM file '%s'", FileName);
|
|
|
|
for (int i = 0; i < index; i++)
|
|
|
|
free(Xpm[i]);
|
|
|
|
free(Xpm);
|
|
|
|
fclose(f);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
esyslog("ERROR: can't open XPM file '%s'", FileName);
|
|
|
|
return Result;
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-06-05 13:20:19 +02:00
|
|
|
bool cBitmap::SetXpm(char *Xpm[], bool IgnoreNone)
|
2002-05-18 14:03:22 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
char **p = Xpm;
|
|
|
|
int w, h, n, c;
|
|
|
|
if (4 != sscanf(*p, "%d %d %d %d", &w, &h, &n, &c)) {
|
|
|
|
esyslog("ERROR: faulty 'values' line in XPM: '%s'", *p);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (n > MAXNUMCOLORS) {
|
|
|
|
esyslog("ERROR: too many colors in XPM: %d", n);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
int b = 0;
|
2004-06-05 13:20:19 +02:00
|
|
|
while (1 << (1 << b) < (IgnoreNone ? n - 1 : n))
|
2004-05-16 10:35:36 +02:00
|
|
|
b++;
|
|
|
|
SetBpp(1 << b);
|
|
|
|
SetSize(w, h);
|
2004-06-05 13:20:19 +02:00
|
|
|
int NoneColorIndex = MAXNUMCOLORS;
|
2004-05-16 10:35:36 +02:00
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
const char *s = *++p;
|
|
|
|
if (int(strlen(s)) < c) {
|
|
|
|
esyslog("ERROR: faulty 'colors' line in XPM: '%s'", s);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
s = skipspace(s + c);
|
|
|
|
if (*s != 'c') {
|
|
|
|
esyslog("ERROR: unknown color key in XPM: '%c'", *s);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
s = skipspace(s + 1);
|
2004-06-05 13:20:19 +02:00
|
|
|
if (strcasecmp(s, "none") == 0) {
|
2004-05-22 13:53:32 +02:00
|
|
|
s = "#00000000";
|
2004-06-05 13:20:19 +02:00
|
|
|
NoneColorIndex = i;
|
|
|
|
if (IgnoreNone)
|
|
|
|
continue;
|
|
|
|
}
|
2004-05-16 10:35:36 +02:00
|
|
|
if (*s != '#') {
|
|
|
|
esyslog("ERROR: unknown color code in XPM: '%c'", *s);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
tColor color = strtoul(++s, NULL, 16) | 0xFF000000;
|
2004-06-05 13:20:19 +02:00
|
|
|
SetColor((IgnoreNone && i > NoneColorIndex) ? i - 1 : i, color);
|
2004-05-16 10:35:36 +02:00
|
|
|
}
|
|
|
|
for (int y = 0; y < h; y++) {
|
|
|
|
const char *s = *++p;
|
|
|
|
if (int(strlen(s)) != w * c) {
|
|
|
|
esyslog("ERROR: faulty pixel line in XPM: %d '%s'", y, s);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (int x = 0; x < w; x++) {
|
|
|
|
for (int i = 0; i <= n; i++) {
|
|
|
|
if (i == n) {
|
|
|
|
esyslog("ERROR: undefined pixel color in XPM: %d %d '%s'", x, y, s);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (strncmp(Xpm[i + 1], s, c) == 0) {
|
2004-06-05 13:20:19 +02:00
|
|
|
if (i == NoneColorIndex)
|
|
|
|
NoneColorIndex = MAXNUMCOLORS;
|
|
|
|
SetIndex(x, y, (IgnoreNone && i > NoneColorIndex) ? i - 1 : i);
|
2004-05-16 10:35:36 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s += c;
|
|
|
|
}
|
|
|
|
}
|
2004-06-05 13:20:19 +02:00
|
|
|
if (NoneColorIndex < MAXNUMCOLORS && !IgnoreNone)
|
|
|
|
return SetXpm(Xpm, true);
|
2004-05-16 10:35:36 +02:00
|
|
|
return true;
|
2002-05-18 14:03:22 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cBitmap::SetIndex(int x, int y, tIndex Index)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
if (bitmap) {
|
|
|
|
if (0 <= x && x < width && 0 <= y && y < height) {
|
|
|
|
if (bitmap[width * y + x] != Index) {
|
|
|
|
bitmap[width * y + x] = Index;
|
|
|
|
if (dirtyX1 > x) dirtyX1 = x;
|
|
|
|
if (dirtyY1 > y) dirtyY1 = y;
|
|
|
|
if (dirtyX2 < x) dirtyX2 = x;
|
|
|
|
if (dirtyY2 < y) dirtyY2 = y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cBitmap::DrawPixel(int x, int y, tColor Color)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
x -= x0;
|
|
|
|
y -= y0;
|
2004-06-05 11:24:37 +02:00
|
|
|
if (0 <= x && x < width && 0 <= y && y < height)
|
|
|
|
SetIndex(x, y, Index(Color));
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cBitmap::DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg, tColor ColorBg)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
if (bitmap && Bitmap.bitmap && Intersects(x, y, x + Bitmap.Width() - 1, y + Bitmap.Height() - 1)) {
|
2004-05-28 15:33:22 +02:00
|
|
|
if (Covers(x, y, x + Bitmap.Width() - 1, y + Bitmap.Height() - 1))
|
|
|
|
Reset();
|
2004-05-16 10:35:36 +02:00
|
|
|
x -= x0;
|
|
|
|
y -= y0;
|
|
|
|
tIndexes Indexes;
|
|
|
|
Take(Bitmap, &Indexes, ColorFg, ColorBg);
|
|
|
|
for (int ix = 0; ix < Bitmap.width; ix++) {
|
|
|
|
for (int iy = 0; iy < Bitmap.height; iy++)
|
|
|
|
SetIndex(x + ix, y + iy, Indexes[int(Bitmap.bitmap[Bitmap.width * iy + ix])]);
|
|
|
|
}
|
|
|
|
}
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cBitmap::DrawText(int x, int y, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width, int Height, int Alignment)
|
|
|
|
{
|
|
|
|
if (bitmap) {
|
|
|
|
int w = Font->Width(s);
|
|
|
|
int h = Font->Height();
|
|
|
|
int limit = 0;
|
|
|
|
if (Width || Height) {
|
|
|
|
int cw = Width ? Width : w;
|
|
|
|
int ch = Height ? Height : h;
|
|
|
|
if (!Intersects(x, y, x + cw - 1, y + ch - 1))
|
|
|
|
return;
|
2004-06-05 11:42:08 +02:00
|
|
|
if (ColorBg != clrTransparent)
|
|
|
|
DrawRectangle(x, y, x + cw - 1, y + ch - 1, ColorBg);
|
2004-05-16 10:35:36 +02:00
|
|
|
limit = x + cw - x0;
|
|
|
|
if (Width) {
|
|
|
|
if ((Alignment & taLeft) != 0)
|
|
|
|
;
|
|
|
|
else if ((Alignment & taRight) != 0) {
|
|
|
|
if (w < Width)
|
|
|
|
x += Width - w;
|
|
|
|
}
|
|
|
|
else { // taCentered
|
|
|
|
if (w < Width)
|
|
|
|
x += (Width - w) / 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (Height) {
|
|
|
|
if ((Alignment & taTop) != 0)
|
|
|
|
;
|
|
|
|
else if ((Alignment & taBottom) != 0) {
|
|
|
|
if (h < Height)
|
|
|
|
y += Height - h;
|
|
|
|
}
|
|
|
|
else { // taCentered
|
|
|
|
if (h < Height)
|
|
|
|
y += (Height - h) / 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!Intersects(x, y, x + w - 1, y + h - 1))
|
|
|
|
return;
|
|
|
|
x -= x0;
|
|
|
|
y -= y0;
|
|
|
|
tIndex fg = Index(ColorFg);
|
2004-06-05 16:52:51 +02:00
|
|
|
tIndex bg = (ColorBg != clrTransparent) ? Index(ColorBg) : 0;
|
2004-05-16 10:35:36 +02:00
|
|
|
while (s && *s) {
|
|
|
|
const cFont::tCharData *CharData = Font->CharData(*s++);
|
|
|
|
if (limit && int(x + CharData->width) > limit)
|
|
|
|
break; // we don't draw partial characters
|
|
|
|
if (int(x + CharData->width) > 0) {
|
|
|
|
for (int row = 0; row < h; row++) {
|
|
|
|
cFont::tPixelData PixelData = CharData->lines[row];
|
|
|
|
for (int col = CharData->width; col-- > 0; ) {
|
2004-06-05 11:42:08 +02:00
|
|
|
if (ColorBg != clrTransparent || (PixelData & 1))
|
|
|
|
SetIndex(x + col, y + row, (PixelData & 1) ? fg : bg);
|
2004-05-16 10:35:36 +02:00
|
|
|
PixelData >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
x += CharData->width;
|
|
|
|
if (x > width - 1)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void cBitmap::DrawRectangle(int x1, int y1, int x2, int y2, tColor Color)
|
|
|
|
{
|
|
|
|
if (bitmap && Intersects(x1, y1, x2, y2)) {
|
2004-05-28 15:33:22 +02:00
|
|
|
if (Covers(x1, y1, x2, y2))
|
|
|
|
Reset();
|
2004-05-16 10:35:36 +02:00
|
|
|
x1 -= x0;
|
|
|
|
y1 -= y0;
|
|
|
|
x2 -= x0;
|
|
|
|
y2 -= y0;
|
|
|
|
x1 = max(x1, 0);
|
|
|
|
y1 = max(y1, 0);
|
|
|
|
x2 = min(x2, width - 1);
|
|
|
|
y2 = min(y2, height - 1);
|
|
|
|
tIndex c = Index(Color);
|
|
|
|
for (int y = y1; y <= y2; y++)
|
|
|
|
for (int x = x1; x <= x2; x++)
|
|
|
|
SetIndex(x, y, c);
|
|
|
|
}
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cBitmap::DrawEllipse(int x1, int y1, int x2, int y2, tColor Color, int Quadrants)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
if (!Intersects(x1, y1, x2, y2))
|
|
|
|
return;
|
|
|
|
// Algorithm based on http://homepage.smc.edu/kennedy_john/BELIPSE.PDF
|
|
|
|
int rx = x2 - x1;
|
|
|
|
int ry = y2 - y1;
|
|
|
|
int cx = (x1 + x2) / 2;
|
|
|
|
int cy = (y1 + y2) / 2;
|
|
|
|
switch (abs(Quadrants)) {
|
|
|
|
case 0: rx /= 2; ry /= 2; break;
|
|
|
|
case 1: cx = x1; cy = y2; break;
|
|
|
|
case 2: cx = x2; cy = y2; break;
|
|
|
|
case 3: cx = x2; cy = y1; break;
|
|
|
|
case 4: cx = x1; cy = y1; break;
|
|
|
|
case 5: cx = x1; ry /= 2; break;
|
|
|
|
case 6: cy = y2; rx /= 2; break;
|
|
|
|
case 7: cx = x2; ry /= 2; break;
|
|
|
|
case 8: cy = y1; rx /= 2; break;
|
|
|
|
}
|
|
|
|
int TwoASquare = 2 * rx * rx;
|
|
|
|
int TwoBSquare = 2 * ry * ry;
|
|
|
|
int x = rx;
|
|
|
|
int y = 0;
|
|
|
|
int XChange = ry * ry * (1 - 2 * rx);
|
|
|
|
int YChange = rx * rx;
|
|
|
|
int EllipseError = 0;
|
|
|
|
int StoppingX = TwoBSquare * rx;
|
|
|
|
int StoppingY = 0;
|
|
|
|
while (StoppingX >= StoppingY) {
|
|
|
|
switch (Quadrants) {
|
|
|
|
case 5: DrawRectangle(cx, cy + y, cx + x, cy + y, Color); // no break
|
|
|
|
case 1: DrawRectangle(cx, cy - y, cx + x, cy - y, Color); break;
|
|
|
|
case 7: DrawRectangle(cx - x, cy + y, cx, cy + y, Color); // no break
|
|
|
|
case 2: DrawRectangle(cx - x, cy - y, cx, cy - y, Color); break;
|
|
|
|
case 3: DrawRectangle(cx - x, cy + y, cx, cy + y, Color); break;
|
|
|
|
case 4: DrawRectangle(cx, cy + y, cx + x, cy + y, Color); break;
|
|
|
|
case 0:
|
|
|
|
case 6: DrawRectangle(cx - x, cy - y, cx + x, cy - y, Color); if (Quadrants == 6) break;
|
|
|
|
case 8: DrawRectangle(cx - x, cy + y, cx + x, cy + y, Color); break;
|
|
|
|
case -1: DrawRectangle(cx + x, cy - y, x2, cy - y, Color); break;
|
|
|
|
case -2: DrawRectangle(x1, cy - y, cx - x, cy - y, Color); break;
|
|
|
|
case -3: DrawRectangle(x1, cy + y, cx - x, cy + y, Color); break;
|
|
|
|
case -4: DrawRectangle(cx + x, cy + y, x2, cy + y, Color); break;
|
|
|
|
}
|
|
|
|
y++;
|
|
|
|
StoppingY += TwoASquare;
|
|
|
|
EllipseError += YChange;
|
|
|
|
YChange += TwoASquare;
|
|
|
|
if (2 * EllipseError + XChange > 0) {
|
|
|
|
x--;
|
|
|
|
StoppingX -= TwoBSquare;
|
|
|
|
EllipseError += XChange;
|
|
|
|
XChange += TwoBSquare;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
x = 0;
|
|
|
|
y = ry;
|
|
|
|
XChange = ry * ry;
|
|
|
|
YChange = rx * rx * (1 - 2 * ry);
|
|
|
|
EllipseError = 0;
|
|
|
|
StoppingX = 0;
|
|
|
|
StoppingY = TwoASquare * ry;
|
|
|
|
while (StoppingX <= StoppingY) {
|
|
|
|
switch (Quadrants) {
|
|
|
|
case 5: DrawRectangle(cx, cy + y, cx + x, cy + y, Color); // no break
|
|
|
|
case 1: DrawRectangle(cx, cy - y, cx + x, cy - y, Color); break;
|
|
|
|
case 7: DrawRectangle(cx - x, cy + y, cx, cy + y, Color); // no break
|
|
|
|
case 2: DrawRectangle(cx - x, cy - y, cx, cy - y, Color); break;
|
|
|
|
case 3: DrawRectangle(cx - x, cy + y, cx, cy + y, Color); break;
|
|
|
|
case 4: DrawRectangle(cx, cy + y, cx + x, cy + y, Color); break;
|
|
|
|
case 0:
|
|
|
|
case 6: DrawRectangle(cx - x, cy - y, cx + x, cy - y, Color); if (Quadrants == 6) break;
|
|
|
|
case 8: DrawRectangle(cx - x, cy + y, cx + x, cy + y, Color); break;
|
|
|
|
case -1: DrawRectangle(cx + x, cy - y, x2, cy - y, Color); break;
|
|
|
|
case -2: DrawRectangle(x1, cy - y, cx - x, cy - y, Color); break;
|
|
|
|
case -3: DrawRectangle(x1, cy + y, cx - x, cy + y, Color); break;
|
|
|
|
case -4: DrawRectangle(cx + x, cy + y, x2, cy + y, Color); break;
|
|
|
|
}
|
|
|
|
x++;
|
|
|
|
StoppingX += TwoBSquare;
|
|
|
|
EllipseError += XChange;
|
|
|
|
XChange += TwoBSquare;
|
|
|
|
if (2 * EllipseError + YChange > 0) {
|
|
|
|
y--;
|
|
|
|
StoppingY -= TwoASquare;
|
|
|
|
EllipseError += YChange;
|
|
|
|
YChange += TwoASquare;
|
|
|
|
}
|
|
|
|
}
|
2000-09-09 14:57:43 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cBitmap::DrawSlope(int x1, int y1, int x2, int y2, tColor Color, int Type)
|
2000-09-09 14:57:43 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
// TODO This is just a quick and dirty implementation of a slope drawing
|
|
|
|
// machanism. If somebody can come up with a better solution, let's have it!
|
|
|
|
if (!Intersects(x1, y1, x2, y2))
|
|
|
|
return;
|
|
|
|
bool upper = Type & 0x01;
|
|
|
|
bool falling = Type & 0x02;
|
|
|
|
bool vertical = Type & 0x04;
|
|
|
|
if (vertical) {
|
|
|
|
for (int y = y1; y <= y2; y++) {
|
|
|
|
double c = cos((y - y1) * M_PI / (y2 - y1 + 1));
|
|
|
|
if (falling)
|
|
|
|
c = -c;
|
|
|
|
int x = int((x2 - x1 + 1) * c / 2);
|
|
|
|
if (upper && !falling || !upper && falling)
|
|
|
|
DrawRectangle(x1, y, (x1 + x2) / 2 + x, y, Color);
|
|
|
|
else
|
|
|
|
DrawRectangle((x1 + x2) / 2 + x, y, x2, y, Color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
for (int x = x1; x <= x2; x++) {
|
|
|
|
double c = cos((x - x1) * M_PI / (x2 - x1 + 1));
|
|
|
|
if (falling)
|
|
|
|
c = -c;
|
|
|
|
int y = int((y2 - y1 + 1) * c / 2);
|
|
|
|
if (upper)
|
|
|
|
DrawRectangle(x, y1, x, (y1 + y2) / 2 + y, Color);
|
|
|
|
else
|
|
|
|
DrawRectangle(x, (y1 + y2) / 2 + y, x, y2, Color);
|
|
|
|
}
|
2000-09-09 14:57:43 +02:00
|
|
|
}
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
const tIndex *cBitmap::Data(int x, int y)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
return &bitmap[y * width + x];
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
// --- cOsd ------------------------------------------------------------------
|
|
|
|
|
|
|
|
bool cOsd::isOpen = false;
|
2000-02-19 13:36:48 +01:00
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
cOsd::cOsd(int Left, int Top)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
if (isOpen)
|
|
|
|
esyslog("ERROR: OSD opened without closing previous OSD!");
|
|
|
|
savedRegion = NULL;
|
|
|
|
numBitmaps = 0;
|
|
|
|
left = Left;
|
|
|
|
top = Top;
|
|
|
|
width = height = 0;
|
|
|
|
isOpen = true;
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
cOsd::~cOsd()
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
for (int i = 0; i < numBitmaps; i++)
|
|
|
|
delete bitmaps[i];
|
|
|
|
delete savedRegion;
|
|
|
|
isOpen = false;
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
cBitmap *cOsd::GetBitmap(int Area)
|
2002-03-16 10:03:04 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
return Area < numBitmaps ? bitmaps[Area] : NULL;
|
2002-03-16 10:03:04 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
eOsdError cOsd::CanHandleAreas(const tArea *Areas, int NumAreas)
|
2002-03-16 10:03:04 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
for (int i = 0; i < NumAreas; i++) {
|
|
|
|
for (int j = i + 1; j < NumAreas; j++) {
|
|
|
|
if (Areas[i].Intersects(Areas[j]))
|
|
|
|
return oeAreasOverlap;
|
|
|
|
if (Areas[i].x1 > Areas[i].x2 || Areas[i].y1 > Areas[i].y2 || Areas[i].x1 < 0 || Areas[i].y1 < 0)
|
|
|
|
return oeWrongAlignment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return oeOk;
|
2002-03-16 10:03:04 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
eOsdError cOsd::SetAreas(const tArea *Areas, int NumAreas)
|
2000-03-11 11:22:37 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
eOsdError Result = oeUnknown;
|
|
|
|
if (numBitmaps == 0) {
|
|
|
|
Result = CanHandleAreas(Areas, NumAreas);
|
|
|
|
if (Result == oeOk) {
|
|
|
|
width = height = 0;
|
|
|
|
for (int i = 0; i < NumAreas; i++) {
|
|
|
|
bitmaps[numBitmaps++] = new cBitmap(Areas[i].Width(), Areas[i].Height(), Areas[i].bpp, Areas[i].x1, Areas[i].y1);
|
2004-06-12 14:04:01 +02:00
|
|
|
width = max(width, Areas[i].x2 + 1);
|
|
|
|
height = max(height, Areas[i].y2 + 1);
|
2004-05-16 10:35:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (Result != oeOk)
|
|
|
|
esyslog("ERROR: cOsd::SetAreas returned %d\n", Result);
|
|
|
|
return Result;
|
2000-03-11 11:22:37 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cOsd::SaveRegion(int x1, int y1, int x2, int y2)
|
2000-10-29 13:17:22 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
delete savedRegion;
|
|
|
|
savedRegion = new cBitmap(x2 - x1 + 1, y2 - y1 + 1, 8, x1, y1);
|
|
|
|
for (int i = 0; i < numBitmaps; i++)
|
|
|
|
savedRegion->DrawBitmap(bitmaps[i]->X0(), bitmaps[i]->Y0(), *bitmaps[i]);
|
2000-10-29 13:17:22 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cOsd::RestoreRegion(void)
|
2000-03-11 11:22:37 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
if (savedRegion) {
|
|
|
|
DrawBitmap(savedRegion->X0(), savedRegion->Y0(), *savedRegion);
|
|
|
|
delete savedRegion;
|
|
|
|
savedRegion = NULL;
|
|
|
|
}
|
2000-03-11 11:22:37 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
eOsdError cOsd::SetPalette(const cPalette &Palette, int Area)
|
2000-03-11 11:22:37 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
if (Area < numBitmaps)
|
|
|
|
bitmaps[Area]->Take(Palette);
|
|
|
|
return oeUnknown;
|
2000-03-11 11:22:37 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cOsd::DrawPixel(int x, int y, tColor Color)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
for (int i = 0; i < numBitmaps; i++)
|
|
|
|
bitmaps[i]->DrawPixel(x, y, Color);
|
2002-05-12 14:46:46 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cOsd::DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg, tColor ColorBg)
|
2002-05-12 14:46:46 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
for (int i = 0; i < numBitmaps; i++)
|
|
|
|
bitmaps[i]->DrawBitmap(x, y, Bitmap, ColorFg, ColorBg);
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cOsd::DrawText(int x, int y, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width, int Height, int Alignment)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
for (int i = 0; i < numBitmaps; i++)
|
|
|
|
bitmaps[i]->DrawText(x, y, s, ColorFg, ColorBg, Font, Width, Height, Alignment);
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cOsd::DrawRectangle(int x1, int y1, int x2, int y2, tColor Color)
|
2002-01-20 14:05:28 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
for (int i = 0; i < numBitmaps; i++)
|
|
|
|
bitmaps[i]->DrawRectangle(x1, y1, x2, y2, Color);
|
2002-01-20 14:05:28 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cOsd::DrawEllipse(int x1, int y1, int x2, int y2, tColor Color, int Quadrants)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
for (int i = 0; i < numBitmaps; i++)
|
|
|
|
bitmaps[i]->DrawEllipse(x1, y1, x2, y2, Color, Quadrants);
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cOsd::DrawSlope(int x1, int y1, int x2, int y2, tColor Color, int Type)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
for (int i = 0; i < numBitmaps; i++)
|
|
|
|
bitmaps[i]->DrawSlope(x1, y1, x2, y2, Color, Type);
|
2000-09-09 14:57:43 +02:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cOsd::Flush(void)
|
2000-11-12 16:48:50 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
// --- cOsdProvider ----------------------------------------------------------
|
|
|
|
|
|
|
|
cOsdProvider *cOsdProvider::osdProvider = NULL;
|
|
|
|
|
|
|
|
cOsdProvider::cOsdProvider(void)
|
2000-09-09 14:57:43 +02:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
delete osdProvider;
|
|
|
|
osdProvider = this;
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
cOsdProvider::~cOsdProvider()
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
osdProvider = NULL;
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
cOsd *cOsdProvider::NewOsd(int Left, int Top)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-06-12 13:30:11 +02:00
|
|
|
if (cOsd::IsOpen()) {
|
|
|
|
esyslog("ERROR: attempt to open OSD while it is already open!");
|
|
|
|
return NULL;
|
|
|
|
}
|
2004-05-16 10:35:36 +02:00
|
|
|
if (osdProvider)
|
|
|
|
return osdProvider->CreateOsd(Left, Top);
|
|
|
|
esyslog("ERROR: no OSD provider available - using dummy OSD!");
|
|
|
|
return new cOsd(Left, Top); // create a dummy cOsd, so that access won't result in a segfault
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cOsdProvider::Shutdown(void)
|
2001-02-03 14:35:28 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
delete osdProvider;
|
|
|
|
osdProvider = NULL;
|
2001-02-03 14:35:28 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
// --- cTextScroller ---------------------------------------------------------
|
|
|
|
|
|
|
|
cTextScroller::cTextScroller(void)
|
2001-02-03 14:35:28 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
osd = NULL;
|
|
|
|
left = top = width = height = 0;
|
|
|
|
font = NULL;
|
|
|
|
colorFg = 0;
|
|
|
|
colorBg = 0;
|
|
|
|
offset = 0;
|
|
|
|
shown = 0;
|
2001-02-03 14:35:28 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
cTextScroller::cTextScroller(cOsd *Osd, int Left, int Top, int Width, int Height, const char *Text, const cFont *Font, tColor ColorFg, tColor ColorBg)
|
2000-03-11 11:22:37 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
Set(Osd, Left, Top, Width, Height, Text, Font, ColorFg, ColorBg);
|
2000-03-11 11:22:37 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cTextScroller::Set(cOsd *Osd, int Left, int Top, int Width, int Height, const char *Text, const cFont *Font, tColor ColorFg, tColor ColorBg)
|
2001-02-03 15:28:49 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
osd = Osd;
|
|
|
|
left = Left;
|
|
|
|
top = Top;
|
|
|
|
width = Width;
|
|
|
|
height = Height;
|
|
|
|
font = Font;
|
|
|
|
colorFg = ColorFg;
|
|
|
|
colorBg = ColorBg;
|
|
|
|
offset = 0;
|
|
|
|
textWrapper.Set(Text, Font, Width);
|
|
|
|
shown = min(Total(), height / font->Height());
|
|
|
|
height = shown * font->Height(); // sets height to the actually used height, which may be less than Height
|
|
|
|
DrawText();
|
2001-02-03 15:28:49 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cTextScroller::Reset(void)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
osd = NULL; // just makes sure it won't draw anything
|
2002-11-10 15:50:21 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cTextScroller::DrawText(void)
|
2002-11-10 15:50:21 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
if (osd) {
|
|
|
|
for (int i = 0; i < shown; i++)
|
|
|
|
osd->DrawText(left, top + i * font->Height(), textWrapper.GetLine(offset + i), colorFg, colorBg, font, width);
|
|
|
|
}
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
|
2004-05-16 10:35:36 +02:00
|
|
|
void cTextScroller::Scroll(bool Up, bool Page)
|
2000-02-19 13:36:48 +01:00
|
|
|
{
|
2004-05-16 10:35:36 +02:00
|
|
|
if (Up) {
|
|
|
|
if (CanScrollUp()) {
|
|
|
|
offset -= Page ? shown : 1;
|
|
|
|
if (offset < 0)
|
|
|
|
offset = 0;
|
|
|
|
DrawText();
|
|
|
|
}
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
2004-05-16 10:35:36 +02:00
|
|
|
else {
|
|
|
|
if (CanScrollDown()) {
|
|
|
|
offset += Page ? shown : 1;
|
|
|
|
if (offset + shown > Total())
|
|
|
|
offset = Total() - shown;
|
|
|
|
DrawText();
|
|
|
|
}
|
2000-02-19 13:36:48 +01:00
|
|
|
}
|
|
|
|
}
|