Entwicklung - Eigenständiges Skin Plugin erstellen

Aus VDR Wiki

Wechseln zu: Navigation, Suche

HIER GIBT ES NOCH EIN PAAR SACHEN ZUM AUSFÜLLEN. BITTE FÜLLT ES AUS WENN DU ES KANNST


Inhaltsverzeichnis

Einleitung

Ich möchte hier beschreiben wie man ein Skin-Plugin erstellt. Dabei ist nicht das Erstellen einer XML basierenden Beschreibung für das Text2Skin Plugin gemeint. Hier wird vielmehr beschrieben, wie man ein eigenes Plugin zur Darstellung für Skins schreibt.

Vor und Nachteile

Vorteile von einem Skin Plugin gegenüber dem Text2Skin Plugin:

  1. Es gibt einem mehr Möglichkeiten
  2. Totale Kontrolle über das Plugin
  3. etwas performanter als die xml Beschreibung

Nachteile:

  1. viel mehr Aufwand
  2. Fehleranfälliger, da viel mehr Code selber geschrieben werden muss

Grundgerüsst

Als erstes sollte man sich mit dem Skript "newplugin" ein neues Grundgerüst erstellen lassen.

./newplugin myskin

Somit erhält man folgenden Code. Ich habe es hier nur auf die wirklich nötigen Methoden reduziert:

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

#include <vdr/plugin.h>

static const char *VERSION        = "0.0.1";
static const char *DESCRIPTION    = "Enter description for 'myskin' plugin";
static const char *MAINMENUENTRY  = "Myskin";

class cPluginMyskin : public cPlugin {
private:
 // Add any member variables or functions you may need here.
public:
 cPluginMyskin(void);
 virtual ~cPluginMyskin();
 virtual const char *Version(void) { return VERSION; }
 virtual const char *Description(void) { return DESCRIPTION; }
 virtual bool Start(void);
 virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
 virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
 };

cPluginMyskin::cPluginMyskin(void)
{}

cPluginMyskin::~cPluginMyskin()
{}

bool cPluginMyskin::Start(void)
{
 new cSkinMySkin;
 return true;
}

VDRPLUGINCREATOR(cPluginMyskin); // Don't touch this!


Die Zeile new cSkinMySkin muss ergänzt werden.

Somit haben wir das Grundgerüst um ein Plugin zu erstellen.

Basis Skin Klasse

Kommen wir nun zu der Basis Skin Klasse die wir zuvor im Plugin-Grundgerüst eingefügt haben. Die Klasse sollte in etwas so aussehen:


class cSkinMySkin : public cSkin {
public:
 cSkinMySkin(void);
 virtual const char *Description(void);
 virtual cSkinDisplayMenu *DisplayMenu(void);
 virtual cSkinDisplayChannel *DisplayChannel(bool WithInfo);
 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);
 };

Wir sehen das hier wieder eine vielzahl von Klassen benötigt werden. Um zu verstehen, was welche Klasse macht, müssen wir erstmal ein paar Sachen definieren.

Was macht welche Klasse?

Es ist erstmal wichtig zu versehen was wo gemacht wird. Dazu will ich zeigen was es alles für Ansichten in VDR gibt.

cSkinDisplayMenu erzeugt
vergrößern
cSkinDisplayMenu erzeugt
cSkinDisplayChannel erzeugt
vergrößern
cSkinDisplayChannel erzeugt
cSkinDisplayReplay erzeugt
vergrößern
cSkinDisplayReplay erzeugt
cSkinDisplayVolume erzeugt
vergrößern
cSkinDisplayVolume erzeugt
Bild:Entwicklung-skins-04.jpg
cSkinDisplayTracks erzeugt
cSkinDisplayMessage erzeugt
vergrößern
cSkinDisplayMessage erzeugt

Implementierung der Skin Basis Klasse

Die Implementierung unserer Skin Basisklasse ist recht einfach, sie gibt nur die speziellen Klassen wieder:

cSkinMySkin::cSkinMySkin(void):cSkin("myskin", &::Theme)
{}

const char *cSkinMySkin::Description(void)
{
 return tr("My first Skin");
}

cSkinDisplayChannel *cSkinMySkin::DisplayChannel(bool WithInfo)
{
 return new cSkinMySkinDisplayChannel(WithInfo);
}

cSkinDisplayMenu *cSkinMySkin::DisplayMenu(void)
{
  return new cSkinMySkinDisplayMenu;
}

cSkinDisplayReplay *cSkinMySkin::DisplayReplay(bool ModeOnly)
{
 return new cSkinMySkinDisplayReplay(ModeOnly);
}

cSkinDisplayVolume *cSkinMySkin::DisplayVolume(void)
{
 return new cSkinMySkinDisplayVolume;
}

cSkinDisplayTracks *cSkinMySkin::DisplayTracks(const char *Title, int NumTracks, const char * const *Tracks)
{
 return new cSkinMySkinDisplayTracks(Title, NumTracks, Tracks);
}

cSkinDisplayMessage *cSkinMySkin::DisplayMessage(void)
{
 return new cSkinMySkinDisplayMessage;
}


Skin Klassen

Nun kommen wir zu den Speziellen Klassen:

SkinDisplayMenu

Beginnen wir mit dem Menü:

class cSkinMySkinDisplayMenu : public cSkinDisplayMenu {
private:
....
public:
 cSkinMySkinDisplayMenu(void);
 virtual ~cSkinMySkinDisplayMenu();
 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);
 };


Hier eine kurze Skizze, was was macht:

/*
* Base Background will be drawn in the Constructor
* 
*  x0
* y0------------------------------------------------------
*  |	MySkin		VDR - Main Menu			  | 		<-----DrawTitle()
*  |				   02.03.06 - 17:23	  | <---included in Flush() 
* y1------------------------------------------------------
*  |							  |				\
*  |	*Schedule	              			<-|-------DrawItem()             \
*  |	*Channels					  |				  \
*  |	*Timers						  |	                           \
*  |	*Recordings					  |			            \
*  |	*Plugin 1					  |	                             \Clear()/SetEvent()/SetRecordings()/SetText()
*  |	*Plugin 2					  |				    /	
*  |							  |			       	   /
*  |							  |			          /		
*  |					        	  |			         /	
*  |							  |		                /
*  |							  |			       /
*y2|---------------\			    		  |	
*  |	Any Logo    \	    (Channel not avalible)   <----|-----------SetMessage()
*  |		     \          			  |	
*y21------------------------------------------------------
*  |							  |
*  |    (RED)	  (GREEN)     (YELLOW)	  (BLUE)	  |			<------SetButtons()
*  | 							  |
*y3------------------------------------------------------	 
*/

Im Constructor werden Variablen initialisiert, die Zeichenfläche besorgt und statische Elemente gezeichnet.

cSkinMySkinDisplayMenu::cSkinMySkinDisplayMenu(void)
{
 //guter Zeitpunkt um ein paar Schriften zu definieren
 const cFont *font = cFont::GetFont(fontOsd);
 lineHeight = font->Height();

 //Es ist immer Sinnvoll ein paar Abstände und Punkte im Menü zu definieren
 x0 = 0;
 .....
 x7 = ...;
 y0 = 0;
 .....
 y7 = ...;

 //hier soll der Speicher für die Fläche auf der Gezeichnet wird reserviert werden.
 //weitere Infos dazu sind in der PLUGIN.html Datei von Klaus
 osd = cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop);
 tArea Areas[] = { { x0, y0, x7 - 1, y7 - 1, 4 } };
 if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk)
    osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
 else {
    tArea Areas[] = { { x0, y0, x7 - 1, y3 - 1, 2 },
                      { x0, y3, x3 - 1, y4 - 1, 1 },
                      { x3, y3, x4 - 1, y4 - 1, 2 },
                      { x4, y3, x7 - 1, y4 - 1, 2 },
                      { x0, y4, x7 - 1, y7 - 1, 4 }
                    };
    osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
    }

 //Statische Elemente sollten hier gezeichnet werden. Dazu gehören Hintergründe, Header und Fooder
 osd->DrawRectangle(x0, y0, x7 - 1, y7 - 1, Theme.Color(clrBackground));
 .......
 osd->DrawEllipse  (x6, y6, x7 - 1, y7 - 1, frameColor, 5);
}


Freigeben des OSDs

cSkinMySkinDisplayMenu::~cSkinMySkinDisplayMenu()
{
 delete osd;
} 


Wenn eine Tabelle oder ein Text im Menü über mehr als nur eine Seite geht, hat man hier die Möglichkeit, Pfeile an Rand zu zeichnen. Der Aufruf sollte an die Parent Klasse weiter gereicht werden.

void cSkinMySkinDisplayMenu::Scroll(bool Up, bool Page)
{
 //Reicht den Skroll Aufruf weiter
 cSkinDisplayMenu::Scroll(Up, Page);

 //Hier sollten Pfeile gezeichnet werden wenn es was zum Scrollen gibt
}


In Tabellen muss VDR wissen wie viele Einträge(Items) in eine Seite passen. Diese Methode wird nur einmal beim initialisieren aufgerufen.

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


Nicht statische Elemente können hier wieder gelöscht werden. Wird aufgerufen, wenn man von einem Menü zu einem anderen kommt. Man sollte nur den Bereich überschreiben, der nicht vom Constructor gezeichnet wurde.

void cSkinMyskinDisplayMenu::Clear(void)
{
 //hier wird z.B. das mittlere Feld mit der Hintergrundfarbe wieder überschieben.
 osd->DrawRectangle(x1, y3, x7 - 1, y4 - 1, Theme.Color(clrBackground));
}


In dem Header können unterschiedliche Informationen stehen. Neben dem Titel(name des Menüs) können noch weitere Informationen in den Header gepackt werden. Beliegt ist hier z.B. die Uhrzeit. Hier sollte jedoch nicht der Hintergrund des Headers gezeichnet werden. Dieses fällt in dem Bereich statische Elemente die vom Constructor gezeichnet werden.

void cSkinMyskinDisplayMenu::SetTitle(const char *Title)
{
 const cFont *font = cFont::GetFont(fontOsd);
 const char *VDR = " VDR";
 int w = font->Width(VDR);
 osd->DrawText(x3 + 5, y0, Title, Theme.Color(clrMenuTitle), frameColor, font, x4 - w - x3 - 5);
 osd->DrawText(x4 - w, y0, VDR, frameColor, clrBlack, font);
}


Hier können die 4 Farbiegen Buttons am unteren Rand gezeicht werden. Ist der Text für einen Button leer, so muss er z.B. nicht gezeichnet werden.

void cSkinMyskinDisplayMenu::SetButtons(const char *Red, const char *Green, const char *Yellow, const char *Blue)
{
}


Die Methode SetMessage ist in jeder SkinKlasse vorhanden, da sie die Möglichkeit gibt, Nachrichten an den User zu schicken, egal, in was für einem OSD er gerade ist. Der Programmierer hat damit die Möglichkeit die Nachricht an sein Menü anzupassen. Der Text ist meist sehr kurz und immer einzeilig. Der Type gibt wieder um was für eine Nachricht es sich handelt. Beispiele: "Kannal nicht verfügbar" "Wollen Sie die Aufzeichnung wirklich löschen?"

void cSkinMyskinDisplayMenu::SetMessage(eMessageType Type, const char *Text)
{
}


Wohl eine der umfangreichsten Methoden ist die SetItem. Diese Methode gibt an wie ein Listeneintrag aus sehen soll.

void cSkinMyskinDisplayMenu::SetItem(const char *Text, int Index, bool Current, bool Selectable)
{

 if (Current) {
    //Aktuell ausgewählter Eintrag
    }
 else {
    if(Selectable)
     //Eintrag könnte man auswählen
    else
     //Eintrag kann man nicht auswählen
    }
 //Text kann über Tabs formatiert werden.
 for (int i = 0; i < MaxTabs; i++) {
     const char *s = GetTabbedText(Text, i);
     if (s) {
        int xt = x3 + 5 + Tab(i);
        //Schreibt den Text an die errechnete Position
        osd->DrawText(xt, y, s, ColorFg, ColorBg, font, x4 - xt);
        }
     if (!Tab(i + 1))
        break;
     }
 SetEditableWidth(x4 - x3 - 5 - Tab(1));
}



Ab hier kommen ein paar Methoden die Sonderfunktionen haben.


Hier werden die EPG Daten auf dem OSD angezeigt. Menu->Schedule-> [select one] Enter

void cSkinMyskinDisplayMenu::SetEvent(const cEvent *Event)
{
}


Es werden Informationen über eine Aufzeichnung über den gesammten inneren Bereich angezeigt. Menu->Recordings-> [select one] BlueButton

void cSkinMyskinDisplayMenu::SetRecording(const cRecording *Recording)
{
}


Wenn ein langer Text im inneren Bereich angezeigt werden soll, dann wird dieses über diese Methode gemacht. Leider fällt mir dabei kein VDR eigenes OSD ein, wo es zum Einsatz kommt. Es wird z.B. vom Manuell Plugin verwendet. Der Text wird automatisch umgebrochen.

void cSkinMyskinDisplayMenu::SetText(const char *Text, bool FixedFont)
{
}


Wie das Flush von printf. Zeichnet die Buffer "Sofort" ins OSD. Hier sind auch Sachen gut aufgehoben, die bei gezeichneten OSD nachträglich hin und wieder geändert werden sollen. Ein Beispiel hierfür ist die Uhr.

void cSkinMyskinDisplayMenu::Flush(void)
{
}

SkinDisplayReplay

class cSkinMySkinDisplayReplay : public cSkinDisplayReplay {
private:

public:
 cSkinMySkinDisplayReplay(bool ModeOnly);
 virtual ~cSkinMySkinDisplayReplay();
 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);
 };


Hier gelten die selben Regeln wie für das Menü

cSkinMySkinDisplayReplay::cSkinMySkinDisplayReplay(bool ModeOnly)
{
}


Freigeben des OSDs

cSkinMySkinDisplayReplay::~cSkinMySkinDisplayReplay()
{
 delete osd;
}


Hier gelten die selben Regeln wie für das Menü

void cSkinMySkinDisplayReplay::SetTitle(const char *Title)
{
}


Abhängig davon wie die Aufnahme gerade wiedergegeben wird, können hier verschiedene Informationen angezeigt werden.

void cSkinMySkinDisplayReplay::SetMode(bool Play, bool Forward, int Speed)
{
 if (Speed < -1)
    Speed = -1;
 if (Speed > 3)
    Speed = 3;
 cBitmap bm(ReplaySymbols[Play][Forward][Speed + 1]);
 osd->DrawBitmap(x0 + (x1 - x0 - bm.Width()) / 2, y3 + (y4 - y3 - bm.Height()) / 2, bm, Theme.Color(clrReplayMode), frameColor);
}


Hier wird der Fortschrittsbalken angezeigt. Die Parameter geben an, wie weit die Wiedergabe gerade ist und wie lang die Aufzeichnung ist.

void cSkinMySkinDisplayReplay::SetProgress(int Current, int Total)
{
 cProgressBar pb(x4 - x3, y4 - y3, Current, Total, marks, Theme.Color(clrReplayProgressSeen), Theme.Color(clrReplayProgressRest), Theme.Color(clrReplayProgressSelected), Theme.Color(clrReplayProgressMark), Theme.Color(clrReplayProgressCurrent));
 osd->DrawBitmap(x3, y3, pb);
}


Die beiden Methoden SetCurrent und SetTotal geben in Textform wieder, wie weit die Aufzeichnung gerade ist.

void cSkinMySkinDisplayReplay::SetTotal(const char *Total)
{
}

void cSkinMySkinDisplayReplay::SetCurrent(const char *Current)
{
}


Hier muss ich selber gerade Passen.....

void cSkinMySkinDisplayReplay::SetJump(const char *Jump)
{
 osd->DrawText(x0 + (x4 - x0) / 4, y6, Jump, Theme.Color(clrReplayJump), frameColor, cFont::GetFont(fontSml), (x4 - x3) / 2, 0, taCenter);
}


Wie bereits beim Menü geschrieben hat jede Ansicht eine SetMessage, somit kann es immer an das aktuelle OSD angepasst werden.

void cSkinMySkinDisplayReplay::SetMessage(eMessageType Type, const char *Text)
{
}

Das selbe wie beim Menü

void cSkinMySkinDisplayReplay::Flush(void)
{
  osd->Flush();
}


SkinDisplayVolume

class cSkinMySkinDisplayVolume : public cSkinDisplayVolume {
private:

public:
 cSkinMySkinDisplayVolume(void);
 virtual ~cSkinMySkinDisplayVolume();
 virtual void SetVolume(int Current, int Total, bool Mute);
 virtual void Flush(void);
 };


Die selben Regeln wie beim Menü

cSkinMySkinDisplayVolume::cSkinMySkinDisplayVolume(void)
{ 
}


Freigabe des OSDs

cSkinMySkinDisplayVolume::~cSkinMySkinDisplayVolume()
{
  delete osd;
}


Zeichnet den Lautstärke Balken ins OSD

void cSkinMySkinDisplayVolume::SetVolume(int Current, int Total, bool Mute)
{
}


Selbe wie beim Menü void cSkinMySkinDisplayVolume::Flush(void)

{
 osd->Flush();
}

DisplayTracks

Bitte ausfüllen (bin mir gerade nicht sicher wann das angezeigt wird)

class cSkinMySkinDisplayTracks : public cSkinDisplayTracks {
private:

public:
 cSkinMySkinDisplayTracks(const char *Title, int NumTracks, const char * const *Tracks);
 virtual ~cSkinMySkinDisplayTracks();
 virtual void SetTrack(int Index, const char * const *Tracks);
 virtual void SetAudioChannel(int AudioChannel);
 virtual void Flush(void);
 };


Bitte ausfüllen

cSkinMySkinDisplayTracks::cSkinMySkinDisplayTracks(const char *Title, int NumTracks, const char * const *Tracks)
{
} 


Gibt das OSD wieder frei

cSkinMySkinDisplayTracks::~cSkinMySkinDisplayTracks()
{
 delete osd;
}


Bitte ausfüllen

void cSkinMySkinDisplayTracks::SetItem(const char *Text, int Index, bool Current)
{
}


Bitte ausfüllen

void cSkinMySkinDisplayTracks::SetTrack(int Index, const char * const *Tracks)
{
}


Biite ausfüllen

void cSkinMySkinDisplayTracks::SetAudioChannel(int AudioChannel)
{
}


Wie beim Menü

void cSkinMySkinDisplayTracks::Flush(void)
{
 osd->Flush();
}

DisplayMessage

Wenn kein OSD offen ist, dann werden die Messages über diese Methode angezeigt.

class cSkinMySkinDisplayMessage : public cSkinDisplayMessage {
private:

public:
 cSkinMySkinDisplayMessage(void);
 virtual ~cSkinMySkinDisplayMessage();
 virtual void SetMessage(eMessageType Type, const char *Text);
 virtual void Flush(void);
 };


Selben Regeln wie beim Menü

cSkinMySkinDisplayMessage::cSkinMySkinDisplayMessage(void)
{
}


Gibt den OSD wieder frei

cSkinMySkinDisplayMessage::~cSkinMySkinDisplayMessage()
{
 delete osd;
}


Selbe wie beim Menü

void cSkinMySkinDisplayMessage::SetMessage(eMessageType Type, const char *Text)
{
}


Selbe wie beim Menü

void cSkinMySkinDisplayMessage::Flush(void)
{
 osd->Flush();
}