diff options
author | Andreas Brugger <brougs78@gmx.net> | 2009-06-06 01:29:22 +0200 |
---|---|---|
committer | Thomas Günther <tom@toms-cafe.de> | 2009-06-06 01:29:22 +0200 |
commit | 60cd7db51838bb076312fd10e08bca0f4a005f82 (patch) | |
tree | 2ce8a57369abbd1ac1fed4a0d6527140808fbc3c | |
parent | 5ace1ef884772e4a15d58839074785b89412c8ee (diff) | |
parent | ac64ce03ec6b5766691ff2da3af6f51ed800792a (diff) | |
download | vdr-plugin-text2skin-60cd7db51838bb076312fd10e08bca0f4a005f82.tar.gz vdr-plugin-text2skin-60cd7db51838bb076312fd10e08bca0f4a005f82.tar.bz2 |
Added brougs78-extensions (thanks to Andreas Brugger / references #39)
- added tokens for the next 3 timers:
CurrentEventsTitle[123], CurrentEventsStartDateTime[123],
CurrentEventsStopDateTime[123], CurrentEventsChannelNumber[123],
CurrentEventsChannelName[123], CurrentEventsIsRecording[123]
- added audio- and video-tokens:
PresentLanguageCode, PresentLanguageDescription, PresentVideoAR and
implemented the missing code for the Language-token
- added tokens for replay:
ReplayName, ReplayDateTime, ReplayShortText, ReplayDescription,
ReplayLanguageCode, ReplayLanguageDescription, ReplayVideoAR
- additional recording-tokens:
RecordingVideoAR, RecordingSize
- added a reset for scrolling text (configurable)
- added recording-tokens:
RecordingLength, RecordingCuttedLength
- added tokens:
OsdWidth, OsdHeight
- switched the search-order for the fonts to priorise the skin-folder
(this avoids error-messages in the log)
- added the possibility to have a scrollbar in every menu - not fully
implemented yet (to position in menu-lists is not necessarily correct,
if there are more items with the same osd-text)
- added a configuration option for showing the scrollbar in the menus
- added token TimerConflicts using the service interface of the epgsearch
plugin "Epgsearch-lastconflictinfo-v1.0"
- added a test-feature to search for reruns of a program and add the
information to the extended epg-info (trigger DEVELOPMENT_FEATURES).
This uses a service interface of the epgsearch plugin
"Epgsearch-searchresults-v1.0"
- the extended epg-info and the recording-info are extended by AUX-Infos
(configurable)
there is also an option to strip known tags
- the tab-widths are scaled for taking into account that different TT-Fonts
have a different width than the default font from VDR
- added tokens for signal-info:
FrontendSTR, FrontendSNR, FrontendHasLock, FrontendHasSignal
- added token PresentEventID for EPG-images
- added tokens for recordings:
RecordingFilename, RecordingPriority, RecordingLifetime
- set EditableWidth. This is important for plugins like 'rotor' or
'extrecmenu'
-rw-r--r-- | HISTORY | 42 | ||||
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | common.c | 316 | ||||
-rw-r--r-- | common.h | 46 | ||||
-rw-r--r-- | display.c | 275 | ||||
-rw-r--r-- | display.h | 3 | ||||
-rw-r--r-- | i18n.c | 300 | ||||
-rw-r--r-- | menu.c | 37 | ||||
-rw-r--r-- | menu.h | 1 | ||||
-rw-r--r-- | render.c | 97 | ||||
-rw-r--r-- | render.h | 30 | ||||
-rw-r--r-- | setup.c | 22 | ||||
-rw-r--r-- | setup.h | 10 | ||||
-rw-r--r-- | status.c | 289 | ||||
-rw-r--r-- | status.h | 37 | ||||
-rw-r--r-- | text2skin.c | 24 | ||||
-rw-r--r-- | text2skin.h | 4 | ||||
-rw-r--r-- | xml/object.c | 9 | ||||
-rw-r--r-- | xml/string.c | 11 | ||||
-rw-r--r-- | xml/string.h | 43 |
20 files changed, 1570 insertions, 35 deletions
@@ -4,6 +4,48 @@ VDR Plugin 'text2skin' Revision History ____-__-__: Version 1.3 - Added Italian language texts (thanks to Diego Pierotto / closes #134) +- Added brougs78-extensions (thanks to Andreas Brugger / references #39) + - added tokens for the next 3 timers: + CurrentEventsTitle[123], CurrentEventsStartDateTime[123], + CurrentEventsStopDateTime[123], CurrentEventsChannelNumber[123], + CurrentEventsChannelName[123], CurrentEventsIsRecording[123] + - added audio- and video-tokens: + PresentLanguageCode, PresentLanguageDescription, PresentVideoAR and + implemented the missing code for the Language-token + - added tokens for replay: + ReplayName, ReplayDateTime, ReplayShortText, ReplayDescription, + ReplayLanguageCode, ReplayLanguageDescription, ReplayVideoAR + - additional recording-tokens: + RecordingVideoAR, RecordingSize + - added a reset for scrolling text (configurable) + - added recording-tokens: + RecordingLength, RecordingCuttedLength + - added tokens: + OsdWidth, OsdHeight + - switched the search-order for the fonts to priorise the skin-folder + (this avoids error-messages in the log) + - added the possibility to have a scrollbar in every menu - not fully + implemented yet (to position in menu-lists is not necessarily correct, + if there are more items with the same osd-text) + - added a configuration option for showing the scrollbar in the menus + - added token TimerConflicts using the service interface of the epgsearch + plugin "Epgsearch-lastconflictinfo-v1.0" + - added a test-feature to search for reruns of a program and add the + information to the extended epg-info (trigger DEVELOPMENT_FEATURES). + This uses a service interface of the epgsearch plugin + "Epgsearch-searchresults-v1.0" + - the extended epg-info and the recording-info are extended by AUX-Infos + (configurable) + there is also an option to strip known tags + - the tab-widths are scaled for taking into account that different TT-Fonts + have a different width than the default font from VDR + - added tokens for signal-info: + FrontendSTR, FrontendSNR, FrontendHasLock, FrontendHasSignal + - added token PresentEventID for EPG-images + - added tokens for recordings: + RecordingFilename, RecordingPriority, RecordingLifetime + - set EditableWidth. This is important for plugins like 'rotor' or + 'extrecmenu' 2009-06-01: Version 1.2 @@ -2,8 +2,11 @@ # Imlib2 for loading images. BEWARE that you can not use GraphTFT together with # Text2Skin if you use Imlib2! (That's why I actually implemented ImageMagick) +#DEBUG=1 +#BENCH=1 + HAVE_IMAGEMAGICK=1 -#HAVE_IMLIB2=1 +#HAVE_IMLIB2=1 # not recommended # comment this out if you don't want to use FreeType font rendering @@ -64,6 +67,10 @@ OBJS = $(PLUGIN).o loader.o display.o render.o common.o bitmap.o \ ### Includes and Defines (add further entries here): +ifdef DEVELOPMENT_FEATURES + DEFINES += -DDEVELOPMENT_FEATURES +endif + ifdef HAVE_IMLIB2 DEFINES += -DHAVE_IMLIB2 LIBS += -lImlib2 @@ -3,7 +3,13 @@ */ #include "common.h" +#include <sstream> #include <vdr/plugin.h> +#include <vdr/device.h> +#include <sys/ioctl.h> +#include <linux/dvb/frontend.h> + +#define FRONTEND_DEVICE "/dev/dvb/adapter%d/frontend%d" const std::string &SkinPath(void) { @@ -69,6 +75,14 @@ const char *ChannelShortName(const cChannel *Channel, int Number) return buffer; } + +const char *EventType(uint Number) +{ + static char buffer[25]; + buffer[0] = '\0'; + snprintf(buffer, sizeof(buffer), "%d", Number); + return buffer; +} /* const char *ChannelBouquet(const cChannel *Channel, int Number) { static char buffer[256]; @@ -115,6 +129,271 @@ const cRecording *GetRecordingByName(const char *Name) return NULL; } +const cRecording *GetRecordingByFileName(const char *FileName) +{ + return (FileName) ? Recordings.GetByName(FileName) : NULL; +} + +int GetFrontendSTR(void) +{ + uint16_t value = 0; + char *dev = NULL; + + asprintf(&dev, FRONTEND_DEVICE, cDevice::ActualDevice()->CardIndex(), 0); + int fe = open(dev, O_RDONLY | O_NONBLOCK); + free(dev); + if (fe >= 0) { + CHECK(ioctl(fe, FE_READ_SIGNAL_STRENGTH, &value)); + close(fe); + } + return value / 655; +} + +int GetFrontendSNR(void) +{ + uint16_t value = 0; + char *dev = NULL; + + asprintf(&dev, FRONTEND_DEVICE, cDevice::ActualDevice()->CardIndex(), 0); + int fe = open(dev, O_RDONLY | O_NONBLOCK); + free(dev); + if (fe >= 0) { + CHECK(ioctl(fe, FE_READ_SNR, &value)); + close(fe); + } + return value / 655; +} + +bool GetFrontendHasLock(void) +{ + uint16_t value = 0; + char *dev = NULL; + + asprintf(&dev, FRONTEND_DEVICE, cDevice::ActualDevice()->CardIndex(), 0); + int fe = open(dev, O_RDONLY | O_NONBLOCK); + free(dev); + if (fe >= 0) { + CHECK(ioctl(fe, FE_READ_STATUS, &value)); + close(fe); + } + return value & FE_HAS_LOCK; +} + +bool GetFrontendHasSignal(void) +{ + uint16_t value = 0; + char *dev = NULL; + + asprintf(&dev, FRONTEND_DEVICE, cDevice::ActualDevice()->CardIndex(), 0); + int fe = open(dev, O_RDONLY | O_NONBLOCK); + free(dev); + if (fe >= 0) { + CHECK(ioctl(fe, FE_READ_STATUS, &value)); + close(fe); + } + return value & FE_HAS_SIGNAL; +} + +std::string AddExtInfoToDescription(const char *Title, const char *ShortText, const char *Description, const char *Aux, bool StripAux) +{ + // max. width so lines don't get wrapped + #define MAX_CHARS 50 + + // prepare the description + std::stringstream desc(""); + + if (!isempty(Description)) { + // it seems that sometimes the description ends with a newline + // and sometimes it does not + std::string buf(Description); + while (!buf.empty() && buf[buf.size() - 1] == '\n') buf.erase(buf.size() - 1); + desc << buf << "\n"; // keep one newline + } + +#ifdef DEVELOPMENT_FEATURES + // try to find a rerun of the show using epgsearch-service + if (!isempty(Title)) { + Epgsearch_searchresults_v1_0 data; + data.query = strdup(Title); + data.mode = 0; + data.channelNr = 0; + data.useTitle = true; + data.useSubTitle = false; + data.useDescription = false; + if (cPluginManager::CallFirstService("Epgsearch-searchresults-v1.0", &data)) { + cList<Epgsearch_searchresults_v1_0::cServiceSearchResult>* list = data.pResultList; + if (list) { + // die aktuelle Sendung wird noch als WH angezeigt !!! + if (!desc.str().empty()) desc << "\n"; + desc << tr("RERUNS OF THIS SHOW") << ":\n"; + int i = 0; + for (Epgsearch_searchresults_v1_0::cServiceSearchResult *r = + list->First(); r && i < 5; r = list->Next(r)) { + i++; + std::stringstream buf; + buf << " - "; + buf << *DayDateTime(r->event->StartTime()); + buf << ": " << r->event->Title(); + if (!isempty(r->event->ShortText())) buf << "~" << r->event->ShortText(); + desc << FitToWidth(buf, MAX_CHARS) << "\n"; + } + delete list; + } + } + } +#endif // DEVELOPMENT_FEATURES + + // Add the AUX-Info of the Recording + if (Aux) { + if (StripAux) { + std::string auxRaw(Aux); + std::string auxEpgsearch = StripXmlTag(auxRaw, "epgsearch"); + if (!auxEpgsearch.empty()) { + if (!desc.str().empty()) + desc << "\n"; + desc << tr("AUXILIARY INFOS") << ":\n"; + std::stringstream buf; + buf << " - " << tr("Search timer") << ": " << StripXmlTag(auxRaw, "Search timer"); + desc << FitToWidth(buf, MAX_CHARS) << "\n"; + } + } + else { + if (!desc.str().empty()) + desc << "\n"; + desc << tr("AUXILIARY INFOS") << ":\n"; + desc << Aux << "\n"; + } + } + + return desc.str(); +} + +int GetRecordingSize(const char *FileName) +#if VDRVERSNUM >= 10338 +// use VDR's routine +{ + const cRecording *rec = GetRecordingByFileName(FileName); + return (rec) ? DirSizeMB(FileName) : 0; +} +#else +// use our own approach +{ + if (FileName != NULL) { + bool bRet = false; + long long size = 0; + int nFiles; + struct stat fileinfo; // Holds file information structure + char *cmd = NULL; +#if VDRVERSNUM >= 10318 + cReadLine reader; +#endif + asprintf(&cmd, "find '%s' -follow -type f -name '*.*'|sort ", FileName); + + FILE *p = popen(cmd, "r"); + int ret = 0; + if (p) { + char *s; + +#if VDRVERSNUM >= 10318 + while ((s = reader.Read(p)) != NULL) { +#else + while ((s = readline(p)) != NULL) { +#endif + if ((ret=stat(s, &fileinfo)) != -1) { + size += (long long)fileinfo.st_size; + nFiles++; + } + } + + bRet = true; + } + + pclose(p); + delete cmd; + + return (int)(size / 1024 / 1024); // [MB] + } + else + return 0; +} +#endif + +int GetRecordingLength(const char *FileName) +{ + // based on the enAIO-Patch for VDR + #define INDEXFILESUFFIX "/index.vdr" + + struct tIndex { int offset; uchar type; uchar number; short reserved; }; + tIndex *index; + char RecLength[21]; + char *filename = NULL; + int last = -1; + index = NULL; + if (FileName) { + filename = MALLOC(char, strlen(FileName) + strlen(INDEXFILESUFFIX) + 1); + if (filename) { + strcpy(filename, FileName); + char *pFileExt = filename + strlen(filename); + strcpy(pFileExt, INDEXFILESUFFIX); + int delta = 0; + if (access(filename, R_OK) == 0) { + struct stat buf; + if (stat(filename, &buf) == 0) { + delta = buf.st_size % sizeof(tIndex); + if (delta) { + delta = sizeof(tIndex) - delta; + esyslog("ERROR: invalid file size (%ld) in '%s'", (long)buf.st_size, filename); + } + last = (buf.st_size + delta) / sizeof(tIndex) - 1; + char hour[2], min[3]; +#if VDRVERSNUM >= 10318 + snprintf(RecLength, sizeof(RecLength), "%s", *IndexToHMSF(last, true)); +#else + snprintf(RecLength, sizeof(RecLength), "%s", IndexToHMSF(last, true)); +#endif + snprintf(hour, sizeof(hour), "%c", RecLength[0]); + snprintf(min, sizeof(min), "%c%c", RecLength[2], RecLength[3]); + return (atoi(hour) * 60) + atoi(min); + } + } + free(filename); + } + } + + return 0; +} + +int GetRecordingCuttedLength(const char *FileName) +{ + cMarks marks; + double length = 0; + int totalLength = GetRecordingLength(FileName); + const double diffIFrame = FRAMESPERSEC / 2; // approx. 1/2 sec. + + marks.Load(FileName); + + if (marks.Count()) { + int start = 1; // first frame + bool isStart = true; + + for (cMark *m = marks.First(); m; m = marks.GetNext(m->position)) { + if (isStart) + start = m->position; + else + length += (double)(m->position - start + 1 + diffIFrame) / (FRAMESPERSEC * 60); // [min] + + isStart = !isStart; + } + + // if there is no end-mark the last segment goes to the end of the rec. + if (!isStart) + length += totalLength - (double)(start - 1 - diffIFrame) / (FRAMESPERSEC * 60); // [min] + } + + // just to avoid, that the cutted length is bigger than the total length + return (int)length > totalLength ? totalLength : (int)length; +} + cxType TimeType(time_t Time, const std::string &Format) { static char result[1000]; @@ -263,3 +542,40 @@ void SkipQuotes(std::string &Value) { else esyslog("ERROR: text2skin: missing closing %c", quote); } + +std::string FitToWidth(std::string &Line, uint Width) +{ + std::string buf(Line); + if (buf.size() > Width) { + buf.erase(Width - 3); + buf.append("..."); + } + return buf; +} + +std::string FitToWidth(std::stringstream &Line, uint Width) +{ + std::string buf(Line.str()); + if (buf.size() > Width) { + buf.erase(Width - 3); + buf.append("..."); + } + return buf; +} + +std::string StripXmlTag(std::string &Line, const char *Tag) +{ + // set the search strings + std::stringstream strStart, strStop; + strStart << "<" << Tag << ">"; + strStop << "</" << Tag << ">"; + // find the strings + std::string::size_type locStart = Line.find(strStart.str()); + std::string::size_type locStop = Line.find(strStop.str()); + if (locStart == std::string::npos || locStop == std::string::npos) + return ""; + // extract relevant text + int pos = locStart + strStart.str().size(); + int len = locStop - pos; + return len < 0 ? "" : Line.substr(pos, len); +} @@ -9,6 +9,7 @@ #include <string> #include <vdr/osd.h> #include <vdr/config.h> +#include <vdr/epg.h> // from recording.h (VDR <= 1.7.2) #define FRAMESPERSEC 25 @@ -27,6 +28,9 @@ # define Ddiff(t,x) #endif +#define DStartBench(x) uint64_t bench_##x = time_ms() +#define DShowBench(t,x) fprintf(stderr, "%s took %llu ms\n", t, time_ms() - bench_##x) + #if VDRVERSNUM >= 10318 # define time_ms() cTimeMs::Now() # define Apid1() Apid(0) @@ -50,10 +54,20 @@ const std::string &SkinPath(void); const char *ChannelNumber(const cChannel *Channel, int Number); const char *ChannelName(const cChannel *Channel, int Number); const char *ChannelShortName(const cChannel *Channel, int Number); +const char *EventType(uint Number); //const char *ChannelBouquet(const cChannel *Channel, int Number); bool StoppedTimer(const char *Name); const cRecording *GetRecordingByName(const char *Name); +const cRecording *GetRecordingByFileName(const char *FileName); +int GetFrontendSTR(void); // Signal strength [%] +int GetFrontendSNR(void); // Signal to Noise ratio [%] +bool GetFrontendHasLock(void); +bool GetFrontendHasSignal(void); +std::string AddExtInfoToDescription(const char *Title, const char *ShortText, const char *Description, const char *Aux = NULL, bool StripAux = false); +int GetRecordingSize(const char *FileName); // [MB] +int GetRecordingLength(const char *FileName); // [min] +int GetRecordingCuttedLength(const char *FileName); // [min] cxType TimeType(time_t Time, const std::string &Format); cxType DurationType(uint Index, const std::string &Format); @@ -62,5 +76,37 @@ bool ParseVar(const char *Text, const char *Name, std::string &Value); bool ParseVar(const char *Text, const char *Name, tColor *Value); void SkipQuotes(std::string &Value); +std::string FitToWidth(std::string &Line, uint Width); +std::string FitToWidth(std::stringstream &Line, uint Width); +std::string StripXmlTag(std::string &Line, const char *Tag); + +// Data structure for service "Epgsearch-searchresults-v1.0" +struct Epgsearch_searchresults_v1_0 { +// in + char* query; // search term + int mode; // search mode (0=phrase, 1=and, 2=or, 3=regular expression) + int channelNr; // channel number to search in (0=any) + bool useTitle; // search in title + bool useSubTitle; // search in subtitle + bool useDescription; // search in description +// out + + class cServiceSearchResult : public cListObject { + public: + const cEvent* event; + cServiceSearchResult(const cEvent* Event) : event(Event) {} + }; + + cList<cServiceSearchResult>* pResultList; // pointer to the results +}; + +// Data structure for service "Epgsearch-lastconflictinfo-v1.0" +struct Epgsearch_lastconflictinfo_v1_0 { +// in +// out + time_t nextConflict; // next conflict date, 0 if none + int relevantConflicts; // number of relevant conflicts + int totalConflicts; // total number of conflicts +}; #endif // VDR_TEXT2SKIN_COMMON_H @@ -10,6 +10,7 @@ #include "common.h" #include "xml/string.h" #include <vdr/menu.h> +#include <vdr/plugin.h> // --- cText2SkinDisplayChannel ----------------------------------------------- @@ -241,7 +242,16 @@ cxType cText2SkinDisplayChannel::GetTokenData(const txToken &Token) case tLanguage: { #if VDRVERSNUM >= 10318 - // TODO !!! + cDevice *dev = cDevice::PrimaryDevice(); + eTrackType trackType = dev->GetCurrentAudioTrack(); + const tTrackId *track = dev->GetTrack(trackType); + if (track) { + std::string buffer(track->language); + if (trackType >= ttDolby) + buffer.append("DD"); + return (cxType)buffer.c_str(); + } + return (cxType)false; #else int cur; const char **tracks = cDevice::PrimaryDevice()->GetAudioTracks(&cur); @@ -582,6 +592,32 @@ cxType cText2SkinDisplayReplay::GetTokenData(const txToken &Token) } return false; + case tLanguage: { +#if VDRVERSNUM >= 10318 + cDevice *dev = cDevice::PrimaryDevice(); + eTrackType trackType = dev->GetCurrentAudioTrack(); + const tTrackId *track = dev->GetTrack(trackType); + if (track) { + std::string buffer(track->language); + if (trackType >= ttDolby) + buffer.append("DD"); + return (cxType)buffer.c_str(); + } + return (cxType)false; +#else + int cur; + const char **tracks = cDevice::PrimaryDevice()->GetAudioTracks(&cur); + if (tracks) { + int i = 0; + while (tracks[i] != NULL) + ++i; + if (cur < i) + return tracks[cur]; + } +#endif + } + return false; + case tMessage: return mText; @@ -737,7 +773,9 @@ void cText2SkinDisplayMenu::Clear(void) mItems.clear(); mCurrentItem = (uint)-1; mEvent = NULL; + ExtPresentDescription = ""; mRecording = NULL; + ExtRecordingDescription = ""; mText = ""; cText2SkinRender::Clear(); SetDirty(); @@ -755,6 +793,9 @@ void cText2SkinDisplayMenu::SetTitle(const char *Title) UpdateLock(); if (Title == NULL) Title = ""; if (mTitle != Title) { + mUpdate.timerConflict = true; + mUpdate.events = true; + mUpdate.resetMarquee = true; mTitle = Title; SetDirty(); } @@ -835,6 +876,9 @@ void cText2SkinDisplayMenu::SetItem(const char *Text, int Index, bool Current, b mCurrentItem = Index; SetDirty(); } + + if (Current) + mRender->mMenuScrollbar.currentOnScreen = (uint)Index; UpdateUnlock(); } @@ -848,6 +892,7 @@ void cText2SkinDisplayMenu::SetEvent(const cEvent *Event) UpdateLock(); if (mEvent != Event) { mEvent = Event; + ExtPresentDescription = ""; if (mEvent != NULL) SetDirty(); } @@ -865,6 +910,7 @@ void cText2SkinDisplayMenu::SetRecording(const cRecording *Recording) // yet unused if (mRecording != Recording) { mRecording = Recording; + ExtRecordingDescription = ""; if (mRecording != NULL) SetDirty(); } @@ -1046,14 +1092,129 @@ cxType cText2SkinDisplayMenu::GetTokenData(const txToken &Token) : (cxType)false; case tPresentDescription: + if (mEvent) { + if (ExtPresentDescription == "") { +#if VDRVERSNUM >= 10344 + // find corresponding timer + const char *aux = NULL; + for (cTimer *tim = Timers.First(); tim; tim = Timers.Next(tim)) + if (tim->Event() == mEvent) + aux = tim->Aux(); + ExtPresentDescription = AddExtInfoToDescription(mEvent->Title(), mEvent->ShortText(), mEvent->Description(), aux, Text2SkinSetup.StripAux); +#else + ExtPresentDescription = AddExtInfoToDescription(mEvent->Title(), mEvent->ShortText(), mEvent->Description()); +#endif + } + return (cxType)ExtPresentDescription; + } else + return (cxType)false; + +#if VDRVERSNUM >= 10318 + case tPresentLanguageCode: + if (mEvent) { + const cComponents *components = mEvent->Components(); + if (components) { + int index = Token.Attrib.Number; + + // don't return language-code for the video-stream + for (int i = 0; i < components->NumComponents(); i++) { + const tComponent *c = components->Component(i); + if (c->stream != 2) // only audio-streams + index++; + if (i == index) { + std::string buffer(c->language); + if (c->type == 1) + buffer.append("MONO"); + if ((c->type == 2) || (c->type == 4)) + buffer.append("DUAL"); + if (c->type == 5) + buffer.append("DD"); + return (cxType)buffer.c_str(); + } + } + } + } + return false; + + case tPresentLanguageDescription: + if (mEvent) { + const cComponents *components = mEvent->Components(); + if (components) { + int index = Token.Attrib.Number; + + // don't return language-code for the video-stream + for (int i = 0; i < components->NumComponents(); i++) { + const tComponent *c = components->Component(i); + if (c->stream != 2) // only audio-streams + index++; + if (i == index) + return (cxType)c->description; + } + } + } + return false; + + case tPresentVideoAR: + if (mEvent) { + const cComponents *components = mEvent->Components(); + if (components) { + for (int i = 0; i < components->NumComponents(); i++) { + const tComponent *c = components->Component(i); + if (c->stream == 1) { + switch (c->type) { + case 1: + return "4:3"; + case 3: + return "16:9"; + } + } + } + } + } + return false; +#endif + + case tPresentEventID: return mEvent != NULL - ? (cxType)mEvent->Description() + ? (cxType)EventType(mEvent->EventID()) : (cxType)false; case tHasVPS: case tChannelHasVPS: return mEvent != NULL && mEvent->Vps() != 0; + case tChannelName: + if (mEvent) { // extended EPG + cChannel *channel = Channels.GetByChannelID(mEvent->ChannelID(), true); + return channel != NULL + ? (cxType)ChannelName(channel, 0) + : (cxType)false; + } + else if (mRecording) { // recording Info + cRecordingInfo *recInfo = const_cast<cRecordingInfo*>(mRecording->Info()); + cChannel *channel = Channels.GetByChannelID(recInfo->ChannelID(), true); + return channel != NULL + ? (cxType)ChannelName(channel, 0) + : (cxType)false; + } else + return (cxType)false; + + case tChannelShortName: + if (mEvent) { // extended EPG + cChannel *channel = Channels.GetByChannelID(mEvent->ChannelID(), true); + return channel != NULL + ? (cxType)ChannelShortName(channel, 0) + : (cxType)false; + } + else if (mRecording) { // recording Info + cRecordingInfo *recInfo = const_cast<cRecordingInfo*>(mRecording->Info()); + cChannel *channel = Channels.GetByChannelID(recInfo->ChannelID(), true); + return channel != NULL + ? (cxType)ChannelShortName(channel, 0) + : (cxType)false; + } else + return (cxType)false; + case tPresentHasVPS: return mEvent != NULL && mEvent->Vps() != 0 && mEvent->Vps() != mEvent->StartTime(); @@ -1074,6 +1235,21 @@ cxType cText2SkinDisplayMenu::GetTokenData(const txToken &Token) ? (cxType)mRecording->Name() : (cxType)false; + case tRecordingFilename: + return mRecording != NULL + ? (cxType)mRecording->FileName() + : (cxType)false; + + case tRecordingPriority: + return mRecording != NULL + ? (cxType)mRecording->priority + : (cxType)false; + + case tRecordingLifetime: + return mRecording != NULL + ? (cxType)mRecording->lifetime + : (cxType)false; + case tRecordingDateTime: return mRecording != NULL ? (cxType)TimeType(mRecording->start, Token.Attrib.Text) @@ -1090,31 +1266,96 @@ cxType cText2SkinDisplayMenu::GetTokenData(const txToken &Token) : (cxType)false; case tRecordingDescription: - return mRecording != NULL - ? (cxType)mRecording->Info()->Description() - : (cxType)false; + if (mRecording) { + if (ExtRecordingDescription == "") +#if VDRVERSNUM >= 10344 + ExtRecordingDescription = AddExtInfoToDescription(mRecording->Info()->Title(), mRecording->Info()->ShortText(), mRecording->Info()->Description(), Text2SkinSetup.ShowAux ? mRecording->Info()->Aux() : NULL, Text2SkinSetup.StripAux); +#else + ExtRecordingDescription = AddExtInfoToDescription(mRecording->Info()->Title(), mRecording->Info()->ShortText(), mRecording->Info()->Description()); +#endif + return (cxType)ExtRecordingDescription; + } else + return (cxType)false; case tRecordingLanguageCode: - if (mRecording != NULL) { - const tComponent *c - = mRecording->Info()->Components()->Component(Token.Attrib.Number); - return c != NULL - ? (cxType)c->language - : (cxType)false; + if (mRecording) { + const cComponents *components = mRecording->Info()->Components(); + if (components) { + int index = Token.Attrib.Number; + + // don't return language-code for the video-stream + for (int i = 0; i < components->NumComponents(); i++) { + const tComponent *c = components->Component(i); + if (c->stream != 2) // only audio-streams + index++; + if (i == index) { + std::string buffer(c->language); + if (c->type == 1) + buffer.append("MONO"); + if ((c->type == 2) || (c->type == 4)) + buffer.append("DUAL"); + if (c->type == 5) + buffer.append("DD"); + return (cxType)buffer.c_str(); + } + } + } } return false; case tRecordingLanguageDescription: - if (mRecording != NULL) { - const tComponent *c - = mRecording->Info()->Components()->Component(Token.Attrib.Number); - return c != NULL - ? (cxType)c->description - : (cxType)false; + if (mRecording) { + const cComponents *components = mRecording->Info()->Components(); + if (components) { + int index = Token.Attrib.Number; + + // don't return language-code for the video-stream + for (int i = 0; i < components->NumComponents(); i++) { + const tComponent *c = components->Component(i); + if (c->stream != 2) // only audio-streams + index++; + if (i == index) + return (cxType)c->description; + } + } + } + return false; + + case tRecordingVideoAR: + if (mRecording) { + const cComponents *components = mRecording->Info()->Components(); + if (components) { + for (int i = 0; i < components->NumComponents(); i++) { + const tComponent *c = components->Component(i); + if (c->stream == 1) { + switch (c->type) { + case 1: + return "4:3"; + case 3: + return "16:9"; + } + } + } + } } return false; #endif + case tRecordingSize: + return mRecording != NULL + ? (cxType)GetRecordingSize(mRecording->FileName()) + : (cxType)false; + + case tRecordingLength: + return mRecording != NULL + ? (cxType)GetRecordingLength(mRecording->FileName()) + : (cxType)false; + + case tRecordingCuttedLength: + return mRecording != NULL + ? (cxType)GetRecordingCuttedLength(mRecording->FileName()) + : (cxType)false; + default: return cText2SkinRender::GetTokenData(Token); } @@ -142,8 +142,10 @@ private: // detailed event view const cEvent *mEvent; + std::string ExtPresentDescription; // detailed recording const cRecording *mRecording; + std::string ExtRecordingDescription; // long text std::string mText; @@ -169,6 +171,7 @@ protected: virtual void SetEditableWidth(int Width) { cSkinDisplayMenu::SetEditableWidth(Width); } virtual int MaxItems(void) { return mMaxItems; } virtual void SetMaxItems(int MaxItems) { mMaxItems = MaxItems; } + virtual int GetMaxItems(void) { return mMaxItems; } public: cText2SkinDisplayMenu(cText2SkinLoader *Loader); @@ -105,6 +105,306 @@ const tI18nPhrase Phrases[] = { "", #endif }, + { "RERUNS OF THIS SHOW", + "WIEDERHOLUNGEN DIESER SENDUNG", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, + { "Scale factor of the tab-widths [%]", + "Skalierungsfaktor für die Tabolatorweiten [%]", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, + { "Scrolling behaviour", + "Scroll-Verhalten", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, + { "Show scrollbar in the menus", + "Zeige Bildlaufleiste in Menüs", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, + { "Reset Marquee for new item", + "Marquee für neues Item zurücksetzen", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, + { "Use 'epgsearch' to check timer-conflicts", + "Timerkonflikte mit 'epgsearch' überprüfen", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, + { "to the left", + "nach links", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, + { "left and right", + "links und rechts", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, + { "AUXILIARY INFOS", + "ZUSATZINFOS", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, + { "Auxiliary infos in recordings/timers", + "Zusatzinfos bei Aufnahmen/Timer anzeigen", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, + { " Extract known tags", + " Bekannte Tags extrahieren", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, + { "Search timer", + "Suchtimer", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +#if VDRVERSNUM >= 10313 + "", +#endif +#if VDRVERSNUM >= 10316 + "", +#endif + }, { "Max. image cache size", "Max. Größe des Bildspeichers", "", @@ -9,19 +9,52 @@ cText2SkinSetupPage::cText2SkinSetupPage(void) { mData = Text2SkinSetup; + Setup(); +} + +void cText2SkinSetupPage::Setup(void) { + int current = Current(); + Clear(); + + Add(new cMenuEditBoolItem(tr("Show scrollbar in the menus"), &mData.MenuScrollbar, tr("no"), tr("yes"))); + Add(new cMenuEditBoolItem(tr("Scrolling behaviour"), &mData.MarqueeLeftRight, tr("to the left"), tr("left and right"))); + Add(new cMenuEditBoolItem(tr("Reset Marquee for new item"), &mData.MarqueeReset, tr("no"), tr("yes"))); +#if VDRVERSNUM >= 10344 + Add(new cMenuEditBoolItem(tr("Show auxiliary infos of recordings"), &mData.ShowAux, tr("no"), tr("yes"))); + if (mData.ShowAux) + Add(new cMenuEditBoolItem(tr(" Extract known tags"), &mData.StripAux, tr("no"), tr("yes"))); +#endif +#if VDRVERSNUM >= 10330 + Add(new cMenuEditBoolItem(tr("Use 'timeline' to check timer-conflicts"), &mData.CheckTimerConflict, tr("no"), tr("yes"))); +#endif Add(new cOsdItem(tr("Flush image cache"), osUser1)); Add(new cMenuEditIntItem(tr("Max. image cache size"), &mData.MaxCacheFill)); + + SetCurrent(Get(current)); + Display(); } cText2SkinSetupPage::~cText2SkinSetupPage() { } void cText2SkinSetupPage::Store(void) { + SetupStore("MenuScrollbar", mData.MenuScrollbar); + SetupStore("MarqueeLeftRight", mData.MarqueeLeftRight); + SetupStore("MarqueeReset", mData.MarqueeReset); +#if VDRVERSNUM >= 10344 + SetupStore("ShowAux", mData.ShowAux); + SetupStore("StripAux", mData.StripAux); +#endif +#if VDRVERSNUM >= 10330 + SetupStore("CheckTimerConflict", mData.CheckTimerConflict); +#endif SetupStore("MaxCacheFill", mData.MaxCacheFill); Text2SkinSetup = mData; } eOSState cText2SkinSetupPage::ProcessKey(eKeys Key) { + int oldShowAux = mData.ShowAux; + eOSState state = cMenuSetupPage::ProcessKey(Key); if (state == osUser1) { Skins.Message(mtInfo, tr("Flushing image cache...")); @@ -29,6 +62,10 @@ eOSState cText2SkinSetupPage::ProcessKey(eKeys Key) { Skins.Message(mtInfo, NULL); return osContinue; } + + if (mData.ShowAux != oldShowAux) + Setup(); + return state; } @@ -17,6 +17,7 @@ public: cText2SkinSetupPage(void); virtual ~cText2SkinSetupPage(); + void Setup(void); virtual void Store(void); eOSState ProcessKey(eKeys Key); }; @@ -145,15 +145,22 @@ void cText2SkinRender::Action(void) void cText2SkinRender::Update(void) { + //DStartBench(malen); + //DStartBench(ges); Dbench(update); for (uint i = 0; i < mDisplay->Objects(); ++i) DrawObject(mDisplay->GetObject(i)); + //DShowBench("---\t", malen); + //DStartBench(flushen); Dbench(flush); mScreen->Flush(); Ddiff("flush only", flush); Ddiff("complete flush", update); + //DShowBench("===\t", flushen); + //DShowBench("=== ges\t", ges); + //printf("====\t%d\n", mDisplay->Objects()); } void cText2SkinRender::DrawObject(const cxObject *Object) @@ -220,7 +227,9 @@ void cText2SkinRender::DrawObject(const cxObject *Object) uint itemheight = item->Size().h; uint maxitems = areasize.h / itemheight; uint yoffset = 0; + bool initialEditableWidthSet = false; + mMenuScrollbar.maxItems = maxitems; SetMaxItems(maxitems); //Dprintf("setmaxitems %d\n", maxitems); for (uint i = 0; i < maxitems; ++i, yoffset += itemheight) { for (uint j = 1; j < Object->Objects(); ++j) { @@ -253,14 +262,36 @@ void cText2SkinRender::DrawObject(const cxObject *Object) ++n; nexttab = GetTab(n); } - + + // set initial EditableWidth + // this is for plugins like 'extrecmenu' and 'rotor' + if ((obj.Type() == cxObject::text || obj.Type() == cxObject::marquee || obj.Type() == cxObject::blink) && !initialEditableWidthSet) { + initialEditableWidthSet = true; + SetEditableWidth(obj.Size().w); + } + if (t >= 0 && nexttab > 0 && nexttab < obj.mPos1.x + obj.Size().w - 1) // there is a "next tab" with text obj.mPos2.x = Object->mPos1.x + o->mPos1.x + nexttab; else { // there is no "next tab", use the rightmost edge obj.mPos2.x += Object->mPos1.x; - SetEditableWidth(obj.Size().w); + /* not used anymore due to change to fontOsd + but could be usefull if someone uses a differnt font + + if ((obj.Type() == cxObject::text || obj.Type() == cxObject::marquee || obj.Type() == cxObject::blink) && t == 1) { + // VDR assumes, that the font in the menu is fontOsd, + // so the EditableWidth is not necessarily correct + // for TTF + const cFont *defFont = cFont::GetFont(fontOsd); + const char *dummy = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 "; + int editableWidth = obj.Size().w; + if (defFont != obj.Font()) + editableWidth = (int)(editableWidth * defFont->Width(dummy) / (1.1 * obj.Font()->Width(dummy))); + SetEditableWidth(editableWidth); + } */ + if ((obj.Type() == cxObject::text || obj.Type() == cxObject::marquee || obj.Type() == cxObject::blink) && t == 1) + SetEditableWidth(obj.Size().w); } obj.mPos2.y += Object->mPos1.y + yoffset; @@ -358,7 +389,21 @@ void cText2SkinRender::DrawMarquee(const txPoint &Pos, const txSize &Size, const state = tState(); state.text = Text; } - + + if (Text2SkinSetup.MarqueeReset && mUpdate.resetMarquee && mUpdate.currentItem.find(Text, 0) != std::string::npos) { + state.offset = 0; + state.direction = 1; + state.nexttime = 0; + state.scrolling = false; + mUpdate.foundFirstItem = true; + } + else { + if (mUpdate.foundFirstItem) { + mUpdate.resetMarquee = false; + mUpdate.foundFirstItem = false; + } + } + if (state.nexttime == 0) state.nexttime = mNow + 1500; else if (mNow >= state.nexttime) { @@ -375,7 +420,7 @@ void cText2SkinRender::DrawMarquee(const txPoint &Pos, const txSize &Size, const ++state.direction; nextin = 1500; } else - --state.offset; + Text2SkinSetup.MarqueeLeftRight ? --state.offset : state.offset = 0; } state.nexttime = mNow + nextin; } @@ -544,6 +589,26 @@ void cText2SkinRender::DrawScrollbar(const txPoint &Pos, const txSize &Size, con DrawRectangle(sp, ss, Fg); } } + else if (mMenuScrollbar.Available()) { + DrawRectangle(Pos, Size, Bg); + txPoint sbPoint = Pos; + txSize sbSize = Size; + if (sbSize.h > sbSize.w) { + // -1 to get at least 1 pixel height + double top = double(mMenuScrollbar.Top()) / mMenuScrollbar.total * (sbSize.h - 1); + double bottom = double(mMenuScrollbar.Bottom()) / mMenuScrollbar.total * (sbSize.h - 1); + sbPoint.y += (uint)top; + sbSize.h -= (uint)top + (uint)bottom; + } + else { + // -1 to get at least 1 pixel height + double left = double(mMenuScrollbar.Top()) / mMenuScrollbar.total * (sbSize.w - 1); + double right = double(mMenuScrollbar.Bottom()) / mMenuScrollbar.total * (sbSize.w - 1); + sbPoint.x += (uint)left; + sbSize.w -= (uint)left + (uint)right; + } + DrawRectangle(sbPoint, sbSize, Fg); + } } txPoint cText2SkinRender::Transform(const txPoint &Pos) @@ -567,7 +632,9 @@ bool cText2SkinRender::ItemColor(const std::string &Color, tColor &Result) std::string cText2SkinRender::ImagePath(const std::string &Filename) { if (mRender) - return mRender->mBasePath + "/" + Filename; + return (*Filename.data() == '/') + ? Filename + : mRender->mBasePath + "/" + Filename; return ""; } @@ -655,12 +722,26 @@ cxType cText2SkinRender::GetTokenData(const txToken &Token) case tDateTime: return TimeType(time(NULL), Token.Attrib.Text); - case tCanScrollUp: return mScroller != NULL && mScroller->CanScrollUp(); + case tCanScrollUp: + if (mScroller) + return mScroller->CanScrollUp(); + if (mMenuScrollbar.Available()) + return mMenuScrollbar.CanScrollUp(); + return false; + + case tCanScrollDown: + if (mScroller) + return mScroller->CanScrollDown(); + if (mMenuScrollbar.Available()) + return mMenuScrollbar.CanScrollDown(); + return false; - case tCanScrollDown: return mScroller != NULL && mScroller->CanScrollDown(); - case tIsRecording: return cRecordControls::Active(); + case tOsdWidth: return (cxType)mBaseSize.w; + + case tOsdHeight: return (cxType)mBaseSize.h; + #if VDRVERSNUM >=10318 case tAudioTrack: { cDevice *dev = cDevice::PrimaryDevice(); @@ -7,6 +7,7 @@ #include "common.h" #include "scroller.h" +#include "setup.h" #include "xml/skin.h" #include "xml/type.h" #include <vdr/osd.h> @@ -47,6 +48,7 @@ private: std::string mBasePath; bool mDirty; + uint mMaxItems; cSkin *mFallback; // update thread @@ -115,6 +117,7 @@ protected: virtual bool HasTabText(int Index, int n) { return false; } virtual void SetEditableWidth(int Width) {} virtual void SetMaxItems(int MaxItems) {} + inline int GetmMaxItems(void) { return mMaxItems; } // functions for display renderer to control behaviour void Flush(bool Force = false); @@ -134,6 +137,33 @@ public: static bool ItemColor(const std::string &Color, tColor &Result); static std::string ImagePath(const std::string &Filename); static cxType GetToken(const txToken &Token); + + // provide scrollbar in every menu + struct tMenuScrollbar { + uint current; // overall (0 ... toal-1) + uint currentOnScreen; // on the current screen (0 ... maxItems-1) + uint total; + uint maxItems; // viewable on current screen + std::vector<std::string> items; + + tMenuScrollbar(void) : current(0), currentOnScreen(0), total(0), maxItems(0) {} + bool Available(void) { return Text2SkinSetup.MenuScrollbar ? total > maxItems : false; } + uint Top(void) { return current - currentOnScreen; } + uint Bottom(void) { return total - Top() - maxItems; } + bool CanScrollUp(void) { return Text2SkinSetup.MenuScrollbar ? Top() > 0 : false; } + bool CanScrollDown(void) { return Text2SkinSetup.MenuScrollbar ? Bottom() > 0 : false; } + } mMenuScrollbar; + + // update infos (e.g. timerConflict) + struct tUpdate { + bool timerConflict; + bool events; + std::string currentItem; + bool resetMarquee; + bool foundFirstItem; + + tUpdate(void) : timerConflict(true), events(true), currentItem(""), resetMarquee(true), foundFirstItem(false) {} + } mUpdate; }; inline void cText2SkinRender::Flush(bool Force) @@ -9,11 +9,31 @@ cText2SkinSetup Text2SkinSetup; // --- cText2SkinSetup -------------------------------------------------------- cText2SkinSetup::cText2SkinSetup(void) { + MenuScrollbar = false; + MarqueeLeftRight = true; + MarqueeReset = false; +#if VDRVERSNUM >= 10344 + ShowAux = true; + StripAux = true; +#endif +#if VDRVERSNUM >= 10330 + CheckTimerConflict = false; +#endif MaxCacheFill = 25; } bool cText2SkinSetup::SetupParse(const char *Name, const char *Value) { - if (strcmp(Name, "MaxCacheFill") == 0) MaxCacheFill = atoi(Value); + if (strcmp(Name, "MenuScrollbar") == 0) MenuScrollbar = atoi(Value); + else if (strcmp(Name, "MarqueeLeftRight") == 0) MarqueeLeftRight = atoi(Value); + else if (strcmp(Name, "MarqueeReset") == 0) MarqueeReset = atoi(Value); +#if VDRVERSNUM >= 10344 + else if (strcmp(Name, "ShowAux") == 0) ShowAux = atoi(Value); + else if (strcmp(Name, "StripAux") == 0) StripAux = atoi(Value); +#endif +#if VDRVERSNUM >= 10330 + else if (strcmp(Name, "CheckTimerConflict") == 0) CheckTimerConflict = atoi(Value); +#endif + else if (strcmp(Name, "MaxCacheFill") == 0) MaxCacheFill = atoi(Value); else return false; return true; } @@ -13,6 +13,16 @@ public: bool SetupParse(const char *Name, const char *Value); + int MenuScrollbar; + int MarqueeLeftRight; + int MarqueeReset; +#if VDRVERSNUM >= 10344 + int ShowAux; + int StripAux; +#endif +#if VDRVERSNUM >= 10330 + int CheckTimerConflict; +#endif int MaxCacheFill; }; @@ -4,6 +4,10 @@ #include "status.h" #include "render.h" +#include "menu.h" +#include <vdr/timers.h> +#include <vdr/plugin.h> +#include <vdr/menu.h> const std::string ReplayNames[__REPLAY_COUNT__] = { "", "normal", "mp3", "mplayer", "dvd", "vcd", "image" }; @@ -51,9 +55,18 @@ void cText2SkinStatus::Replaying(const cControl* /*Control*/, const char *Name, mReplayIsLoop = Name[1] == 'L'; mReplayIsShuffle = Name[2] == 'S'; } - } - else if (GetRecordingByName(Name) != NULL) + } +#if VDRVERSNUM >= 10338 + else if (const cRecording *rec = GetRecordingByFileName(FileName)) { + mReplay = rec; mReplayMode = replayNormal; + } +#else + else if (const cRecording *rec = GetRecordingByName(Name)) { + mReplay = rec; + mReplayMode = replayNormal; + } +#endif else if (strcmp(Name, "DVD") == 0) mReplayMode = replayDVD; else if (strcmp(Name, "VCD") == 0) @@ -118,14 +131,286 @@ void cText2SkinStatus::OsdClear(void) #endif cxString::Reparse(); } + + if (mRender != NULL) + mRender->mMenuScrollbar.total = 0; +} + +void cText2SkinStatus::OsdCurrentItem(const char *Text) +{ + if (mRender && Text) { + // update infos + cText2SkinRender::tUpdate *u = &mRender->mUpdate; + //static std::string lastItem; + + //lastItem = u->currentItem; + u->currentItem = Text; + u->resetMarquee = true; + u->foundFirstItem = false; + + // find current item in scrollbar + if (Text2SkinSetup.MenuScrollbar) { + cText2SkinRender::tMenuScrollbar *sb = &mRender->mMenuScrollbar; + for (uint i = 0; i < sb->total; i++) { + if (sb->items[i] == Text) { + sb->current = i; + break; + } + } + } + } +} + +void cText2SkinStatus::OsdItem(const char *Text, int Index) +{ + if (mRender && Text2SkinSetup.MenuScrollbar && Text) { + uint curr = (uint)Index; + cText2SkinRender::tMenuScrollbar *sb = &mRender->mMenuScrollbar; + + if (curr < sb->items.size()) + sb->items[curr] = Text; + else { + sb->items.push_back(Text); + sb->total = curr + 1; + } + + if (curr + 1 > sb->total) + sb->total = curr + 1; + } +} + +void cText2SkinStatus::UpdateEvents(void) +{ + if (mRender->mUpdate.events) { + mRender->mUpdate.events = false; + + mEvents.Clear(); + Timers.IncBeingEdited(); + + for (cTimer *tim = Timers.First(); tim; tim = Timers.Next(tim)) { + if (tim->HasFlags(tfActive)) { + int i = 0; + cTimer dummy; + dummy = *tim; + + do { + mEvents.Add(new tEvent(&dummy)); + + if (!dummy.IsSingleEvent()) { // add 4 additional rep. timer + int j = 0; + do { + j++; // just to avoid a endless loop + dummy.Skip(); + dummy.Matches(); // Refresh start- and end-time + } while (!dummy.DayMatches(dummy.StartTime()) && (j < 7)); + } + + i++; + } while (!dummy.IsSingleEvent() && i < 5); + } + } + + Timers.DecBeingEdited(); + mEvents.Sort(); + } } cxType cText2SkinStatus::GetTokenData(const txToken &Token) { + int event = 0; + switch (Token.Type) { case tReplayMode: return ReplayNames[mReplayMode]; + case tFrontendSTR: + return GetFrontendSTR(); + + case tFrontendSNR: + return GetFrontendSNR(); + + case tFrontendHasLock: + return GetFrontendHasLock(); + + case tFrontendHasSignal: + return GetFrontendHasSignal(); + + case tCurrentEventsTitle3: + event++; + case tCurrentEventsTitle2: + event++; + case tCurrentEventsTitle1: + UpdateEvents(); + return mEvents.Count() > event + ? (cxType)mEvents.Get(event)->title.c_str() + : (cxType)false; + + case tCurrentEventsStartDateTime3: + event++; + case tCurrentEventsStartDateTime2: + event++; + case tCurrentEventsStartDateTime1: + UpdateEvents(); + return mEvents.Count() > event + ? (cxType)TimeType(mEvents.Get(event)->startTime, Token.Attrib.Text) + : (cxType)false; + + case tCurrentEventsStopDateTime3: + event++; + case tCurrentEventsStopDateTime2: + event++; + case tCurrentEventsStopDateTime1: + UpdateEvents(); + return mEvents.Count() > event + ? (cxType)TimeType(mEvents.Get(event)->stopTime, Token.Attrib.Text) + : (cxType)false; + + case tCurrentEventsChannelNumber3: + event++; + case tCurrentEventsChannelNumber2: + event++; + case tCurrentEventsChannelNumber1: + UpdateEvents(); + return mEvents.Count() > event + ? (cxType)mEvents.Get(event)->channelNumber + : (cxType)false; + + case tCurrentEventsChannelName3: + event++; + case tCurrentEventsChannelName2: + event++; + case tCurrentEventsChannelName1: + UpdateEvents(); + return mEvents.Count() > event + ? (cxType)mEvents.Get(event)->channelName.c_str() + : (cxType)false; + + case tCurrentEventsIsRecording3: + event++; + case tCurrentEventsIsRecording2: + event++; + case tCurrentEventsIsRecording1: + UpdateEvents(); + return mEvents.Count() > event + ? (cxType)mEvents.Get(event)->isRecording + : (cxType)false; + + case tTimerConflicts: +#if VDRVERSNUM >= 10330 + if (Text2SkinSetup.CheckTimerConflict) { + if (mRender->mUpdate.timerConflict) { + Epgsearch_lastconflictinfo_v1_0 conflict; + mRender->mUpdate.timerConflict = false; + + if (cPluginManager::CallFirstService("Epgsearch-lastconflictinfo-v1.0", &conflict)) + mTimerConflicts = conflict.relevantConflicts; + else + mTimerConflicts = 0; + } + return mTimerConflicts; + } else +#endif + return 0; + +#if VDRVERSNUM >= 10325 +#if VDRVERSNUM >= 10338 + case tReplayName: + return mReplay != NULL + ? (cxType)mReplay->Name() + : (cxType)false; + + case tReplayDateTime: + return mReplay != NULL + ? (cxType)TimeType(mReplay->start, Token.Attrib.Text) + : (cxType)false; + + case tReplayShortText: + return mReplay != NULL + ? (cxType)mReplay->Info()->ShortText() + : (cxType)false; + + case tReplayDescription: + return mReplay != NULL + ? (cxType)mReplay->Info()->Description() + : (cxType)false; +#else + case tReplayName: + return (cxType)false; + + case tReplayDateTime: + return (cxType)false; + + case tReplayShortText: + return (cxType)false; + + case tReplayDescription: + return (cxType)false; +#endif + + case tReplayLanguageCode: + if (mReplay) { + const cComponents *components = mReplay->Info()->Components(); + if (components) { + int index = Token.Attrib.Number; + + // don't return language-code for the video-stream + for (int i = 0; i < components->NumComponents(); i++) { + const tComponent *c = components->Component(i); + if (c->stream != 2) // only audio-streams + index++; + { + std::string buffer(c->language); + if (c->type == 1) + buffer.append("MONO"); + if ((c->type == 2) || (c->type == 4)) + buffer.append("DUAL"); + if (c->type == 5) + buffer.append("DD"); + return (cxType)buffer.c_str(); + } + } + } + } + return false; + + case tReplayLanguageDescription: + if (mReplay) { + const cComponents *components = mReplay->Info()->Components(); + if (components) { + int index = Token.Attrib.Number; + + // don't return language-code for the video-stream + for (int i = 0; i < components->NumComponents(); i++) { + const tComponent *c = components->Component(i); + if (c->stream != 2) // only audio-streams + index++; + if (i == index) + return (cxType)c->description; + } + } + } + return false; + + case tReplayVideoAR: + if (mReplay) { + const cComponents *components = mReplay->Info()->Components(); + if (components) { + for (int i = 0; i < components->NumComponents(); i++) { + const tComponent *c = components->Component(i); + if (c->stream == 1) { + switch (c->type) { + case 1: + return "4:3"; + case 3: + return "16:9"; + } + } + } + } + } + return false; +#endif + case tCurrentRecording: Dprintf("token attrib type is: %d, number: %d\n", Token.Attrib.Type, Token.Attrib.Number); if (Token.Attrib.Type == aNumber) { @@ -27,16 +27,50 @@ public: typedef std::string tRecordingInfo; typedef std::vector<tRecordingInfo> tRecordings; + struct tEvent : public cListObject { + time_t startTime; + time_t stopTime; + int channelNumber; + std::string channelName; + int priority; + bool isRecording; + std::string title; + + tEvent(cTimer *timer) : + startTime(timer->StartTime()), + stopTime(timer->StopTime()), + channelNumber(timer->Channel()->Number()), + channelName(timer->Channel()->Name()), + priority(timer->Priority()), + isRecording(timer->Recording()), + title(timer->File()) {} + + virtual int Compare(const cListObject &listObj) const { + tEvent *e = (tEvent *)&listObj; + int r = startTime - e->startTime; + if (r == 0) + r = e->priority - priority; + return r; + } + }; + + typedef std::vector<tEvent> tEvents; + private: + void UpdateEvents(void); + cText2SkinRender *mRender; eReplayMode mReplayMode; bool mReplayIsLoop; bool mReplayIsShuffle; tRecordings mRecordings; + const cRecording *mReplay; + cList<tEvent> mEvents; cMutex mRecordingsLock; uint mCurrentRecording; uint mNextRecording; int mLastLanguage; + int mTimerConflicts; protected: virtual void Replaying(const cControl *Control, const char *Name, @@ -45,6 +79,9 @@ protected: const char *FileName, bool On); virtual void OsdClear(void); + virtual void OsdCurrentItem(const char *Text); + virtual void OsdItem(const char *Text, int Index); + public: cText2SkinStatus(void); diff --git a/text2skin.c b/text2skin.c index f45496d..30c806b 100644 --- a/text2skin.c +++ b/text2skin.c @@ -7,6 +7,7 @@ */ #include "text2skin.h" +#include "bitmap.h" #include "setup.h" #include "menu.h" #include "i18n.h" @@ -23,6 +24,29 @@ cText2SkinPlugin::cText2SkinPlugin(void) { cText2SkinPlugin::~cText2SkinPlugin() { } +#if VDRVERSNUM >= 10331 +const char **cText2SkinPlugin::SVDRPHelpPages(void) +{ + static const char *HelpPages[] = { + "FLUS\n" + " Flush the image cache (useful if images have changed and the" + " current version should be loaded).", + NULL + }; + return HelpPages; +} + +cString cText2SkinPlugin::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) +{ + if (strcasecmp(Command, "FLUS") == 0) { + // we use the default reply code here + cText2SkinBitmap::FlushCache(); + return "image cache flushed."; + } + return NULL; +} +#endif + bool cText2SkinPlugin::Start(void) { #if VDRVERSNUM < 10507 RegisterI18n(Phrases); diff --git a/text2skin.h b/text2skin.h index b40ea54..19fa341 100644 --- a/text2skin.h +++ b/text2skin.h @@ -21,6 +21,10 @@ public: virtual ~cText2SkinPlugin(); virtual const char *Version(void) { return VERSION; } virtual const char *Description(void) { return tr(DESCRIPTION); } +#if VDRVERSNUM >= 10331 + virtual const char **SVDRPHelpPages(void); + virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); +#endif virtual bool Start(void); virtual cMenuSetupPage *SetupMenu(void); virtual bool SetupParse(const char *Name, const char *Value); diff --git a/xml/object.c b/xml/object.c index 6ea0ab6..4a3beec 100644 --- a/xml/object.c +++ b/xml/object.c @@ -144,14 +144,13 @@ const cFont *cxObject::Font(void) const { const cFont *font; - if ((font = cText2SkinFont::Load(SkinPath() + "/fonts", mFontFace, mFontSize, mFontWidth)) - != NULL) - return font; - if ((font = cText2SkinFont::Load(SkinPath() + "/" + mSkin->Name(), mFontFace, mFontSize, mFontWidth)) != NULL) return font; - + + if ((font = cText2SkinFont::Load(SkinPath() + "/fonts", mFontFace, mFontSize, mFontWidth)) != NULL) + return font; + return cFont::GetFont(fontOsd); } diff --git a/xml/string.c b/xml/string.c index afc0c7a..3f671bd 100644 --- a/xml/string.c +++ b/xml/string.c @@ -11,7 +11,12 @@ static const char *Tokens[__COUNT_TOKEN__] = { // Channel Display "ChannelNumber", "ChannelName", "ChannelShortName", "ChannelBouquet", "ChannelPortal", "ChannelSource", "ChannelID", "PresentStartDateTime", "PresentVPSDateTime", + "CurrentEventsTitle1", "CurrentEventsStartDateTime1", "CurrentEventsStopDateTime1", "CurrentEventsChannelNumber1", "CurrentEventsChannelName1", "CurrentEventsIsRecording1", + "CurrentEventsTitle2", "CurrentEventsStartDateTime2", "CurrentEventsStopDateTime2", "CurrentEventsChannelNumber2", "CurrentEventsChannelName2", "CurrentEventsIsRecording2", + "CurrentEventsTitle3", "CurrentEventsStartDateTime3", "CurrentEventsStopDateTime3", "CurrentEventsChannelNumber3", "CurrentEventsChannelName3", "CurrentEventsIsRecording3", + "TimerConflicts", "PresentEndDateTime", "PresentDuration", "PresentProgress", "PresentRemaining", + "PresentLanguageCode", "PresentLanguageDescription", "PresentVideoAR", "PresentEventID", "PresentTitle", "PresentShortText", "PresentDescription", "FollowingStartDateTime", "FollowingVPSDateTime", "FollowingEndDateTime", "FollowingDuration", "FollowingTitle", "FollowingShortText", "FollowingDescription", "Language", @@ -30,6 +35,8 @@ static const char *Tokens[__COUNT_TOKEN__] = { // Replay Display "ReplayTitle", "ReplayPositionIndex", "ReplayDurationIndex", "ReplayPrompt", + "ReplayName", "ReplayDateTime", "ReplayShortText", "ReplayDescription", + "ReplayLanguageCode", "ReplayLanguageDescription", "ReplayVideoAR", "IsPlaying", "ReplayIsPlaying", "IsFastForward", "ReplayIsFastForward", "IsFastRewind", "ReplayIsFastRewind", "IsSlowForward", "ReplayIsSlowForward", "IsSlowRewind", "ReplayIsSlowRewind", "IsPausing", "ReplayIsPausing", @@ -38,8 +45,10 @@ static const char *Tokens[__COUNT_TOKEN__] = { // Menu Page "MenuTitle", "MenuGroup", "IsMenuGroup", "MenuItem", "IsMenuItem", "MenuCurrent", - "IsMenuCurrent", "MenuText", "RecordingName", "RecordingDateTime", "RecordingTitle", + "IsMenuCurrent", "MenuText", "RecordingName", "RecordingFilename", "RecordingDateTime", "RecordingTitle", "RecordingShortText", "RecordingDescription", "RecordingLanguageCode", + "FrontendSTR", "FrontendSNR", "FrontendHasLock", "FrontendHasSignal", "RecordingPriority", "RecordingLifetime", + "RecordingVideoAR", "RecordingSize", "RecordingLength", "RecordingCuttedLength", "OsdWidth", "OsdHeight", "RecordingLanguageDescription", "ButtonRed", "ButtonGreen", "ButtonYellow", "ButtonBlue", "CanScrollUp", "CanScrollDown" }; diff --git a/xml/string.h b/xml/string.h index 7c3f30f..a459253 100644 --- a/xml/string.h +++ b/xml/string.h @@ -26,10 +26,33 @@ enum exToken { // next 9 also in Menu tPresentStartDateTime, tPresentVPSDateTime, + tCurrentEventsTitle1, + tCurrentEventsStartDateTime1, + tCurrentEventsStopDateTime1, + tCurrentEventsChannelNumber1, + tCurrentEventsChannelName1, + tCurrentEventsIsRecording1, + tCurrentEventsTitle2, + tCurrentEventsStartDateTime2, + tCurrentEventsStopDateTime2, + tCurrentEventsChannelNumber2, + tCurrentEventsChannelName2, + tCurrentEventsIsRecording2, + tCurrentEventsTitle3, + tCurrentEventsStartDateTime3, + tCurrentEventsStopDateTime3, + tCurrentEventsChannelNumber3, + tCurrentEventsChannelName3, + tCurrentEventsIsRecording3, + tTimerConflicts, tPresentEndDateTime, tPresentDuration, tPresentProgress, tPresentRemaining, + tPresentLanguageCode, + tPresentLanguageDescription, + tPresentVideoAR, + tPresentEventID, tPresentTitle, tPresentShortText, tPresentDescription, @@ -85,6 +108,13 @@ enum exToken { tReplayPositionIndex, tReplayDurationIndex, tReplayPrompt, + tReplayName, + tReplayDateTime, + tReplayShortText, + tReplayDescription, + tReplayLanguageCode, + tReplayLanguageDescription, + tReplayVideoAR, tIsPlaying, tReplayIsPlaying, // alias tIsFastForward, @@ -115,11 +145,24 @@ enum exToken { tMenuText, // Recordings Page tRecordingName, + tRecordingFilename, tRecordingDateTime, tRecordingTitle, tRecordingShortText, tRecordingDescription, tRecordingLanguageCode, + tFrontendSTR, + tFrontendSNR, + tFrontendHasLock, + tFrontendHasSignal, + tRecordingPriority, + tRecordingLifetime, + tRecordingVideoAR, + tRecordingSize, + tRecordingLength, + tRecordingCuttedLength, + tOsdWidth, + tOsdHeight, tRecordingLanguageDescription, // next four also in Channel and Replay display (if supported by vdr/plugin) tButtonRed, |