mirror of
https://github.com/VDR4Arch/vdr.git
synced 2023-10-10 13:36:52 +02:00
416 lines
11 KiB
C
416 lines
11 KiB
C
/*
|
|
* skins.c: The optical appearance of the OSD
|
|
*
|
|
* See the main source file 'vdr.c' for copyright information and
|
|
* how to reach the author.
|
|
*
|
|
* $Id: skins.c 4.2 2019/05/29 16:43:09 kls Exp $
|
|
*/
|
|
|
|
#include "skins.h"
|
|
#include "interface.h"
|
|
#include "status.h"
|
|
|
|
// --- cSkinQueuedMessage ----------------------------------------------------
|
|
|
|
class cSkinQueuedMessage : public cListObject {
|
|
friend class cSkins;
|
|
private:
|
|
eMessageType type;
|
|
char *message;
|
|
int seconds;
|
|
int timeout;
|
|
tThreadId threadId;
|
|
eKeys key;
|
|
int state;
|
|
cMutex mutex;
|
|
cCondVar condVar;
|
|
public:
|
|
cSkinQueuedMessage(eMessageType Type, const char *s, int Seconds, int Timeout);
|
|
virtual ~cSkinQueuedMessage();
|
|
};
|
|
|
|
cSkinQueuedMessage::cSkinQueuedMessage(eMessageType Type, const char *s, int Seconds, int Timeout)
|
|
{
|
|
type = Type;
|
|
message = s ? strdup(s) : NULL;
|
|
seconds = Seconds;
|
|
timeout = Timeout;
|
|
threadId = cThread::ThreadId();
|
|
key = kNone;
|
|
state = 0; // waiting
|
|
}
|
|
|
|
cSkinQueuedMessage::~cSkinQueuedMessage()
|
|
{
|
|
free(message);
|
|
}
|
|
|
|
cList<cSkinQueuedMessage> SkinQueuedMessages;
|
|
|
|
// --- cSkinDisplay ----------------------------------------------------------
|
|
|
|
cSkinDisplay *cSkinDisplay::current = NULL;
|
|
|
|
cSkinDisplay::cSkinDisplay(void)
|
|
{
|
|
current = this;
|
|
editableWidth = 100; //XXX
|
|
}
|
|
|
|
cSkinDisplay::~cSkinDisplay()
|
|
{
|
|
current = NULL;
|
|
}
|
|
|
|
// --- cSkinDisplayChannel ---------------------------------------------------
|
|
|
|
cSkinDisplayChannel::cSkinDisplayChannel(void)
|
|
{
|
|
positioner = NULL;
|
|
}
|
|
|
|
void cSkinDisplayChannel::SetPositioner(const cPositioner *Positioner)
|
|
{
|
|
if (positioner && Positioner != positioner)
|
|
SetMessage(mtInfo, NULL);
|
|
positioner = Positioner;
|
|
if (positioner)
|
|
SetMessage(mtInfo, cString::sprintf(tr("Moving dish to %.1f..."), double(positioner->TargetLongitude()) / 10));
|
|
}
|
|
|
|
// --- cSkinDisplayMenu ------------------------------------------------------
|
|
|
|
cSkinDisplayMenu::cSkinDisplayMenu(void)
|
|
{
|
|
menuCategory = mcUndefined;
|
|
SetTabs(0);
|
|
}
|
|
|
|
void cSkinDisplayMenu::SetMenuCategory(eMenuCategory MenuCategory)
|
|
{
|
|
menuCategory = MenuCategory;
|
|
}
|
|
|
|
void cSkinDisplayMenu::SetTabs(int Tab1, int Tab2, int Tab3, int Tab4, int Tab5)
|
|
{
|
|
tabs[0] = 0;
|
|
tabs[1] = Tab1 ? tabs[0] + Tab1 : 0;
|
|
tabs[2] = Tab2 ? tabs[1] + Tab2 : 0;
|
|
tabs[3] = Tab3 ? tabs[2] + Tab3 : 0;
|
|
tabs[4] = Tab4 ? tabs[3] + Tab4 : 0;
|
|
tabs[5] = Tab5 ? tabs[4] + Tab5 : 0;
|
|
for (int i = 1; i < MaxTabs; i++)
|
|
tabs[i] *= AvgCharWidth();
|
|
}
|
|
|
|
void cSkinDisplayMenu::Scroll(bool Up, bool Page)
|
|
{
|
|
textScroller.Scroll(Up, Page);
|
|
}
|
|
|
|
const char *cSkinDisplayMenu::GetTabbedText(const char *s, int Tab)
|
|
{
|
|
if (!s)
|
|
return NULL;
|
|
static char buffer[1000];
|
|
const char *a = s;
|
|
const char *b = strchrnul(a, '\t');
|
|
while (*b && Tab-- > 0) {
|
|
a = b + 1;
|
|
b = strchrnul(a, '\t');
|
|
}
|
|
if (!*b)
|
|
return (Tab <= 0) ? a : NULL;
|
|
unsigned int n = b - a;
|
|
if (n >= sizeof(buffer))
|
|
n = sizeof(buffer) - 1;
|
|
strncpy(buffer, a, n);
|
|
buffer[n] = 0;
|
|
return buffer;
|
|
}
|
|
|
|
void cSkinDisplayMenu::SetScrollbar(int Total, int Offset)
|
|
{
|
|
}
|
|
|
|
int cSkinDisplayMenu::GetTextAreaWidth(void) const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const cFont *cSkinDisplayMenu::GetTextAreaFont(bool) const
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// --- cSkinDisplayReplay::cProgressBar --------------------------------------
|
|
|
|
cSkinDisplayReplay::cProgressBar::cProgressBar(int Width, int Height, int Current, int Total, const cMarks *Marks, tColor ColorSeen, tColor ColorRest, tColor ColorSelected, tColor ColorMark, tColor ColorCurrent)
|
|
:cBitmap(Width, Height, 2)
|
|
{
|
|
total = Total;
|
|
if (total > 0) {
|
|
int p = Pos(Current);
|
|
DrawRectangle(0, 0, p, Height - 1, ColorSeen);
|
|
DrawRectangle(p + 1, 0, Width - 1, Height - 1, ColorRest);
|
|
if (Marks) {
|
|
bool Start = true;
|
|
for (const cMark *m = Marks->First(); m; m = Marks->Next(m)) {
|
|
int p1 = Pos(m->Position());
|
|
if (Start) {
|
|
const cMark *m2 = Marks->Next(m);
|
|
int p2 = Pos(m2 ? m2->Position() : total);
|
|
int h = Height / 3;
|
|
DrawRectangle(p1, h, p2, Height - h, ColorSelected);
|
|
}
|
|
Mark(p1, Start, m->Position() == Current, ColorMark, ColorCurrent);
|
|
Start = !Start;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void cSkinDisplayReplay::cProgressBar::Mark(int x, bool Start, bool Current, tColor ColorMark, tColor ColorCurrent)
|
|
{
|
|
DrawRectangle(x, 0, x, Height() - 1, ColorMark);
|
|
const int d = Height() / (Current ? 3 : 9);
|
|
for (int i = 0; i < d; i++) {
|
|
int h = Start ? i : Height() - 1 - i;
|
|
DrawRectangle(x - d + i, h, x + d - i, h, Current ? ColorCurrent : ColorMark);
|
|
}
|
|
}
|
|
|
|
// --- cSkinDisplayReplay ----------------------------------------------------
|
|
|
|
cSkinDisplayReplay::cSkinDisplayReplay(void)
|
|
{
|
|
marks = NULL;
|
|
}
|
|
|
|
void cSkinDisplayReplay::SetRecording(const cRecording *Recording)
|
|
{
|
|
SetTitle(Recording->Title());
|
|
}
|
|
|
|
void cSkinDisplayReplay::SetMarks(const cMarks *Marks)
|
|
{
|
|
marks = Marks;
|
|
}
|
|
|
|
// --- cSkin -----------------------------------------------------------------
|
|
|
|
cSkin::cSkin(const char *Name, cTheme *Theme)
|
|
{
|
|
name = strdup(Name);
|
|
theme = Theme;
|
|
if (theme)
|
|
cThemes::Save(name, theme);
|
|
Skins.Add(this);
|
|
}
|
|
|
|
cSkin::~cSkin()
|
|
{
|
|
free(name);
|
|
}
|
|
|
|
// --- cSkins ----------------------------------------------------------------
|
|
|
|
cSkins Skins;
|
|
|
|
cSkins::cSkins(void)
|
|
{
|
|
displayMessage = NULL;
|
|
}
|
|
|
|
cSkins::~cSkins()
|
|
{
|
|
delete displayMessage;
|
|
}
|
|
|
|
bool cSkins::SetCurrent(const char *Name)
|
|
{
|
|
if (Name) {
|
|
for (cSkin *Skin = First(); Skin; Skin = Next(Skin)) {
|
|
if (strcmp(Skin->Name(), Name) == 0) {
|
|
isyslog("setting current skin to \"%s\"", Name);
|
|
current = Skin;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
current = First();
|
|
if (current)
|
|
isyslog("skin \"%s\" not available - using \"%s\" instead", Name, current->Name());
|
|
else
|
|
esyslog("ERROR: no skin available");
|
|
return current != NULL;
|
|
}
|
|
|
|
eKeys cSkins::Message(eMessageType Type, const char *s, int Seconds)
|
|
{
|
|
if (!cThread::IsMainThread()) {
|
|
if (Type != mtStatus)
|
|
QueueMessage(Type, s, Seconds);
|
|
else
|
|
dsyslog("cSkins::Message(%d, \"%s\", %d) called from background thread - ignored! (Use cSkins::QueueMessage() instead)", Type, s, Seconds);
|
|
return kNone;
|
|
}
|
|
switch (Type) {
|
|
case mtInfo: isyslog("info: %s", s); break;
|
|
case mtWarning: isyslog("warning: %s", s); break;
|
|
case mtError: esyslog("ERROR: %s", s); break;
|
|
default: ;
|
|
}
|
|
if (!Current())
|
|
return kNone;
|
|
if (!cSkinDisplay::Current()) {
|
|
if (displayMessage)
|
|
delete displayMessage;
|
|
displayMessage = Current()->DisplayMessage();
|
|
}
|
|
cSkinDisplay::Current()->SetMessage(Type, s);
|
|
cSkinDisplay::Current()->Flush();
|
|
cStatus::MsgOsdStatusMessage(s);
|
|
eKeys k = kNone;
|
|
if (Type != mtStatus) {
|
|
k = Interface->Wait(Seconds);
|
|
if (displayMessage) {
|
|
delete displayMessage;
|
|
displayMessage = NULL;
|
|
cStatus::MsgOsdClear();
|
|
}
|
|
else {
|
|
cSkinDisplay::Current()->SetMessage(Type, NULL);
|
|
cStatus::MsgOsdStatusMessage(NULL);
|
|
}
|
|
}
|
|
else if (!s && displayMessage) {
|
|
delete displayMessage;
|
|
displayMessage = NULL;
|
|
cStatus::MsgOsdClear();
|
|
}
|
|
return k;
|
|
}
|
|
|
|
int cSkins::QueueMessage(eMessageType Type, const char *s, int Seconds, int Timeout)
|
|
{
|
|
if (Type == mtStatus) {
|
|
dsyslog("cSkins::QueueMessage() called with mtStatus - ignored!");
|
|
return kNone;
|
|
}
|
|
if (isempty(s)) {
|
|
if (!cThread::IsMainThread()) {
|
|
queueMessageMutex.Lock();
|
|
for (cSkinQueuedMessage *m = SkinQueuedMessages.Last(); m; m = SkinQueuedMessages.Prev(m)) {
|
|
if (m->threadId == cThread::ThreadId() && m->state == 0)
|
|
m->state = 2; // done
|
|
}
|
|
queueMessageMutex.Unlock();
|
|
}
|
|
else
|
|
dsyslog("cSkins::QueueMessage() called with empty message from main thread - ignored!");
|
|
return kNone;
|
|
}
|
|
int k = kNone;
|
|
if (Timeout > 0) {
|
|
if (cThread::IsMainThread()) {
|
|
dsyslog("cSkins::QueueMessage() called from main thread with Timeout = %d - ignored!", Timeout);
|
|
return k;
|
|
}
|
|
cSkinQueuedMessage *m = new cSkinQueuedMessage(Type, s, Seconds, Timeout);
|
|
queueMessageMutex.Lock();
|
|
SkinQueuedMessages.Add(m);
|
|
m->mutex.Lock();
|
|
queueMessageMutex.Unlock();
|
|
if (m->condVar.TimedWait(m->mutex, Timeout * 1000))
|
|
k = m->key;
|
|
else
|
|
k = -1; // timeout, nothing has been displayed
|
|
m->state = 2; // done
|
|
m->mutex.Unlock();
|
|
}
|
|
else {
|
|
queueMessageMutex.Lock();
|
|
// Check if there is a waiting message w/o timeout for this thread:
|
|
if (Timeout == -1) {
|
|
for (cSkinQueuedMessage *m = SkinQueuedMessages.Last(); m; m = SkinQueuedMessages.Prev(m)) {
|
|
if (m->threadId == cThread::ThreadId()) {
|
|
if (m->state == 0 && m->timeout == -1)
|
|
m->state = 2; // done
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Add the new message:
|
|
SkinQueuedMessages.Add(new cSkinQueuedMessage(Type, s, Seconds, Timeout));
|
|
queueMessageMutex.Unlock();
|
|
}
|
|
return k;
|
|
}
|
|
|
|
void cSkins::ProcessQueuedMessages(void)
|
|
{
|
|
if (!cThread::IsMainThread()) {
|
|
dsyslog("cSkins::ProcessQueuedMessages() called from background thread - ignored!");
|
|
return;
|
|
}
|
|
// Check whether there is a cSkinDisplay object (if any) that implements SetMessage():
|
|
if (cSkinDisplay *sd = cSkinDisplay::Current()) {
|
|
if (!(dynamic_cast<cSkinDisplayChannel *>(sd) ||
|
|
dynamic_cast<cSkinDisplayMenu *>(sd) ||
|
|
dynamic_cast<cSkinDisplayReplay *>(sd) ||
|
|
dynamic_cast<cSkinDisplayMessage *>(sd)))
|
|
return;
|
|
}
|
|
cSkinQueuedMessage *msg = NULL;
|
|
// Get the first waiting message:
|
|
queueMessageMutex.Lock();
|
|
for (cSkinQueuedMessage *m = SkinQueuedMessages.First(); m; m = SkinQueuedMessages.Next(m)) {
|
|
if (m->state == 0) { // waiting
|
|
m->state = 1; // active
|
|
msg = m;
|
|
break;
|
|
}
|
|
}
|
|
queueMessageMutex.Unlock();
|
|
// Display the message:
|
|
if (msg) {
|
|
msg->mutex.Lock();
|
|
if (msg->state == 1) { // might have changed since we got it
|
|
msg->key = Skins.Message(msg->type, msg->message, msg->seconds);
|
|
if (msg->timeout == 0)
|
|
msg->state = 2; // done
|
|
else
|
|
msg->condVar.Broadcast();
|
|
}
|
|
msg->mutex.Unlock();
|
|
}
|
|
// Remove done messages from the queue:
|
|
queueMessageMutex.Lock();
|
|
for (;;) {
|
|
cSkinQueuedMessage *m = SkinQueuedMessages.First();
|
|
if (m && m->state == 2) { // done
|
|
SkinQueuedMessages.Del(m);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
queueMessageMutex.Unlock();
|
|
}
|
|
|
|
void cSkins::Flush(void)
|
|
{
|
|
if (cSkinDisplay::Current())
|
|
cSkinDisplay::Current()->Flush();
|
|
}
|
|
|
|
void cSkins::Clear(void)
|
|
{
|
|
if (displayMessage) {
|
|
delete displayMessage;
|
|
displayMessage = NULL;
|
|
}
|
|
cList<cSkin>::Clear();
|
|
}
|