diff --git a/HISTORY b/HISTORY index 9950b38..4698d9f 100644 --- a/HISTORY +++ b/HISTORY @@ -43,7 +43,8 @@ VDR Plugin 'tvguide' Revision History - Added setup option to switch functionality of keys "Blue" and "OK" - Setup option to hide schedules time display in horizontal EPG grids -Version 0.0.6 +2013-07-08: Version 0.0.6 - added frame around scaled video picture - added theme "keep it simple" (thanks @saman) - display of additional EPG pictures in detailed epg view +- Introduction of "Search & Recording" Menu diff --git a/Makefile b/Makefile index df0ba33..b668830 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ LIBDIR = $(call PKGCFG,libdir) LOCDIR = $(call PKGCFG,locdir) PLGCFG = $(call PKGCFG,plgcfg) VDRCONFDIR= $(call PKGCFG,configdir) +PLGRESDIR = $(call PKGCFG,resdir)/plugins/$(PLUGIN) TMPDIR ?= /tmp ### The compiler options: @@ -100,6 +101,7 @@ i18n: $(I18Nmo) $(I18Npot) install-i18n: $(I18Nmsgs) ### Targets: + $(SOFILE): $(OBJS) $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) $(LIBS) -o $@ @@ -110,7 +112,11 @@ install-themes: @mkdir -p $(DESTDIR)$(VDRCONFDIR)/themes cp themes/* $(DESTDIR)$(VDRCONFDIR)/themes -install: install-lib install-i18n install-themes +install-icons: + mkdir -p $(DESTDIR)$(PLGRESDIR)/icons + cp -r icons/* $(DESTDIR)$(PLGRESDIR)/icons + +install: install-lib install-i18n install-themes install-icons dist: $(I18Npo) clean @-rm -rf $(TMPDIR)/$(ARCHIVE) diff --git a/channelcolumn.c b/channelcolumn.c index dae36a0..8ed34b0 100644 --- a/channelcolumn.c +++ b/channelcolumn.c @@ -5,6 +5,7 @@ cChannelColumn::cChannelColumn(int num, const cChannel *channel, cMyTime *myTime this->num = num; this->myTime = myTime; hasTimer = channel->HasTimer(); + hasSwitchTimer = SwitchTimers.ChannelInSwitchList(channel); schedulesLock = new cSchedulesLock(false, 100); header = NULL; } @@ -339,6 +340,22 @@ cGrid *cChannelColumn::addDummyGrid(time_t start, time_t end, cGrid *firstGrid, return dummy; } +void cChannelColumn::SetTimers() { + hasTimer = channel->HasTimer(); + hasSwitchTimer = SwitchTimers.ChannelInSwitchList(channel); + for (cGrid *grid = grids.First(); grid; grid = grids.Next(grid)) { + bool gridHadTimer = grid->HasTimer(); + grid->SetTimer(); + if (gridHadTimer != grid->HasTimer()) + grid->SetDirty(); + bool gridHadSwitchTimer = grid->HasSwitchTimer(); + grid->SetSwitchTimer(); + if (gridHadSwitchTimer != grid->HasSwitchTimer()) + grid->SetDirty(); + grid->Draw(); + } +} + void cChannelColumn::dumpGrids() { esyslog("tvguide: ------Channel %s: %d entires ---------", channel->Name(), grids.Count()); int i=1; diff --git a/channelcolumn.h b/channelcolumn.h index a84f9a6..1dbd174 100644 --- a/channelcolumn.h +++ b/channelcolumn.h @@ -16,6 +16,7 @@ private: cSchedulesLock *schedulesLock; const cSchedules *schedules; bool hasTimer; + bool hasSwitchTimer; cGrid *addEpgGrid(const cEvent *event, cGrid *firstGrid, bool color); cGrid *addDummyGrid(time_t start, time_t end, cGrid *firstGrid, bool color); public: @@ -42,8 +43,11 @@ public: void ClearOutdatedEnd(); int GetNum() {return num;}; void SetNum(int num) {this->num = num;}; - void setTimer() {hasTimer = true;}; + void setTimer() {hasTimer = channel->HasTimer();}; bool HasTimer() { return hasTimer; }; + void setSwitchTimer() {hasSwitchTimer = SwitchTimers.ChannelInSwitchList(channel);}; + bool HasSwitchTimer() { return hasSwitchTimer; }; + void SetTimers(); void clearGrids(); void dumpGrids(); }; diff --git a/config.c b/config.c index ac767fa..408374f 100644 --- a/config.c +++ b/config.c @@ -81,6 +81,8 @@ cTvguideConfig::cTvguideConfig() { FontGridHorizontalSmallDelta = 0; FontTimeLineDateHorizontalDelta = 0; FontTimeLineTimeHorizontalDelta = 0; + FontRecMenuItemDelta = 0; + FontRecMenuItemSmallDelta = 0; //Common Fonts FontButton = NULL; FontDetailView = NULL; @@ -102,7 +104,9 @@ cTvguideConfig::cTvguideConfig() { FontGridHorizontalSmall = NULL; FontTimeLineDateHorizontal = NULL; FontTimeLineTimeHorizontal = NULL; - + //Fonts for RecMenu + FontRecMenuItem = NULL; + FontRecMenuItemSmall = NULL; timeFormat = 1; themeIndex = 4; useBlending = 2; @@ -133,6 +137,8 @@ cTvguideConfig::~cTvguideConfig() { delete FontGridHorizontalSmall; delete FontTimeLineDateHorizontal; delete FontTimeLineTimeHorizontal; + delete FontRecMenuItem; + delete FontRecMenuItemSmall; } void cTvguideConfig::setDynamicValues(int width, int height) { @@ -207,7 +213,9 @@ void cTvguideConfig::SetFonts(void){ FontGridHorizontalSmall = cFont::CreateFont(*fontname, rowHeight/4 + FontGridHorizontalSmallDelta); FontTimeLineDateHorizontal = cFont::CreateFont(*fontname, timeLineHeight/2 + 5 + FontTimeLineDateHorizontalDelta); FontTimeLineTimeHorizontal = cFont::CreateFont(*fontname, timeLineHeight/2 + FontTimeLineTimeHorizontalDelta); - + //Fonts for RecMenu + FontRecMenuItem = cFont::CreateFont(*fontname, osdHeight/30 + FontRecMenuItemDelta); + FontRecMenuItemSmall = cFont::CreateFont(*fontname, osdHeight/40 + FontRecMenuItemSmallDelta); } void cTvguideConfig::SetBlending(void) { @@ -228,6 +236,10 @@ void cTvguideConfig::SetImagesPath(cString path) { epgImagePath = path; } +void cTvguideConfig::SetIconsPath(cString path) { + iconPath = path; +} + void cTvguideConfig::loadTheme() { cThemes themes; themes.Load(*cString("tvguide")); @@ -295,6 +307,8 @@ bool cTvguideConfig::SetupParse(const char *Name, const char *Value) { else if (strcmp(Name, "FontGridHorizontalSmallDelta") == 0) FontGridHorizontalSmallDelta = atoi(Value); else if (strcmp(Name, "FontTimeLineDateHorizontalDelta") == 0) FontTimeLineDateHorizontalDelta = atoi(Value); else if (strcmp(Name, "FontTimeLineTimeHorizontalDelta") == 0) FontTimeLineTimeHorizontalDelta = atoi(Value); + else if (strcmp(Name, "FontRecMenuItemDelta") == 0) FontRecMenuItemDelta = atoi(Value); + else if (strcmp(Name, "FontRecMenuItemSmallDelta") == 0) FontRecMenuItemSmallDelta = atoi(Value); else if (strcmp(Name, "displayRerunsDetailEPGView") == 0) displayRerunsDetailEPGView = atoi(Value); else if (strcmp(Name, "numReruns") == 0) numReruns = atoi(Value); else if (strcmp(Name, "useSubtitleRerun") == 0) useSubtitleRerun = atoi(Value); diff --git a/config.h b/config.h index 3269e50..3afd857 100644 --- a/config.h +++ b/config.h @@ -10,6 +10,7 @@ class cTvguideConfig { ~cTvguideConfig(); void SetLogoPath(cString path); void SetImagesPath(cString path); + void SetIconsPath(cString path); void SetBlending(void); int showMainMenuEntry; int osdWidth; @@ -61,6 +62,7 @@ class cTvguideConfig { int epgImageWidthLarge; int epgImageHeightLarge; cString epgImagePath; + cString iconPath; int fontIndex; const char *fontNameDefault; int FontButtonDelta; @@ -83,6 +85,8 @@ class cTvguideConfig { int FontGridHorizontalSmallDelta; int FontTimeLineDateHorizontalDelta; int FontTimeLineTimeHorizontalDelta; + int FontRecMenuItemDelta; + int FontRecMenuItemSmallDelta; const cFont *FontChannelHeader; const cFont *FontChannelHeaderHorizontal; const cFont *FontChannelGroups; @@ -103,6 +107,8 @@ class cTvguideConfig { const cFont *FontDetailHeader; const cFont *FontMessageBox; const cFont *FontMessageBoxLarge; + const cFont *FontRecMenuItem; + const cFont *FontRecMenuItemSmall; int timeFormat; int themeIndex; int useBlending; diff --git a/detailview.c b/detailview.c index f06229f..0ce16f0 100644 --- a/detailview.c +++ b/detailview.c @@ -2,12 +2,9 @@ #include #include "detailview.h" -cDetailView::cDetailView(cGrid *grid) { - this->grid = grid; - this->event = grid->GetEvent(); +cDetailView::cDetailView(const cEvent *event) { + this->event = event; imgScrollBar = NULL; - FrameTime = 40; // ms - FadeTime = 500; // ms borderWidth = 100; //px scrollBarWidth = 40; headerHeight = max (40 + 3 * tvguideConfig.FontDetailHeader->Height(), // border + 3 Lines @@ -48,24 +45,18 @@ bool cDetailView::setContentDrawportHeight() { void cDetailView::createPixmaps() { header = new cStyledPixmap(osdManager.requestPixmap(5, cRect(borderWidth, borderWidth, tvguideConfig.osdWidth - 2*borderWidth, headerHeight), cRect::Null)); - header->SetAlpha(0); headerLogo = osdManager.requestPixmap(6, cRect(borderWidth, borderWidth, tvguideConfig.osdWidth - 2*borderWidth, headerHeight), cRect::Null); headerLogo->Fill(clrTransparent); - headerLogo->SetAlpha(0); headerBack = osdManager.requestPixmap(4, cRect(borderWidth, borderWidth, tvguideConfig.osdWidth - 2*borderWidth, headerHeight), cRect::Null); - headerBack->SetAlpha(0); headerBack->Fill(clrBlack); header->setColor(theme.Color(clrHeader), theme.Color(clrHeaderBlending)); content = osdManager.requestPixmap(5, cRect(borderWidth, borderWidth + headerHeight, tvguideConfig.osdWidth - 2*borderWidth - scrollBarWidth, tvguideConfig.osdHeight-2*borderWidth-headerHeight), cRect(0,0, tvguideConfig.osdWidth - 2*borderWidth - scrollBarWidth, max(heightContent, tvguideConfig.osdHeight-2*borderWidth-headerHeight))); - content->SetAlpha(0); header->setColor(theme.Color(clrHeader), theme.Color(clrHeaderBlending)); scrollBar = osdManager.requestPixmap(5, cRect(tvguideConfig.osdWidth-borderWidth-scrollBarWidth, borderWidth + headerHeight, scrollBarWidth, tvguideConfig.osdHeight-2*borderWidth-headerHeight)); - scrollBar->SetAlpha(0); footer = osdManager.requestPixmap(5, cRect(borderWidth, borderWidth + headerHeight + content->ViewPort().Height(), tvguideConfig.osdWidth - 2*borderWidth, 3)); - footer->SetAlpha(0); footer->Fill(theme.Color(clrBorder)); } @@ -79,7 +70,8 @@ void cDetailView::drawHeader() { cImageLoader imgLoader; bool logoDrawn = false; if (!tvguideConfig.hideChannelLogos) { - if (imgLoader.LoadLogo(grid->column->getChannel()->Name(), logoWidth, logoHeight)) { + cString channelName = Channels.GetByChannelID(event->ChannelID())->Name(); + if (imgLoader.LoadLogo(*channelName, logoWidth, logoHeight)) { cImage logo = imgLoader.GetImage(); headerLogo->DrawImage(cPoint(10, (header->Height() - logoHeight)/2), logo); logoDrawn = true; @@ -293,28 +285,21 @@ void cDetailView::drawEPGPictures(int height) { } } -void cDetailView::Action(void) { - drawHeader(); - drawContent(); - drawScrollbar(); - uint64_t Start = cTimeMs::Now(); - while (true) { - uint64_t Now = cTimeMs::Now(); - cPixmap::Lock(); - double t = min(double(Now - Start) / FadeTime, 1.0); - int Alpha = t * ALPHA_OPAQUE; - header->SetAlpha(Alpha); - headerBack->SetAlpha(Alpha); - headerLogo->SetAlpha(Alpha); - content->SetAlpha(Alpha); - scrollBar->SetAlpha(Alpha); - footer->SetAlpha(Alpha); - osdManager.flush(); - cPixmap::Unlock(); - int Delta = cTimeMs::Now() - Now; - if (Delta < FrameTime) - cCondWait::SleepMs(FrameTime - Delta); - if ((Now - Start) > FadeTime) +eOSState cDetailView::ProcessKey(eKeys Key) { + eOSState state = osContinue; + switch (Key & ~k_Repeat) { + case kUp: + scrollUp(); + osdManager.flush(); + break; + case kDown: + scrollDown(); + osdManager.flush(); + break; + case kOk: + case kBack: + state = osEnd; break; } + return state; } \ No newline at end of file diff --git a/detailview.h b/detailview.h index 3d72795..6b59bbb 100644 --- a/detailview.h +++ b/detailview.h @@ -5,9 +5,8 @@ class cEpgGrid; -class cDetailView : public cThread { +class cDetailView { private: - cGrid *grid; cStyledPixmap *header; cPixmap *headerLogo; cPixmap *headerBack; @@ -16,8 +15,6 @@ private: cPixmap *footer; const cEvent *event; cImage *imgScrollBar; - int FrameTime; - int FadeTime; cTextWrapper description; cTextWrapper reruns; int borderWidth; @@ -29,19 +26,19 @@ private: int numEPGPics; bool contentScrollable; void loadReruns(void); - void drawHeader(); - void drawContent(); - void drawScrollbar(); int heightEPGPics(void); void drawEPGPictures(int height); cImage *createScrollbar(int width, int height, tColor clrBgr, tColor clrBlend); - virtual void Action(void); public: - cDetailView(cGrid *grid); + cDetailView(const cEvent *event); virtual ~cDetailView(void); void createPixmaps(); + void drawHeader(); + void drawContent(); + void drawScrollbar(); void scrollUp(); void scrollDown(); + eOSState ProcessKey(eKeys Key); }; #endif //__TVGUIDE_DETAILVIEW_H \ No newline at end of file diff --git a/dummygrid.c b/dummygrid.c index 97a8d1a..42025e0 100644 --- a/dummygrid.c +++ b/dummygrid.c @@ -5,6 +5,7 @@ cDummyGrid::cDummyGrid(cChannelColumn *c, time_t start, time_t end) : cGrid(c) { this->end = end; strText = tr("No EPG Information available"); dummy = true; + hasTimer = false; } cDummyGrid::~cDummyGrid(void) { diff --git a/epggrid.c b/epggrid.c index 97a57f8..434a612 100644 --- a/epggrid.c +++ b/epggrid.c @@ -7,6 +7,9 @@ cEpgGrid::cEpgGrid(cChannelColumn *c, const cEvent *event) : cGrid(c) { hasTimer = false; if (column->HasTimer()) hasTimer = event->HasTimer(); + hasSwitchTimer = false; + if (column->HasSwitchTimer()) + hasSwitchTimer = SwitchTimers.EventInSwitchList(event); dummy = false; } @@ -89,8 +92,6 @@ void cEpgGrid::drawText() { pixmap->DrawText(cPoint(borderWidth, borderWidth + offset + i*textHeight), extText->GetLine(i), colorText, colorTextBack, tvguideConfig.FontGridSmall); } } - if (hasTimer) - drawRecIcon(); } else if (tvguideConfig.displayMode == eHorizontal) { if (Width()/tvguideConfig.minutePixel < 10) { int titleY = (tvguideConfig.rowHeight - tvguideConfig.FontGridHorizontal->Height())/2; @@ -107,14 +108,21 @@ void cEpgGrid::drawText() { } pixmap->DrawText(cPoint(borderWidth, titleY), *strTitle, colorText, colorTextBack, tvguideConfig.FontGridHorizontal); } + if (hasSwitchTimer) + drawIcon("Switch", theme.Color(clrButtonYellow)); + if (hasTimer) + drawIcon("REC", theme.Color(clrButtonRed)); } -void cEpgGrid::drawRecIcon() { - cString recIconText("REC"); - int width = tvguideConfig.FontGrid->Width(*recIconText)+2*borderWidth; - int height = tvguideConfig.FontGrid->Height()+10; - pixmap->DrawRectangle( cRect(Width() - width - borderWidth, Height() - height - borderWidth, width, height), theme.Color(clrButtonRed)); - pixmap->DrawText(cPoint(Width() - width, Height() - height - borderWidth/2), *recIconText, theme.Color(clrFont), theme.Color(clrButtonRed), tvguideConfig.FontGrid); +void cEpgGrid::drawIcon(cString iconText, tColor color) { + + const cFont *font = (tvguideConfig.displayMode == eVertical) + ?tvguideConfig.FontGrid + :tvguideConfig.FontGridHorizontalSmall; + int textWidth = font->Width(*iconText)+2*borderWidth; + int textHeight = font->Height()+10; + pixmap->DrawRectangle( cRect(Width() - textWidth - borderWidth, Height() - textHeight - borderWidth, textWidth, textHeight), color); + pixmap->DrawText(cPoint(Width() - textWidth, Height() - textHeight - borderWidth/2), *iconText, theme.Color(clrFont), color, font); } cString cEpgGrid::getTimeString(void) { diff --git a/epggrid.h b/epggrid.h index d7cae70..0465d06 100644 --- a/epggrid.h +++ b/epggrid.h @@ -8,9 +8,8 @@ private: const cEvent *event; cTextWrapper *extText; cString timeString; - bool hasTimer; void drawText(); - void drawRecIcon(); + void drawIcon(cString iconText, tColor color); time_t Duration(void) { return event->Duration(); }; public: cEpgGrid(cChannelColumn *c, const cEvent *event); @@ -21,7 +20,8 @@ public: const cEvent *GetEvent() {return event;}; time_t StartTime() { return event->StartTime(); }; time_t EndTime() { return event->EndTime(); }; - void setTimer() {hasTimer = true;}; + void SetTimer() {hasTimer = event->HasTimer();}; + void SetSwitchTimer() {hasSwitchTimer = SwitchTimers.EventInSwitchList(event);}; cString getTimeString(void); void debug(); }; diff --git a/footer.c b/footer.c index 4c26336..7a19053 100644 --- a/footer.c +++ b/footer.c @@ -38,7 +38,7 @@ void cFooter::DrawButton(const char *text, tColor color, tColor borderColor, int } void cFooter::drawRedButton() { - cString text(tr("Set Timer")); + cString text(tr("Search & Rec")); DrawButton(*text, theme.Color(clrButtonRed), theme.Color(clrButtonRedBorder), 0); } @@ -78,4 +78,4 @@ void cFooter::UpdateGroupButtons(const cChannel *channel) { drawGreenButton(channelGroups->GetPrev(group)); drawYellowButton(channelGroups->GetNext(group)); } -} \ No newline at end of file +} diff --git a/grid.h b/grid.h index 42ad41d..d51271b 100644 --- a/grid.h +++ b/grid.h @@ -12,6 +12,8 @@ protected: bool isColor1; bool active; bool dirty; + bool hasTimer; + bool hasSwitchTimer; bool intersects(cGrid *neighbor); virtual time_t Duration(void) {}; virtual void drawText(void) {}; @@ -37,10 +39,13 @@ public: virtual void SetStartTime(time_t start) {}; virtual void SetEndTime(time_t end) {}; int calcOverlap(cGrid *neighbor); - virtual void setTimer() {}; + virtual void SetTimer() {}; + virtual void SetSwitchTimer() {}; virtual cString getText(void) { return cString("");}; virtual cString getTimeString(void) { return cString("");}; bool Active(void) { return active; }; + bool HasTimer() {return hasTimer;}; + bool HasSwitchTimer() {return hasSwitchTimer;}; bool isDummy() { return dummy; }; virtual void debug() {}; }; diff --git a/headergrid.c b/headergrid.c index 280ea46..4bb25c8 100644 --- a/headergrid.c +++ b/headergrid.c @@ -24,8 +24,8 @@ void cHeaderGrid::createBackground(int num) { width = tvguideConfig.channelHeaderWidth; height = tvguideConfig.rowHeight; } - pixmap = osdManager.requestPixmap(2, cRect(x, y, width, height)); - pixmapLogo = osdManager.requestPixmap(3, cRect(x, y, width, height)); + pixmap = osdManager.requestPixmap(1, cRect(x, y, width, height)); + pixmapLogo = osdManager.requestPixmap(2, cRect(x, y, width, height)); if ((!pixmap) || (!pixmapLogo)){ return; } diff --git a/icons/arrow_left.png b/icons/arrow_left.png new file mode 100644 index 0000000..a65e47d Binary files /dev/null and b/icons/arrow_left.png differ diff --git a/icons/arrow_right.png b/icons/arrow_right.png new file mode 100644 index 0000000..97fa123 Binary files /dev/null and b/icons/arrow_right.png differ diff --git a/icons/delete_active.png b/icons/delete_active.png new file mode 100644 index 0000000..b01dc5c Binary files /dev/null and b/icons/delete_active.png differ diff --git a/icons/delete_inactive.png b/icons/delete_inactive.png new file mode 100644 index 0000000..b327f07 Binary files /dev/null and b/icons/delete_inactive.png differ diff --git a/icons/edit_active.png b/icons/edit_active.png new file mode 100644 index 0000000..5b3b667 Binary files /dev/null and b/icons/edit_active.png differ diff --git a/icons/edit_inactive.png b/icons/edit_inactive.png new file mode 100644 index 0000000..d4f59a1 Binary files /dev/null and b/icons/edit_inactive.png differ diff --git a/icons/icon_backspace.png b/icons/icon_backspace.png new file mode 100644 index 0000000..e20b7bd Binary files /dev/null and b/icons/icon_backspace.png differ diff --git a/icons/icon_del_ins.png b/icons/icon_del_ins.png new file mode 100644 index 0000000..3d06162 Binary files /dev/null and b/icons/icon_del_ins.png differ diff --git a/icons/icon_shift.png b/icons/icon_shift.png new file mode 100644 index 0000000..998d7fa Binary files /dev/null and b/icons/icon_shift.png differ diff --git a/icons/info_active.png b/icons/info_active.png new file mode 100644 index 0000000..7f1ccf7 Binary files /dev/null and b/icons/info_active.png differ diff --git a/icons/info_inactive.png b/icons/info_inactive.png new file mode 100644 index 0000000..9a79e95 Binary files /dev/null and b/icons/info_inactive.png differ diff --git a/icons/no.png b/icons/no.png new file mode 100644 index 0000000..3630d29 Binary files /dev/null and b/icons/no.png differ diff --git a/icons/record_active.png b/icons/record_active.png new file mode 100644 index 0000000..8c42156 Binary files /dev/null and b/icons/record_active.png differ diff --git a/icons/record_inactive.png b/icons/record_inactive.png new file mode 100644 index 0000000..6720a6f Binary files /dev/null and b/icons/record_inactive.png differ diff --git a/icons/yes.png b/icons/yes.png new file mode 100644 index 0000000..ec30010 Binary files /dev/null and b/icons/yes.png differ diff --git a/imageloader.c b/imageloader.c index 31e0d0c..292d183 100644 --- a/imageloader.c +++ b/imageloader.c @@ -59,8 +59,19 @@ bool cImageLoader::LoadAdditionalEPGImage(cString name) { return true; } +bool cImageLoader::LoadIcon(const char *cIcon, int size) { + if (size==0) + return false; + bool success = false; + success = LoadImage(cString(cIcon), tvguideConfig.iconPath, "png"); + if (!success) + return false; + buffer.sample(Geometry(size, size)); + return true; +} + bool cImageLoader::DrawBackground(tColor back, tColor blend, int width, int height) { - if ((width < 1) || (height < 1)) + if ((width < 1) || (height < 1) || (width > 1920) || (height > 1080)) return false; Color Back = Argb2Color(back); Color Blend = Argb2Color(blend); diff --git a/imageloader.h b/imageloader.h index a38a5a0..bc2b7dd 100644 --- a/imageloader.h +++ b/imageloader.h @@ -17,6 +17,7 @@ public: bool LoadLogo(const char *logo, int width, int height); bool LoadEPGImage(int eventID); bool LoadAdditionalEPGImage(cString name); + bool LoadIcon(const char *cIcon, int size); bool DrawBackground(tColor back, tColor blend, int width, int height); private: Image buffer; diff --git a/osdmanager.c b/osdmanager.c index 8fdd7f6..c74162d 100644 --- a/osdmanager.c +++ b/osdmanager.c @@ -1,6 +1,3 @@ -#include -#include - #ifndef __TVGUIDE_OSDMANAGER_H #define __TVGUIDE_OSDMANAGER_H @@ -55,32 +52,4 @@ void cOsdManager::releasePixmap(cPixmap *pixmap) { if (!pixmap) return; osd->DestroyPixmap(pixmap); -} - -static std::string CutText(std::string text, int width, const cFont *font) { - if (width <= font->Size()) - return text.c_str(); - if (font->Width(text.c_str()) < width) - return text.c_str(); - cTextWrapper twText; - twText.Set(text.c_str(), font, width); - std::string cuttedTextNative = twText.GetLine(0); - std::stringstream sstrText; - sstrText << cuttedTextNative << "..."; - std::string cuttedText = sstrText.str(); - int actWidth = font->Width(cuttedText.c_str()); - if (actWidth > width) { - int overlap = actWidth - width; - int charWidth = font->Width("."); - if (charWidth == 0) - charWidth = 1; - int cutChars = overlap / charWidth; - if (cutChars > 0) { - cuttedTextNative = cuttedTextNative.substr(0, cuttedTextNative.length() - cutChars); - std::stringstream sstrText2; - sstrText2 << cuttedTextNative << "..."; - cuttedText = sstrText2.str(); - } - } - return cuttedText; } \ No newline at end of file diff --git a/po/de_DE.po b/po/de_DE.po index e075e74..d1a58c6 100755 --- a/po/de_DE.po +++ b/po/de_DE.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: vdr-tvguide 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-06-03 09:00+0200\n" +"POT-Creation-Date: 2013-07-07 13:43+0200\n" "PO-Revision-Date: 2012-08-25 17:49+0200\n" "Last-Translator: Horst\n" "Language-Team: \n" @@ -21,8 +21,8 @@ msgstr "Wiederholungen dieser Sendung" msgid "No EPG Information available" msgstr "Keine EPG Daten verfügbar" -msgid "Set Timer" -msgstr "Aufnehmen" +msgid "Search & Rec" +msgstr "Search & Rec" msgid "Channels back" msgstr "Kanäle zurück" @@ -36,6 +36,336 @@ msgstr "Umschalten" msgid "Detailed EPG" msgstr "Detailiertes EPG" +msgid "Transp." +msgstr "Transp." + +msgid "Timer Conflict" +msgstr "Timer Konflikt" + +msgid "all Channels" +msgstr "alle Kanäle" + +msgid "unknown channel" +msgstr "unbekannter Kanal" + +msgid "Duration" +msgstr "Dauer" + +msgid "min" +msgstr "min" + +msgid "recorded at" +msgstr "aufgenommen am" + +msgid "from" +msgstr "von" + +msgid "Instant Record" +msgstr "Sofortaufnahme" + +msgid "Delete Timer" +msgstr "Timer löschen" + +msgid "Edit Timer" +msgstr "Timer bearbeiten" + +msgid "Create Series Timer" +msgstr "Serientimer anlegen" + +msgid "Create Search Timer" +msgstr "Suchtimer anlegen" + +msgid "Create Switch Timer" +msgstr "Umschalttimer anlegen" + +msgid "Delete Switch Timer" +msgstr "Umschalttimer löschen" + +msgid "Search" +msgstr "Suchen" + +msgid "Search in Recordings" +msgstr "In Aufnahmen suchen" + +msgid "Check for Timer Conflicts" +msgstr "Auf Timerkoflikte prüfen" + +msgid "Timer created" +msgstr "Timer angelegt" + +msgid "Timer NOT created" +msgstr "Timer NICHT angelegt" + +msgid "OK" +msgstr "OK" + +msgid "Timer deleted" +msgstr "Timer gelöscht" + +msgid "Timer" +msgstr "Timer" + +msgid "still recording - really delete?" +msgstr "Aufzeichnung läuft - wirklich löschen?" + +msgid "Yes" +msgstr "Ja" + +msgid "No" +msgstr "Nein" + +msgid "One" +msgstr "Ein" + +msgid "detected" +msgstr "gefunden" + +msgid "Timer Conflicts" +msgstr "Timerkonflikte" + +msgid "Show conflict" +msgstr "Konflikt zeigen" + +msgid "timers involved" +msgstr "Timer beteiligt" + +msgid "Ignore Conflicts" +msgstr "Konflikte ignorieren" + +msgid "Ignore Conflict" +msgstr "Konflikt ignorieren" + +msgid "No Timer Conflicts found" +msgstr "Keine Timerkonflikte gefunden" + +msgid "Close" +msgstr "Schließen" + +msgid "Timer Active" +msgstr "Timer aktiv" + +msgid "Priority" +msgstr "Priorität" + +msgid "Lifetime" +msgstr "Lebensdauer" + +msgid "Day" +msgstr "Tag" + +msgid "Timer start time" +msgstr "Timer Start Zeit" + +msgid "Timer stop time" +msgstr "Timer Stop Zeit" + +msgid "Save" +msgstr "Speichern" + +msgid "Cancel" +msgstr "Abbrechen" + +msgid "Create Series Timer based on" +msgstr "Serientimer anlegen basierend auf" + +msgid "Channel" +msgstr "Kanal" + +msgid "Series Timer start time" +msgstr "Serientimer Start Zeit" + +msgid "Series Timer stop time" +msgstr "Serientimer Stop Zeit" + +msgid "Days to record" +msgstr "Tage" + +msgid "Day to start" +msgstr "Beginnen am" + +msgid "Create Timer" +msgstr "Timer anlegen" + +msgid "Series Timer created" +msgstr "Serientimer angelegt" + +msgid "Start" +msgstr "Start" + +msgid "Stop" +msgstr "Stop" + +msgid "Configure Search Timer based on" +msgstr "Suchtimer konfigurieren basierend auf" + +msgid "Search Expression:" +msgstr "Suchausdruck:" + +msgid "Continue" +msgstr "Weiter" + +msgid "Configure Search Timer for Search String" +msgstr "Suchtimer konfigurieren für Suchbegriff" + +msgid "Manually configure Options" +msgstr "Optionen manuell konfigurieren" + +msgid "Use Template" +msgstr "Template benutzen" + +msgid "Creating Search Timer" +msgstr "Suchtimer anlegen" + +msgid "Search Term" +msgstr "Suchbegriff" + +msgid "Using Template" +msgstr "Template" + +msgid "Display Results for Search Timer" +msgstr "Ergebnisse für Suchtimer anzeigen" + +msgid "Use other Template" +msgstr "Anderes Template benutzen" + +msgid "Configure Search Timer Options for Search String" +msgstr "Suchtimer Optionen konfigurieren für Suchbegriff" + +msgid "whole term must appear" +msgstr "vollständiger Ausdruck" + +msgid "all terms must exist" +msgstr "alle Worte" + +msgid "one term must exist" +msgstr "ein Wort" + +msgid "exact match" +msgstr "exakt" + +msgid "regular expression" +msgstr "Regulärer Ausdruck" + +msgid "Search Mode" +msgstr "Suchmodus" + +msgid "Use Title" +msgstr "Titel benutzen" + +msgid "Use Subtitle" +msgstr "Untertitel benutzen" + +msgid "Use Description" +msgstr "Beschreibung benutzen" + +msgid "Limit Channels" +msgstr "Kanäle einschränken" + +msgid "Start Channel" +msgstr "Startkanal" + +msgid "Stop Channel" +msgstr "Stopkanal" + +msgid "Use Time" +msgstr "Zeit benutzen" + +msgid "Start after" +msgstr "Beginn nach" + +msgid "Start before" +msgstr "Beginn vor" + +msgid "search results for Search Timer" +msgstr "Treffer für Suchtimer" + +msgid "search result for Search Timer" +msgstr "Treffer für Suchtimer" + +msgid "Nothing found for Search String" +msgstr "Keine Treffer für Suchbegriff" + +msgid "Search Timer sucessfully created." +msgstr "Suchtimer erfolgreich angelegt" + +msgid "Search Timer update initialised" +msgstr "Suchtimer update initialisiert" + +msgid "Search Timer NOT sucessfully created" +msgstr "Suchtimer NICHT erfolgreich angelegt" + +msgid "Configure Options for Switchtimer" +msgstr "Optionen für Umschalttimer konfigurieren" + +msgid "Minutes before switching" +msgstr "Minuten vor umschalten" + +msgid "switch" +msgstr "umschalten" + +msgid "announce only" +msgstr "nur ankündigen" + +msgid "ask for switch" +msgstr "vor umschalten fragen" + +msgid "Switch Mode" +msgstr "Umschaltmodus" + +msgid "Create" +msgstr "Anlegen" + +msgid "Switch Timer sucessfully created" +msgstr "Umschalttimer erfolgreich angelegt" + +msgid "Switch Timer NOT sucessfully created" +msgstr "Umschalttimer NICHT erfolgreich angelegt" + +msgid "Switch Timer deleted" +msgstr "Umschalttimer gelöscht" + +msgid "Show Search Options" +msgstr "Suchoptionen anzeigen" + +msgid "Perform Search" +msgstr "Suche ausführen" + +msgid "Channel to Search" +msgstr "Suche auf Kanal" + +msgid "Search in title" +msgstr "In Titel suchen" + +msgid "Search in Subtitle" +msgstr "In Untertitel suchen" + +msgid "Search in Description" +msgstr "In Beschreibung suchen" + +msgid "search results for" +msgstr "Suchergebnisse für" + +msgid "search result for" +msgstr "Suchergebnis für" + +msgid "Adapt Search" +msgstr "Suche anpassen" + +msgid "Found" +msgstr " " + +msgid "recording" +msgstr "Aufnahme gefunden" + +msgid "recordings" +msgstr "Aufnahmen gefunden" + +msgid "for" +msgstr "für" + +msgid "No recordings found for" +msgstr "Keine Aufnahmen gefunden für" + msgid "General Settings" msgstr "Allgemeine Einstellungen" @@ -234,8 +564,8 @@ msgstr "Zeitleiste Datum Schriftgröße" msgid "Timeline Time Font Size" msgstr "Zeitleiste Zeit Schriftgröße" -msgid "Timer not set! There is already a timer for this item." -msgstr "Timer wurde nicht gesetzt! Es existiert bereits ein Timer für diese Sendung" +msgid "Search & Recording Menu Font Size" +msgstr "Suchen & Aufnehmen Menu Schriftgröße" -msgid "Timer set" -msgstr "Timer gesetzt" +msgid "Search & Recording Menu Small Font Size" +msgstr "Suchen & Aufnehmen Menu kleine Schriftgröße" diff --git a/recmanager.c b/recmanager.c new file mode 100644 index 0000000..df65cc3 --- /dev/null +++ b/recmanager.c @@ -0,0 +1,598 @@ +#include +#include +#include "recmanager.h" + +static int CompareRecording(const void *p1, const void *p2) { + return (int)((*(cRecording **)p1)->Start() - (*(cRecording **)p2)->Start()); +} + +bool TVGuideTimerConflict::timerInvolved(int involvedID) { + int numConflicts = timerIDs.size(); + for (int i=0; iToDescr()); + return timer; +} + +void cRecManager::DeleteTimer(const cEvent *event) { + cTimer *t = Timers.GetMatch(event); + if (!t) + return; + DeleteTimer(t); +} + +void cRecManager::DeleteTimer(int timerID) { + cTimer *t = Timers.Get(timerID); + if (!t) + return; + DeleteTimer(t); +} + +void cRecManager::DeleteTimer(cTimer *timer) { + if (timer->Recording()) { + timer->Skip(); + cRecordControls::Process(time(NULL)); + } + isyslog("timer %s deleted", *timer->ToDescr()); + Timers.Del(timer, true); + Timers.SetModified(); +} + +void cRecManager::SaveTimer(cTimer *timer, cRecMenu *menu) { + if (!timer) + return; + + bool active = menu->GetBoolValue(1); + int prio = menu->GetIntValue(2); + int lifetime = menu->GetIntValue(3); + time_t day = menu->GetTimeValue(4); + int start = menu->GetIntValue(5); + int stop = menu->GetIntValue(6); + + timer->SetDay(day); + timer->SetStart(start); + timer->SetStop(stop); + timer->SetPriority(prio); + timer->SetLifetime(lifetime); + + if (timer->HasFlags(tfActive) && !active) + timer->ClrFlags(tfActive); + else if (!timer->HasFlags(tfActive) && active) + timer->SetFlags(tfActive); + + timer->SetEventFromSchedule(); + Timers.SetModified(); +} + +bool cRecManager::IsRecorded(const cEvent *event) { + cTimer *timer = Timers.GetMatch(event); + if (!timer) + return false; + return timer->Recording(); +} + +std::vector cRecManager::CheckTimerConflict(void) { + /* TIMERCONFLICT FORMAT: + The result list looks like this for example when we have 2 timer conflicts at one time: + 1190232780:152|30|50#152#45:45|10|50#152#45 + '1190232780' is the time of the conflict in seconds since 1970-01-01. + It's followed by list of timers that have a conflict at this time: + '152|30|50#1 int editTimer(cTimer *timer, bool active, int prio, int start, int stop); + 52#45' is the description of the first conflicting timer. Here: + '152' is VDR's timer id of this timer as returned from VDR's LSTT command + '30' is the percentage of recording that would be done (0...100) + '50#152#45' is the list of concurrent timers at this conflict + '45|10|50#152#45' describes the next conflict + */ + std::vector results; + if (!epgSearchAvailable) + return results; + Epgsearch_services_v1_1 *epgSearch = new Epgsearch_services_v1_1; + if (epgSearchPlugin->Service("Epgsearch-services-v1.1", epgSearch)) { + std::list conflicts = epgSearch->handler->TimerConflictList(); + int numConflicts = conflicts.size(); + if (numConflicts > 0) { + for (std::list::iterator it=conflicts.begin(); it != conflicts.end(); ++it) { + TVGuideTimerConflict sConflict; + splitstring s(it->c_str()); + std::vector flds = s.split(':'); + if (flds.size() < 2) + continue; + sConflict.time = atoi(flds[0].c_str()); + splitstring s2(flds[1].c_str()); + std::vector flds2 = s2.split('|'); + if (flds2.size() < 3) + continue; + sConflict.timerID = atoi(flds2[0].c_str()); + sConflict.percentPossible = atoi(flds2[1].c_str()); + splitstring s3(flds2[2].c_str()); + std::vector flds3 = s3.split('#'); + std::vector timerIDs; + for (int k = 0; k < flds3.size(); k++) { + timerIDs.push_back(atoi(flds3[k].c_str()) - 1); + } + sConflict.timerIDs = timerIDs; + results.push_back(sConflict); + } + } + } + delete epgSearch; + + int numConflicts = results.size(); + time_t startTime = 0; + time_t endTime = 0; + for (int i=0; i < numConflicts; i++) { + cTimeInterval *unionSet = NULL; + int numTimers = results[i].timerIDs.size(); + for (int j=0; j < numTimers; j++) { + const cTimer *timer = Timers.Get(results[i].timerIDs[j]); + if (timer) { + if (!unionSet) { + unionSet = new cTimeInterval(timer->StartTime(), timer->StopTime()); + } else { + cTimeInterval *timerInterval = new cTimeInterval(timer->StartTime(), timer->StopTime()); + cTimeInterval *newUnion = unionSet->Union(timerInterval); + delete unionSet; + delete timerInterval; + unionSet = newUnion; + } + } + } + results[i].timeStart = unionSet->Start(); + results[i].timeStop = unionSet->Stop(); + delete unionSet; + + cTimeInterval *intersect = NULL; + for (int j=0; j < numTimers; j++) { + const cTimer *timer = Timers.Get(results[i].timerIDs[j]); + if (timer) { + if (!intersect) { + intersect = new cTimeInterval(timer->StartTime(), timer->StopTime()); + } else { + cTimeInterval *timerInterval = new cTimeInterval(timer->StartTime(), timer->StopTime()); + cTimeInterval *newIntersect = intersect->Intersect(timerInterval); + if (newIntersect) { + delete intersect; + intersect = newIntersect; + } + delete timerInterval; + } + } + } + results[i].overlapStart = intersect->Start(); + results[i].overlapStop = intersect->Stop(); + delete intersect; + } + + return results; +} + +cTimer *cRecManager::CreateSeriesTimer(cRecMenu *menu) { + bool active = menu->GetBoolValue(1); + int channelNumber = menu->GetIntValue(2); + int start = menu->GetIntValue(3); + int stop = menu->GetIntValue(4); + int weekdays = menu->GetIntValue(5); + time_t tday = menu->GetTimeValue(6); + int prio = menu->GetIntValue(7); + int lifetime = menu->GetIntValue(8); + + cChannel *channel = Channels.GetByNumber(channelNumber); + cTimer *seriesTimer = new cTimer(false, false, channel); + + seriesTimer->SetDay(tday); + seriesTimer->SetStart(start); + seriesTimer->SetStop(stop); + seriesTimer->SetPriority(prio); + seriesTimer->SetLifetime(lifetime); + seriesTimer->SetWeekDays(weekdays); + seriesTimer->SetFile("TITLE EPISODE"); + if (active) + seriesTimer->SetFlags(tfActive); + else + seriesTimer->SetFlags(tfNone); + seriesTimer->SetEventFromSchedule(); + Timers.Add(seriesTimer); + Timers.SetModified(); + return seriesTimer; +} + +std::vector cRecManager::ReadEPGSearchTemplates(void) { + cString ConfigDir = cPlugin::ConfigDirectory("epgsearch"); + cString epgsearchConf = "epgsearchtemplates.conf"; + cString fileName = AddDirectory(*ConfigDir, *epgsearchConf); + std::vector epgTemplates; + if (access(fileName, F_OK) == 0) { + FILE *f = fopen(fileName, "r"); + if (f) { + char *s; + cReadLine ReadLine; + while ((s = ReadLine.Read(f)) != NULL) { + char *p = strchr(s, '#'); + if (p) + *p = 0; + stripspace(s); + try { + if (!isempty(s)) { + std::string templ = s; + int posID = templ.find_first_of(":"); + int posName = templ.find_first_of(":", posID+1); + std::string name = templ.substr(posID+1, posName - posID - 1); + std::string templValue = templ.substr(posName); + TVGuideEPGSearchTemplate tmp; + tmp.name = name; + tmp.templValue = templValue; + epgTemplates.push_back(tmp); + } + } catch (...){} + } + } + } + return epgTemplates; +} + +std::string cRecManager::BuildEPGSearchString(cString searchString, std::string templValue) { + std::stringstream searchTimerString; + searchTimerString << "0:"; + searchTimerString << *searchString; + searchTimerString << templValue; + return searchTimerString.str(); +} + +std::string cRecManager::BuildEPGSearchString(cString searchString, cRecMenu *menu) { + int searchMode = menu->GetIntValue(0); + bool useTitle = menu->GetBoolValue(1); + bool useSubTitle = menu->GetBoolValue(2); + bool useDescription = menu->GetBoolValue(3); + bool limitChannels = menu->GetBoolValue(4); + int startChannel = -1; + int stopChannel = -1; + if (limitChannels) { + startChannel = menu->GetIntValue(5); + stopChannel = menu->GetIntValue(6); + } + int after = 0; + int before = 0; + bool limitTime = (limitChannels)?menu->GetBoolValue(7):menu->GetBoolValue(5); + if (limitTime) { + after = (limitChannels)?menu->GetIntValue(8):menu->GetIntValue(6); + before = (limitChannels)?menu->GetIntValue(9):menu->GetIntValue(7); + } + + std::stringstream searchTimerString; + //1 - unique search timer id + searchTimerString << "0:"; + //2 - the search term + searchTimerString << *searchString; + //3 - use time? 0/1 + //4 - start time in HHMM + //5 - stop time in HHMM + if (limitTime) { + searchTimerString << ":1:" << after << ":" << before << ":"; + } else { + searchTimerString << ":0:::"; + } + //6 - use channel? 0 = no, 1 = Interval, 2 = Channel group, 3 = FTA only + //7 - if 'use channel' = 1 then channel id[|channel id] in VDR format, + // one entry or min/max entry separated with |, if 'use channel' = 2 + // then the channel group name + if (limitChannels) { + searchTimerString << "1:"; + cChannel *startChan = Channels.GetByNumber(startChannel); + cChannel *stopChan = Channels.GetByNumber(stopChannel); + searchTimerString << *(startChan->GetChannelID().ToString()); + searchTimerString << "|"; + searchTimerString << *(stopChan->GetChannelID().ToString()) << ":"; + } else { + searchTimerString << "0::"; + } + //8 - match case? 0/1 + searchTimerString << ":0"; + /*9 - search mode: + 0 - the whole term must appear as substring + 1 - all single terms (delimiters are blank,',', ';', '|' or '~') + must exist as substrings. + 2 - at least one term (delimiters are blank, ',', ';', '|' or '~') + must exist as substring. + 3 - matches exactly + 4 - regular expression */ + searchTimerString << searchMode << ":"; + //10 - use title? 0/1 + if (useTitle) + searchTimerString << "1:"; + else + searchTimerString << "0:"; + //11 - use subtitle? 0/1 + if (useSubTitle) + searchTimerString << "1:"; + else + searchTimerString << "0:"; + // 12 - use description? 0/1 + if (useDescription) + searchTimerString << "1:"; + else + searchTimerString << "0:"; + //13 - use duration? 0/1 + //14 - min duration in hhmm + //15 - max duration in hhmm + searchTimerString << "0:::"; + //16 - use as search timer? 0/1 + searchTimerString << "1:"; + //17 - use day of week? 0/1 + //18 - day of week (0 = Sunday, 1 = Monday...; + // -1 Sunday, -2 Monday, -4 Tuesday, ...; -7 Sun, Mon, Tue) + searchTimerString << "0::"; + //19 - use series recording? 0/1 + searchTimerString << "1:"; + //20 - directory for recording + searchTimerString << ":"; + //21 - priority of recording + //22 - lifetime of recording + searchTimerString << "99:99:"; + //23 - time margin for start in minutes + //24 - time margin for stop in minutes + searchTimerString << "5:5:"; + //25 - use VPS? 0/1 + searchTimerString << "0:"; + /*26 - action: + 0 = create a timer + 1 = announce only via OSD (no timer) + 2 = switch only (no timer) + 3 = announce via OSD and switch (no timer) + 4 = announce via mail*/ + searchTimerString << "0:"; + /*27 - use extended EPG info? 0/1 + 28 - extended EPG info values. This entry has the following format + (delimiter is '|' for each category, '#' separates id and value): + 1 - the id of the extended EPG info category as specified in + epgsearchcats.conf + 2 - the value of the extended EPG info category + (a ':' will be translated to "!^colon^!", e.g. in "16:9") */ + searchTimerString << "0::"; + /*29 - avoid repeats? 0/1 + 30 - allowed repeats + 31 - compare title when testing for a repeat? 0/1 + 32 - compare subtitle when testing for a repeat? 0/1/2 + 0 - no + 1 - yes + 2 - yes, if present + 33 - compare description when testing for a repeat? 0/1 + 34 - compare extended EPG info when testing for a repeat? + This entry is a bit field of the category IDs. + 35 - accepts repeats only within x days */ + searchTimerString << "1:1:1:2:1:::"; + /*36 - delete a recording automatically after x days + 37 - but keep this number of recordings anyway + 38 - minutes before switch (if action = 2) + 39 - pause if x recordings already exist + 40 - blacklist usage mode (0 none, 1 selection, 2 all) + 41 - selected blacklist IDs separated with '|' + 42 - fuzzy tolerance value for fuzzy searching + 43 - use this search in favorites menu (0 no, 1 yes) + 44 - id of a menu search template + 45 - auto deletion mode (0 don't delete search timer, 1 delete after given + count of recordings, 2 delete after given days after first recording) + 46 - count of recordings after which to delete the search timer + 47 - count of days after the first recording after which to delete the search + timer + 48 - first day where the search timer is active (see parameter 16) + 49 - last day where the search timer is active (see parameter 16) + 50 - ignore missing EPG categories? 0/1 + 51 - unmute sound if off when used as switch timer + 52 - percentage of match when comparing the summary of two events (with 'avoid repeats') + 53 - HEX representation of the content descriptors, each descriptor ID is represented with 2 chars + 54 - compare date when testing for a repeat? (0=no, 1=same day, 2=same week, 3=same month) */ + searchTimerString << "0::::0:::0::0:::::::::0"; + + //esyslog("tvguide: epgsearch String: %s", searchTimerString.str().c_str()); + + return searchTimerString.str(); +} + +const cEvent **cRecManager::PerformSearchTimerSearch(std::string epgSearchString, int &numResults) { + if (!epgSearchAvailable) + return NULL; + const cEvent **searchResults = NULL; + Epgsearch_services_v1_1 *epgSearch = new Epgsearch_services_v1_1; + if (epgSearchPlugin->Service("Epgsearch-services-v1.1", epgSearch)) { + std::list results = epgSearch->handler->QuerySearch(epgSearchString); + numResults = results.size(); + if (numResults > 0) { + searchResults = new const cEvent *[numResults]; + cSchedulesLock *schedulesLock; + const cSchedules *schedules; + schedules = cSchedules::Schedules(*schedulesLock); + const cEvent *event = NULL; + int index=0; + for (std::list::iterator it=results.begin(); it != results.end(); ++it) { + try { + splitstring s(it->c_str()); + std::vector flds = s.split(':', 1); + int eventID = atoi(flds[1].c_str()); + std::string channelID = flds[7]; + tChannelID chanID = tChannelID::FromString(channelID.c_str()); + cChannel *channel = Channels.GetByChannelID(chanID); + if (channel) { + const cSchedule *Schedule = NULL; + Schedule = schedules->GetSchedule(channel); + event = Schedule->GetEvent(eventID); + if (event) { + searchResults[index] = event; + } else + return NULL; + } else + return NULL; + index++; + } catch (...){} + } + } + } + return searchResults; +} + +const cEvent **cRecManager::PerformSearch(cRecMenu *menu, bool withOptions, int &numResults) { + if (epgSearchAvailable) { + cString searchString = menu->GetStringValue(1); + Epgsearch_searchresults_v1_0 data; + data.query = (char *)*searchString; + int mode = 0; + int channelNr = 0; + bool useTitle = true; + bool useSubTitle = true; + bool useDescription = false; + if (withOptions) { + mode = menu->GetIntValue(2); + channelNr = menu->GetIntValue(3); + useTitle = menu->GetBoolValue(4); + useSubTitle = menu->GetBoolValue(5); + useDescription = menu->GetBoolValue(6); + } + data.mode = mode; + data.channelNr = channelNr; + data.useTitle = useTitle; + data.useSubTitle = useSubTitle; + data.useDescription = useDescription; + + if (epgSearchPlugin->Service("Epgsearch-searchresults-v1.0", &data)) { + cList *list = data.pResultList; + int numElements = list->Count(); + const cEvent **searchResults = NULL; + if (numElements > 0) { + searchResults = new const cEvent *[numElements]; + numResults = numElements; + int index = 0; + for (Epgsearch_searchresults_v1_0::cServiceSearchResult *r = list->First(); r ; r = list->Next(r)) { + searchResults[index] = r->event; + index++; + } + } + delete list; + return searchResults; + } + } + return NULL; +} + +int cRecManager::CreateSearchTimer(std::string epgSearchString) { + int timerID = -1; + if (!epgSearchAvailable) + return timerID; + Epgsearch_services_v1_1 *epgSearch = new Epgsearch_services_v1_1; + if (epgSearchPlugin->Service("Epgsearch-services-v1.1", epgSearch)) { + timerID = epgSearch->handler->AddSearchTimer(epgSearchString); + } + return timerID; +} + +void cRecManager::UpdateSearchTimers(void) { + if (epgSearchAvailable) { + Epgsearch_updatesearchtimers_v1_0 data; + data.showMessage = false; + epgSearchPlugin->Service("Epgsearch-updatesearchtimers-v1.0", &data); + } +} + +// announceOnly: 0 = switch, 1 = announce only, 2 = ask for switch +bool cRecManager::CreateSwitchTimer(const cEvent *event, cRecMenu *menu) { + int switchMinsBefore = menu->GetIntValue(1); + int announceOnly = menu->GetIntValue(2); + if (epgSearchAvailable) { + Epgsearch_switchtimer_v1_0 data; + data.event = event; + data.mode = 1; + data.switchMinsBefore = switchMinsBefore; + data.announceOnly = announceOnly; + data.success = false; + epgSearchPlugin->Service("Epgsearch-switchtimer-v1.0", &data); + cSwitchTimer *t = new cSwitchTimer(event); + SwitchTimers.Add(t); + return data.success; + } + return false; +} + +void cRecManager::DeleteSwitchTimer(const cEvent *event) { + SwitchTimers.DeleteSwitchTimer(event); + if (epgSearchAvailable) { + Epgsearch_switchtimer_v1_0 data; + data.event = event; + data.mode = 2; + data.switchMinsBefore = 0; + data.announceOnly = 0; + data.success = false; + epgSearchPlugin->Service("Epgsearch-switchtimer-v1.0", &data); + } +} + +cRecording **cRecManager::SearchForRecordings(cString searchString, int &numResults) { + + cRecording **matchingRecordings = NULL; + int num = 0; + numResults = 0; + + for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) { + std::string s1 = recording->Name(); + std::string s2 = *searchString; + if (s1.empty() || s2.empty()) continue; + + // tolerance for fuzzy searching: 90% of the shorter text length, but at least 1 + int tolerance = std::max(1, (int)std::min(s1.size(), s2.size()) / 10); + + bool match = FindIgnoreCase(s1, s2) >= 0 || FindIgnoreCase(s2, s1) >= 0; + + if (!match) { + AFUZZY af = { NULL, NULL, NULL, NULL, NULL, NULL, { 0 }, { 0 }, 0, 0, 0, 0, 0, 0 }; + if (s1.size() > 32) s1 = s1.substr(0, 32); + afuzzy_init(s1.c_str(), tolerance, 0, &af); + /* Checking substring */ + int res = afuzzy_checkSUB(s2.c_str(), &af); + afuzzy_free(&af); + match = (res > 0); + } + + if (!match) { + AFUZZY af = { NULL, NULL, NULL, NULL, NULL, NULL, { 0 }, { 0 }, 0, 0, 0, 0, 0, 0 }; + if (s2.size() > 32) s2 = s2.substr(0, 32); + afuzzy_init(s2.c_str(), tolerance, 0, &af); + /* Checking substring */ + int res = afuzzy_checkSUB(s1.c_str(), &af); + afuzzy_free(&af); + match = (res > 0); + } + + if (match) { + matchingRecordings = (cRecording **)realloc(matchingRecordings, (num + 1) * sizeof(cRecording *)); + matchingRecordings[num++] = recording; + } + } + if (num > 0) { + qsort(matchingRecordings, num, sizeof(cRecording *), CompareRecording); + numResults = num; + return matchingRecordings; + } + return NULL; +} \ No newline at end of file diff --git a/recmanager.h b/recmanager.h new file mode 100644 index 0000000..0b010c4 --- /dev/null +++ b/recmanager.h @@ -0,0 +1,53 @@ +#ifndef __TVGUIDE_RECMMANAGER_H +#define __TVGUIDE_RECMMANAGER_H + +class TVGuideTimerConflict { +public: + time_t time; + time_t timeStart; + time_t timeStop; + time_t overlapStart; + time_t overlapStop; + int percentPossible; + int timerID; + std::vector timerIDs; + bool timerInvolved(int involvedID); +}; + +struct TVGuideEPGSearchTemplate { +public: + std::string name; + std::string templValue; +}; + +// --- cRecManager ------------------------------------------------------------- +class cRecManager { +private: + cPlugin *epgSearchPlugin; + bool epgSearchAvailable; + void DeleteTimer(cTimer *timer); +public: + cRecManager (void); + void SetEPGSearchPlugin(void); + bool EpgSearchAvailable(void) {return epgSearchAvailable;}; + cTimer *createTimer(const cEvent *event); + void DeleteTimer(const cEvent *event); + void DeleteTimer(int timerID); + void SaveTimer(cTimer *timer, cRecMenu *menu); + bool IsRecorded(const cEvent *event); + std::vector CheckTimerConflict(void); + cTimer *CreateSeriesTimer(cRecMenu *menu); + std::string BuildEPGSearchString(cString searchString, cRecMenu *menu); + std::string BuildEPGSearchString(cString searchString, std::string templValue); + const cEvent **PerformSearchTimerSearch(std::string epgSearchString, int &numResults); + const cEvent **PerformSearch(cRecMenu *menu, bool withOptions, int &numResults); + std::vector ReadEPGSearchTemplates(void); + int CreateSearchTimer(std::string epgSearchString); + void UpdateSearchTimers(void); + bool CreateSwitchTimer(const cEvent *event, cRecMenu *menu); + void DeleteSwitchTimer(const cEvent *event); + cRecording **SearchForRecordings(cString searchString, int &numResults); + virtual ~cRecManager (void); +}; + +#endif //__TVGUIDE_RECMMANAGER_H \ No newline at end of file diff --git a/recmenu.c b/recmenu.c new file mode 100644 index 0000000..66a5a84 --- /dev/null +++ b/recmenu.c @@ -0,0 +1,525 @@ +#include "recmenu.h" + +// --- cRecMenu ------------------------------------------------------------- + +cRecMenu::cRecMenu(void) { + border = 10; + height = 2*border; + headerHeight = 0; + footerHeight = 0; + scrollHeight = 0; + scrollItemHeight = 0; + scrollable = false; + scrollbarWidth = 3 * border; + pixmapScrollBar = NULL; + imgScrollBar = NULL; + startIndex = 0; + stopIndex = 0; + numItems = 0; + header = NULL; + footer = NULL; +} + +cRecMenu::~cRecMenu(void) { + if (header) + delete header; + menuItems.Clear(); + if (footer) + delete footer; + if (pixmapScrollBar) + osdManager.releasePixmap(pixmapScrollBar); + if (imgScrollBar) + delete imgScrollBar; +} + +void cRecMenu::SetWidthPercent(int percentOSDWidth) { + width = tvguideConfig.osdWidth * percentOSDWidth / 100; + x = (tvguideConfig.osdWidth - width) / 2; +} + +void cRecMenu::SetWidthPixel(int pixel) { + width = pixel; + x = (tvguideConfig.osdWidth - width) / 2; +} + +int cRecMenu::CalculateOptimalWidth(void) { + int optWidth = 0; + for (cRecMenuItem *item = menuItems.First(); item; item = menuItems.Next(item)) { + int itemWidth = item->GetWidth(); + if (itemWidth > optWidth) + optWidth = itemWidth; + } + return optWidth; +} + + +void cRecMenu::AddMenuItem(cRecMenuItem *item, cRecMenuItem *before) { + if (!before) + menuItems.Add(item); + else + menuItems.Ins(item, before); +} + +void cRecMenu::AddMenuItemScroll(cRecMenuItem *item) { + scrollHeight += item->GetHeight(); + stopIndex++; + numItems++; + if (scrollItemHeight == 0) + scrollItemHeight = item->GetHeight(); + menuItems.Add(item); +} + +bool cRecMenu::CheckHeight(void) { + int nextHeight = headerHeight + footerHeight + scrollHeight + 2*border + 150; + if (nextHeight > tvguideConfig.osdHeight) { + scrollable = true; + return false; + } + return true; +} + +void cRecMenu::CalculateHeight(void) { + height = 2*border; + if (header) + height += headerHeight; + for (cRecMenuItem *item = menuItems.First(); item; item = menuItems.Next(item)) { + height += item->GetHeight(); + } + if (footer) + height += footerHeight; + y = (tvguideConfig.osdHeight - height) / 2; + + if (scrollable) { + width += scrollbarWidth + border; + } +} + +void cRecMenu::CreatePixmap(void) { + pixmap = osdManager.requestPixmap(3, cRect(x, y, width, height)); + if (scrollable) { + int scrollBarX = x + width - scrollbarWidth - border; + int scrollBarY = y + border + headerHeight; + int scrollBarHeight = height - headerHeight - footerHeight - 2 * border; + pixmapScrollBar = osdManager.requestPixmap(4, cRect(scrollBarX, scrollBarY, scrollbarWidth, scrollBarHeight)); + } +} + +void cRecMenu::SetFooter(cRecMenuItem *footer) { + this->footer = footer; + footerHeight = footer->GetHeight(); + height += footerHeight; +} + +void cRecMenu::SetHeader(cRecMenuItem *header) { + this->header = header; + headerHeight = header->GetHeight(); + height += headerHeight; +} + +cRecMenuItem *cRecMenu::GetActiveMenuItem(void) { + for (cRecMenuItem *item = menuItems.First(); item; item = menuItems.Next(item)) { + if (item->isActive()) + return item; + } + if (footer && footer->isActive()) + return footer; + return NULL; +} + +int cRecMenu::GetActive(bool withOffset) { + int numActive = withOffset?startIndex:0; + int i = 0; + for (cRecMenuItem *item = menuItems.First(); item; item = menuItems.Next(item)) { + if (item->isActive()) { + numActive += i; + break; + } + i++; + } + return numActive; +} + +bool cRecMenu::ActivatePrev(void) { + cRecMenuItem *activeItem = GetActiveMenuItem(); + if (!scrollable && footer && footer->isActive()) { + Activate(footer, menuItems.Last()); + return true; + } else if (activeItem) { + cRecMenuItem *prev = NULL; + for (cRecMenuItem *item = menuItems.Prev(activeItem); item; item = menuItems.Prev(item)) { + if (item->isSelectable()) { + prev = item; + break; + } + } + if (prev) { + Activate(activeItem , prev); + return true; + } + } + return false; +} + +bool cRecMenu::ActivateNext(void) { + cRecMenuItem *activeItem = GetActiveMenuItem(); + if (activeItem) { + cRecMenuItem *next = NULL; + for (cRecMenuItem *item = menuItems.Next(activeItem); item; item = menuItems.Next(item)) { + if (item->isSelectable()) { + next = item; + break; + } + } + if (next) { + Activate(activeItem , next); + return true; + } else if (!scrollable && footer && footer->isSelectable()) { + Activate(activeItem , footer); + return true; + } + } + return false; +} + +void cRecMenu::Activate(cRecMenuItem *itemOld, cRecMenuItem *item) { + itemOld->setInactive(); + itemOld->setBackground(); + itemOld->Draw(); + item->setActive(); + item->setBackground(); + item->Draw(); +} + +void cRecMenu::ScrollUp(void) { + if (footer && footer->isActive()) { + Activate(footer, menuItems.Last()); + } else { + //get perv x items + int numNewItems = numItems / 2; + int numAdded = 0; + cRecMenuItem *newItem = NULL; + while (newItem = GetMenuItem(startIndex-1)) { + AddMenuItem(newItem, menuItems.First()); + menuItems.Del(menuItems.Last(), true); + stopIndex--; + startIndex--; + numAdded++; + if (numAdded >= numNewItems) + break; + } + if (numAdded != 0) { + Arrange(true); + Display(true); + ActivatePrev(); + } + } +} + +void cRecMenu::ScrollDown(void) { + //get next x items + int numNewItems = numItems / 2; + int numAdded = 0; + cRecMenuItem *newItem = NULL; + while (newItem = GetMenuItem(stopIndex)) { + menuItems.Add(newItem); + menuItems.Del(menuItems.First(), true); + stopIndex++; + startIndex++; + numAdded++; + if (numAdded >= numNewItems) + break; + } + if (numAdded != 0) { + Arrange(true); + Display(true); + ActivateNext(); + } else { + //last item reached, activate footer + if (footer) { + cRecMenuItem *activeItem = GetActiveMenuItem(); + Activate(activeItem , footer); + } + } +} + +void cRecMenu::JumpBegin(void) { + cRecMenuItem *activeItem = GetActiveMenuItem(); + if (!scrollable) { + cRecMenuItem *firstSelectable= NULL; + for (cRecMenuItem *item = menuItems.First(); item; item = menuItems.Next(item)) { + if (item->isSelectable()) { + firstSelectable = item; + break; + } + } + if (activeItem && firstSelectable) { + Activate(activeItem , firstSelectable); + } + } else { + activeItem->setInactive(); + activeItem->setBackground(); + if (footer) + footer->Draw(); + menuItems.Clear(); + int currentItem = 0; + cRecMenuItem *newItem = NULL; + while (newItem = GetMenuItem(currentItem)) { + AddMenuItem(newItem); + currentItem++; + if (currentItem >= numItems) + break; + } + Arrange(true); + startIndex = 0; + stopIndex = numItems-1; + menuItems.First()->setActive(); + menuItems.First()->setBackground(); + menuItems.First()->Draw(); + Display(true); + } +} + +void cRecMenu::JumpEnd(void) { + cRecMenuItem *activeItem = GetActiveMenuItem(); + if (!activeItem) + return; + if (!scrollable) { + cRecMenuItem *lastSelectable= NULL; + if (footer && footer->isSelectable()) { + lastSelectable = footer; + } else { + for (cRecMenuItem *item = menuItems.Last(); item; item = menuItems.Prev(item)) { + if (item->isSelectable()) { + lastSelectable = item; + break; + } + } + } + if (lastSelectable) { + Activate(activeItem , lastSelectable); + } + } else { + activeItem->setInactive(); + activeItem->setBackground(); + menuItems.Clear(); + int totalNumItems = GetTotalNumMenuItems(); + int currentItem = totalNumItems-1; + int itemsAdded = 0; + cRecMenuItem *newItem = NULL; + while (newItem = GetMenuItem(currentItem)) { + AddMenuItem(newItem, menuItems.First()); + currentItem--; + itemsAdded++; + if (itemsAdded >= numItems) + break; + } + Arrange(true); + stopIndex = totalNumItems; + startIndex = stopIndex - numItems; + if (footer) { + footer->setActive(); + footer->setBackground(); + footer->Draw(); + } else { + menuItems.Last()->setActive(); + menuItems.Last()->setBackground(); + menuItems.Last()->Draw(); + } + Display(true); + } +} + +void cRecMenu::Arrange(bool scroll) { + int xElement = x + border; + int yElement = y + border; + int widthElement = width - 2 * border; + if (scrollable) + widthElement -= scrollbarWidth + border; + + if (header) { + if (!scroll) { + header->SetGeometry(xElement, yElement, widthElement); + header->SetPixmaps(); + } + yElement += header->GetHeight(); + } + for (cRecMenuItem *item = menuItems.First(); item; item = menuItems.Next(item)) { + item->SetGeometry(xElement, yElement, widthElement); + item->SetPixmaps(); + yElement += item->GetHeight(); + } + if (footer && !scroll) { + footer->SetGeometry(xElement, yElement, widthElement); + footer->SetPixmaps(); + } +} + +void cRecMenu::Display(bool scroll) { + pixmap->Fill(theme.Color(clrBackground)); + drawBorder(); + if (header && !scroll) { + header->setBackground(); + header->Draw(); + } + for (cRecMenuItem *item = menuItems.First(); item; item = menuItems.Next(item)) { + item->setBackground(); + item->Draw(); + } + if (footer && !scroll) { + footer->setBackground(); + footer->Draw(); + } + if (scrollable) + DrawScrollBar(); +} + +void cRecMenu::Hide(void) { + pixmap->SetLayer(-1); + if (pixmapScrollBar) + pixmapScrollBar->SetLayer(-1); + if (header) + header->Hide(); + if (footer) + footer->Hide(); + for (cRecMenuItem *item = menuItems.First(); item; item = menuItems.Next(item)) { + item->Hide(); + } +} + +void cRecMenu::Show(void) { + pixmap->SetLayer(3); + if (pixmapScrollBar) + pixmapScrollBar->SetLayer(3); + if (header) + header->Show(); + if (footer) + footer->Show(); + for (cRecMenuItem *item = menuItems.First(); item; item = menuItems.Next(item)) { + item->Show(); + } +} + +void cRecMenu::DrawScrollBar(void) { + pixmapScrollBar->Fill(theme.Color(clrBorder)); + pixmapScrollBar->DrawRectangle(cRect(2,2,pixmapScrollBar->ViewPort().Width()-4, pixmapScrollBar->ViewPort().Height() - 4), theme.Color(clrBackground)); + + int totalNumItems = GetTotalNumMenuItems(); + if (imgScrollBar == NULL) { + int scrollBarImgHeight = (pixmapScrollBar->ViewPort().Height() - 8) * numItems / totalNumItems; + imgScrollBar = createScrollbar(pixmapScrollBar->ViewPort().Width()-8, scrollBarImgHeight, theme.Color(clrHighlight), theme.Color(clrHighlightBlending)); + } + int offset = (pixmapScrollBar->ViewPort().Height() - 8) * startIndex / totalNumItems; + pixmapScrollBar->DrawImage(cPoint(4, 2 + offset), *imgScrollBar); +} + +int cRecMenu::GetIntValue(int itemNumber) { + cRecMenuItem *item = NULL; + item = menuItems.Get(itemNumber); + if (item) { + return item->GetIntValue(); + } + return -1; +} + +time_t cRecMenu::GetTimeValue(int itemNumber) { + cRecMenuItem *item = NULL; + item = menuItems.Get(itemNumber); + if (item) { + return item->GetTimeValue(); + } + return 0; +} + +bool cRecMenu::GetBoolValue(int itemNumber) { + cRecMenuItem *item = NULL; + item = menuItems.Get(itemNumber); + if (item) { + return item->GetBoolValue(); + } + return false; +} + +cString cRecMenu::GetStringValue(int itemNumber) { + cRecMenuItem *item = NULL; + item = menuItems.Get(itemNumber); + if (item) { + return item->GetStringValue(); + } + return cString(""); +} + +const cEvent *cRecMenu::GetEventValue(int itemNumber) { + cRecMenuItem *item = NULL; + item = menuItems.Get(itemNumber); + if (item) { + return item->GetEventValue(); + } + return NULL; +} + +eRecMenuState cRecMenu::ProcessKey(eKeys Key) { + cRecMenuItem *activeItem = GetActiveMenuItem(); + eRecMenuState state = rmsContinue; + if (!activeItem) + return state; + + state = activeItem->ProcessKey(Key); + if (state == rmsRefresh) { + CreateMenuItems(); + Display(); + } else if (state == rmsNotConsumed) { + switch (Key & ~k_Repeat) { + case kUp: + if (!ActivatePrev() && scrollable) + ScrollUp(); + state = rmsConsumed; + break; + case kDown: + if (!ActivateNext() && scrollable) + ScrollDown(); + state = rmsConsumed; + break; + case kLeft: + JumpBegin(); + state = rmsConsumed; + break; + case kRight: + JumpEnd(); + state = rmsConsumed; + break; + default: + break; + } + } + return state; +} + +cImage *cRecMenu::createScrollbar(int width, int height, tColor clrBgr, tColor clrBlend) { + cImage *image = new cImage(cSize(width, height)); + image->Fill(clrBgr); + if (tvguideConfig.useBlending) { + int numSteps = 64; + int alphaStep = 0x03; + if (height < 30) + return image; + else if (height < 100) { + numSteps = 32; + alphaStep = 0x06; + } + int stepY = 0.5*height / numSteps; + if (stepY == 0) + stepY = 1; + int alpha = 0x40; + tColor clr; + for (int i = 0; iSetPixel(cPoint(x,y), clr); + } + } + alpha += alphaStep; + } + } + return image; +} diff --git a/recmenu.h b/recmenu.h new file mode 100644 index 0000000..87d3ef5 --- /dev/null +++ b/recmenu.h @@ -0,0 +1,61 @@ +#ifndef __TVGUIDE_RECMENU_H +#define __TVGUIDE_RECMENU_H + +// --- cRecMenu ------------------------------------------------------------- + +class cRecMenu : public cStyledPixmap { +protected: + int x, y; + int width, height; + int headerHeight, footerHeight; + int scrollHeight; + int scrollItemHeight; + int scrollbarWidth; + cPixmap *pixmapScrollBar; + cImage *imgScrollBar; + int border; + bool scrollable; + int numItems; + int startIndex, stopIndex; + cRecMenuItem *header; + cRecMenuItem *footer; + cList menuItems; + void SetWidthPercent(int percentOSDWidth); + void SetWidthPixel(int pixel); + int CalculateOptimalWidth(void); + bool CheckHeight(void); + void CalculateHeight(void); + void CreatePixmap(void); + void SetHeader(cRecMenuItem *header); + void SetFooter(cRecMenuItem *footer); + void AddMenuItemScroll(cRecMenuItem *item); + void AddMenuItem(cRecMenuItem *item, cRecMenuItem *before = NULL); + cRecMenuItem *GetActiveMenuItem(void); + bool ActivateNext(void); + bool ActivatePrev(void); + void Activate(cRecMenuItem *itemOld, cRecMenuItem *item); + void ScrollUp(void); + void ScrollDown(void); + void JumpBegin(void); + void JumpEnd(void); + void DrawScrollBar(void); + cImage *createScrollbar(int width, int height, tColor clrBgr, tColor clrBlend); + void Arrange(bool scroll = false); + virtual cRecMenuItem *GetMenuItem(int number) { return NULL; }; + virtual int GetTotalNumMenuItems(void) { return 0; }; + virtual void CreateMenuItems(void) {}; +public: + cRecMenu(void); + virtual ~cRecMenu(void); + void Display(bool scroll = false); + void Hide(void); + void Show(void); + int GetActive(bool withOffset); + int GetIntValue(int itemNumber); + time_t GetTimeValue(int itemNumber); + bool GetBoolValue(int itemNumber); + cString GetStringValue(int itemNumber); + const cEvent *GetEventValue(int itemNumber); + eRecMenuState ProcessKey(eKeys Key); +}; +#endif //__TVGUIDE_RECMENU_H \ No newline at end of file diff --git a/recmenuitem.c b/recmenuitem.c new file mode 100644 index 0000000..eb931e9 --- /dev/null +++ b/recmenuitem.c @@ -0,0 +1,2016 @@ +#include +#include +#include "recmenuitem.h" + +// --- cRecMenuItem ------------------------------------------------------------- + +cRecMenuItem::cRecMenuItem(void) { + height = 0; + action = rmsNotConsumed; + drawn = false; + font = tvguideConfig.FontRecMenuItem; + fontSmall = tvguideConfig.FontRecMenuItemSmall; +} + +cRecMenuItem::~cRecMenuItem(void) { +} + +void cRecMenuItem::SetGeometry(int x, int y, int width) { + this->x = x; + this->y = y; + this->width = width; + +} + +void cRecMenuItem::SetPixmaps(void) { + if (!pixmap) + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + else + pixmap->SetViewPort(cRect(x, y, width, height)); +} + +void cRecMenuItem::setBackground(void) { + if (active) { + color = theme.Color(clrHighlight); + colorBlending = theme.Color(clrHighlightBlending); + colorText = theme.Color(clrFontActive); + } else { + color = theme.Color(clrGrid1); + colorBlending = theme.Color(clrGrid1Blending); + colorText = theme.Color(clrFont); + } + colorTextBack = (tvguideConfig.useBlending==0)?color:clrTransparent; + drawBackground(); + drawBorder(); +} + +// --- cRecMenuItemButton ------------------------------------------------------- + +cRecMenuItemButton::cRecMenuItemButton(const char *text, eRecMenuState action, bool active, bool halfWidth) { + selectable = true; + this->text = text; + this->action = action; + this->active = active; + height = 3 * font->Height() / 2; + this->halfWidth = halfWidth; +} + +cRecMenuItemButton::~cRecMenuItemButton(void) { +} + +int cRecMenuItemButton::GetWidth(void) { + return font->Width(*text); +} + +void cRecMenuItemButton::SetPixmaps(void) { + if (halfWidth) { + x += width / 4; + width = width / 2; + } + if (!pixmap) + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + else + pixmap->SetViewPort(cRect(x, y, width, height)); +} + +void cRecMenuItemButton::Draw(void) { + int y = (height - font->Height()) / 2; + int x = (width - font->Width(*text)) / 2;; + pixmap->DrawText(cPoint(x, y), *text, colorText, colorTextBack, font); +} + +eRecMenuState cRecMenuItemButton::ProcessKey(eKeys Key) { + switch (Key & ~k_Repeat) { + case kOk: + return action; + break; + default: + break; + } + return rmsNotConsumed; +} + + +// --- cRecMenuItemButtonYesNo ------------------------------------------------------- +cRecMenuItemButtonYesNo::cRecMenuItemButtonYesNo(cString textYes, + cString textNo, + eRecMenuState actionYes, + eRecMenuState actionNo, + bool active) { + selectable = true; + this->textYes = textYes; + this->textNo = textNo; + this->action = actionYes; + this->actionNo = actionNo; + this->active = active; + yesActive = true; + height = 3 * font->Height() / 2; + pixmapNo = NULL; +} + +cRecMenuItemButtonYesNo::~cRecMenuItemButtonYesNo(void) { + if (pixmapNo) + delete pixmapNo; +} + +void cRecMenuItemButtonYesNo::SetPixmaps(void) { + int buttonWidth = 44 * width / 100; + int yesX = x + width / 25; + int noX = x + 52 * width / 100; + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(yesX, y, buttonWidth, height)); + pixmapNo = new cStyledPixmap(osdManager.requestPixmap(4, cRect(noX, y, buttonWidth, height))); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapNo->SetViewPort(cRect(x, y, width, height)); + } +} + +void cRecMenuItemButtonYesNo::Hide(void) { + pixmap->SetLayer(-1); + pixmapNo->SetLayer(-1); +} + +void cRecMenuItemButtonYesNo::Show(void) { + pixmap->SetLayer(4); + pixmapNo->SetLayer(4); +} + +void cRecMenuItemButtonYesNo::setBackground() { + if (active) { + if (yesActive) { + color = theme.Color(clrHighlight); + colorBlending = theme.Color(clrHighlightBlending); + colorText = theme.Color(clrFontActive); + pixmapNo->setColor( theme.Color(clrGrid1), + theme.Color(clrGrid1Blending)); + colorTextNo = theme.Color(clrFont); + } else { + color = theme.Color(clrGrid1); + colorBlending = theme.Color(clrGrid1Blending); + colorText = theme.Color(clrFont); + pixmapNo->setColor( theme.Color(clrHighlight), + theme.Color(clrHighlightBlending)); + colorTextNo = theme.Color(clrFontActive); + } + } else { + color = theme.Color(clrGrid1); + colorBlending = theme.Color(clrGrid1Blending); + colorText = theme.Color(clrFont); + pixmapNo->setColor( theme.Color(clrGrid1), + theme.Color(clrGrid1Blending)); + colorTextNo = theme.Color(clrFont); + } + colorTextBack = (tvguideConfig.useBlending==0)?color:clrTransparent; + drawBackground(); + drawBorder(); + pixmapNo->drawBackground(); + pixmapNo->drawBorder(); +} + +void cRecMenuItemButtonYesNo::Draw(void) { + int textYesX = (pixmap->ViewPort().Width() - font->Width(*textYes)) / 2; + int textNoX = (pixmapNo->Width() - font->Width(*textNo)) / 2; + int textY = (height - font->Height()) / 2; + pixmap->DrawText(cPoint(textYesX, textY), *textYes, colorText, clrTransparent, font); + pixmapNo->DrawText(cPoint(textNoX, textY), *textNo, colorTextNo, clrTransparent, font); +} + +eRecMenuState cRecMenuItemButtonYesNo::ProcessKey(eKeys Key) { + switch (Key & ~k_Repeat) { + case kLeft: + if (!yesActive) { + yesActive = true; + setBackground(); + Draw(); + return rmsConsumed; + } else + return rmsNotConsumed; + break; + case kRight: + if (yesActive) { + yesActive = false; + setBackground(); + Draw(); + return rmsConsumed; + } else + return rmsNotConsumed; + break; + case kOk: + if (yesActive) + return action; + return actionNo; + break; + default: + break; + } + return rmsNotConsumed; +} + +// --- cRecMenuItemInfo ------------------------------------------------------- +cRecMenuItemInfo::cRecMenuItemInfo(const char *text) { + selectable = false; + active = false; + this->text = text; + border = 10; +} + +cRecMenuItemInfo::~cRecMenuItemInfo(void) { +} + +void cRecMenuItemInfo::CalculateHeight(int textWidth) { + wrapper.Set(*text, font, textWidth); + height = font->Height() * wrapper.Lines() + 2*border; +} + +void cRecMenuItemInfo::setBackground(void) { + pixmap->Fill(clrTransparent); +} + +void cRecMenuItemInfo::Draw(void) { + int lines = wrapper.Lines(); + int lineHeight = font->Height(); + int x = 0; + int y = border; + for (int i = 0; i < lines; i++) { + x = (width - font->Width(wrapper.GetLine(i))) / 2; + pixmap->DrawText(cPoint(x,y), wrapper.GetLine(i), theme.Color(clrFont), clrTransparent, font); + y += lineHeight; + } +} + +// --- cRecMenuItemInt ------------------------------------------------------- +cRecMenuItemInt::cRecMenuItemInt(cString text, + int initialVal, + int minVal, + int maxVal, + bool active) { + selectable = true; + this->text = text; + this->currentVal = initialVal; + this->minVal = minVal; + this->maxVal = maxVal; + this->active = active; + height = 3 * font->Height() / 2; + pixmapVal = NULL; + fresh = true; +} + +cRecMenuItemInt::~cRecMenuItemInt(void) { + if (pixmapVal) + osdManager.releasePixmap(pixmapVal); +} + +void cRecMenuItemInt::SetPixmaps(void) { + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapVal = osdManager.requestPixmap(5, cRect(x, y, width, height)); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapVal->SetViewPort(cRect(x, y, width, height)); + } +} + +void cRecMenuItemInt::Hide(void) { + pixmap->SetLayer(-1); + pixmapVal->SetLayer(-1); +} + +void cRecMenuItemInt::Show(void) { + pixmap->SetLayer(4); + pixmapVal->SetLayer(5); +} + +void cRecMenuItemInt::setBackground() { + cRecMenuItem::setBackground(); + fresh = true; +} + +void cRecMenuItemInt::Draw(void) { + int textY = (height - font->Height()) / 2; + pixmap->DrawText(cPoint(10, textY), *text, colorText, colorTextBack, font); + DrawValue(); +} + +void cRecMenuItemInt::DrawValue(void) { + pixmapVal->Fill(clrTransparent); + cString textVal = cString::sprintf("%d", currentVal); + int textX = width - font->Width(*textVal) - 10; + int textY = (height - font->Height()) / 2; + pixmapVal->DrawText(cPoint(textX, textY), *textVal, colorText, clrTransparent, font); +} + +eRecMenuState cRecMenuItemInt::ProcessKey(eKeys Key) { + int oldValue = currentVal; + switch (Key & ~k_Repeat) { + case kLeft: + fresh = true; + if (currentVal > minVal) { + currentVal--; + DrawValue(); + } + return rmsConsumed; + break; + case kRight: + fresh = true; + if (currentVal < maxVal) { + currentVal++; + DrawValue(); + } + return rmsConsumed; + break; + case k0 ... k9: + if (fresh) { + currentVal = 0; + fresh = false; + } + currentVal = currentVal * 10 + (Key - k0); + if (!((currentVal >= minVal) && (currentVal <= maxVal))) + currentVal = oldValue; + DrawValue(); + return rmsConsumed; + break; + default: + break; + } + return rmsNotConsumed; +} + +// --- cRecMenuItemBool ------------------------------------------------------- +cRecMenuItemBool::cRecMenuItemBool(cString text, + bool initialVal, + bool refresh, + bool active) { + selectable = true; + this->text = text; + this->yes = initialVal; + this->refresh = refresh; + this->active = active; + height = 3 * font->Height() / 2; + pixmapVal = NULL; +} + +cRecMenuItemBool::~cRecMenuItemBool(void) { + if (pixmapVal) + osdManager.releasePixmap(pixmapVal); +} + +void cRecMenuItemBool::SetPixmaps(void) { + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapVal = osdManager.requestPixmap(5, cRect(x, y, width, height)); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapVal->SetViewPort(cRect(x, y, width, height)); + } +} + +void cRecMenuItemBool::Hide(void) { + pixmap->SetLayer(-1); + pixmapVal->SetLayer(-1); +} + +void cRecMenuItemBool::Show(void) { + pixmap->SetLayer(4); + pixmapVal->SetLayer(5); +} + +void cRecMenuItemBool::Draw(void) { + int textY = (height - font->Height()) / 2; + pixmap->DrawText(cPoint(10, textY), *text, colorText, colorTextBack, font); + DrawValue(); +} + +void cRecMenuItemBool::DrawValue(void) { + pixmapVal->Fill(clrTransparent); + cString strIcon = yes?"yes":"no"; + int iconSize = height - 8; + int iconX = width - iconSize - 10; + int iconY = (height - iconSize) / 2; + cImageLoader imgLoader; + if (imgLoader.LoadIcon(strIcon, iconSize)) { + cImage icon = imgLoader.GetImage(); + pixmapVal->DrawImage(cPoint(iconX, iconY), icon); + } +} + +eRecMenuState cRecMenuItemBool::ProcessKey(eKeys Key) { + switch (Key & ~k_Repeat) { + case kLeft: + case kRight: + yes = !yes; + DrawValue(); + if (refresh) + return rmsRefresh; + else + return rmsConsumed; + break; + default: + break; + } + return rmsNotConsumed; +} + +// --- cRecMenuItemSelect ------------------------------------------------------- +cRecMenuItemSelect::cRecMenuItemSelect(cString text, + const char * const *Strings, + int initialVal, + int numValues, + bool active) { + selectable = true; + this->text = text; + strings = Strings; + this->numValues = numValues; + if ((initialVal < 0) || (initialVal > numValues-1)) + this->currentVal = 0; + else + this->currentVal = initialVal; + this->active = active; + height = 3 * font->Height() / 2; + pixmapVal = NULL; +} + +cRecMenuItemSelect::~cRecMenuItemSelect(void) { + if (pixmapVal) + osdManager.releasePixmap(pixmapVal); +} + +void cRecMenuItemSelect::SetPixmaps(void) { + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapVal = osdManager.requestPixmap(5, cRect(x, y, width, height)); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapVal->SetViewPort(cRect(x, y, width, height)); + } +} + +void cRecMenuItemSelect::Hide(void) { + pixmap->SetLayer(-1); + pixmapVal->SetLayer(-1); +} + +void cRecMenuItemSelect::Show(void) { + pixmap->SetLayer(4); + pixmapVal->SetLayer(5); +} + +void cRecMenuItemSelect::Draw(void) { + int textY = (height - font->Height()) / 2; + pixmap->DrawText(cPoint(10, textY), *text, colorText, colorTextBack, font); + DrawValue(); +} + +void cRecMenuItemSelect::DrawValue(void) { + pixmapVal->Fill(clrTransparent); + const char *textVal = strings[currentVal]; + int iconSize = min(128, height); + int textX = width - font->Width(textVal) - iconSize; + int textY = (height - font->Height()) / 2; + pixmapVal->DrawText(cPoint(textX, textY), textVal, colorText, clrTransparent, font); + int iconLeftX = textX - iconSize; + int iconRightX = width - iconSize; + int iconY = (height - iconSize) / 2; + cImageLoader imgLoader; + if (imgLoader.LoadIcon("arrow_left", iconSize)) { + cImage icon = imgLoader.GetImage(); + pixmapVal->DrawImage(cPoint(iconLeftX, iconY), icon); + } + if (imgLoader.LoadIcon("arrow_right", iconSize)) { + cImage icon = imgLoader.GetImage(); + pixmapVal->DrawImage(cPoint(iconRightX, iconY), icon); + } +} + +eRecMenuState cRecMenuItemSelect::ProcessKey(eKeys Key) { + int oldValue = currentVal; + switch (Key & ~k_Repeat) { + case kLeft: + currentVal--; + if (currentVal<0) + currentVal = numValues - 1; + DrawValue(); + return rmsConsumed; + break; + case kRight: + currentVal = (currentVal+1)%numValues; + DrawValue(); + return rmsConsumed; + break; + default: + break; + } + return rmsNotConsumed; +} + +// --- cRecMenuItemText ------------------------------------------------------- +cRecMenuItemText::cRecMenuItemText(cString title, + char *initialVal, + int length, + bool active) { + selectable = true; + this->title = title; + value = initialVal; + this->active = active; + height = 3 * font->Height(); + pixmapVal = NULL; + pixmapKeyboard = NULL; + pixmapKeyboardHighlight = NULL; + pixmapKeyboardIcons = NULL; + keyboardWidth = 50; + gridHeight = 3 * fontSmall->Height(); + keyboardHeight = 5 * gridHeight; + + this->length = length; + allowed = trVDR(FileNameChars); + pos = -1; + offset = 0; + insert = uppercase = false; + newchar = true; + lengthUtf8 = 0; + valueUtf8 = NULL; + allowedUtf8 = NULL; + charMapUtf8 = NULL; + currentCharUtf8 = NULL; + lastKey = kNone; + keyboardDrawn = false; +} + +cRecMenuItemText::~cRecMenuItemText(void) { + if (pixmapVal) + osdManager.releasePixmap(pixmapVal); + if (pixmapKeyboard) + delete pixmapKeyboard; + if (pixmapKeyboardHighlight) + osdManager.releasePixmap(pixmapKeyboardHighlight); + if (pixmapKeyboardIcons) + osdManager.releasePixmap(pixmapKeyboardIcons); + delete[] valueUtf8; + delete[] allowedUtf8; + delete[] charMapUtf8; +} + +void cRecMenuItemText::SetPixmaps(void) { + int xPixmapVal = x + 20; + int yPixmapVal = y + height / 2 + 10; + int widthPixmapVal = width - 40; + int heightPixmapVal = height / 2 - 20; + int keyboardX = x + (100 - keyboardWidth)*width / 100; + int keyboardY = y + height; + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapVal = osdManager.requestPixmap(5, cRect(xPixmapVal, yPixmapVal, widthPixmapVal, heightPixmapVal)); + pixmapKeyboard = new cStyledPixmap(osdManager.requestPixmap(-1, cRect(keyboardX, keyboardY, keyboardWidth*width/100, keyboardHeight))); + pixmapKeyboardHighlight = osdManager.requestPixmap(-1, cRect(keyboardX, keyboardY, keyboardWidth*width/100, keyboardHeight)); + pixmapKeyboardIcons = osdManager.requestPixmap(-1, cRect(keyboardX, keyboardY, keyboardWidth*width/100, keyboardHeight)); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapVal->SetViewPort(cRect(xPixmapVal, yPixmapVal, widthPixmapVal, heightPixmapVal)); + pixmapKeyboard->SetViewPort(cRect(keyboardX, keyboardY, keyboardWidth*width/100, keyboardHeight)); + pixmapKeyboardHighlight->SetViewPort(cRect(keyboardX, keyboardY, keyboardWidth*width/100, keyboardHeight)); + pixmapKeyboardIcons->SetViewPort(cRect(keyboardX, keyboardY, keyboardWidth*width/100, keyboardHeight)); + } + pixmapKeyboardIcons->Fill(clrTransparent); +} + +void cRecMenuItemText::Hide(void) { + pixmap->SetLayer(-1); + pixmapVal->SetLayer(-1); + pixmapKeyboard->SetLayer(-1); + pixmapKeyboardHighlight->SetLayer(-1); + pixmapKeyboardIcons->SetLayer(-1); +} + +void cRecMenuItemText::Show(void) { + pixmap->SetLayer(4); + pixmapVal->SetLayer(5); +} + +void cRecMenuItemText::setBackground() { + if (!active) { + DeactivateKeyboard(); + } + cRecMenuItem::setBackground(); +} + +void cRecMenuItemText::Draw(void) { + int textY = (height/2 - font->Height()) / 2; + pixmap->DrawText(cPoint(10, textY), *title, colorText, colorTextBack, font); + DrawValue(value); +} + +void cRecMenuItemText::DrawValue(char *newValue) { + tColor clrBack = InEditMode()?theme.Color(clrRecMenuTextActiveBack):theme.Color(clrRecMenuTextBack); + pixmapVal->Fill(clrBack); + int textX = pixmapVal->DrawPort().Width() - font->Width(newValue) - 10; + int textY = (pixmapVal->DrawPort().Height() - font->Height()) / 2; + pixmapVal->DrawText(cPoint(textX, textY), newValue, colorText, clrTransparent, font); +} + +void cRecMenuItemText::ActivateKeyboard(void) { + pixmapKeyboard->SetLayer(6); + pixmapKeyboardHighlight->SetLayer(6); + pixmapKeyboardIcons->SetLayer(6); + pixmapKeyboardHighlight->Fill(clrTransparent); + + if (keyboardDrawn) + return; + + pixmapKeyboard->setColor(theme.Color(clrRecMenuKeyboardBack), theme.Color(clrRecMenuKeyboardBack)); + pixmapKeyboard->drawBackground(); + + int widthKeyBoard = pixmapKeyboard->Width(); + gridWidth = widthKeyBoard / 3; + + pixmapKeyboard->DrawRectangle(cRect(0, 0, widthKeyBoard, keyboardHeight), theme.Color(clrRecMenuKeyboardBorder)); + int num = 1; + for (int row = 0; row < 5; row++) { + for (int col = 0; col < 3; col++) { + int X = col*gridWidth; + int Y = row*gridHeight; + tColor clrBack = theme.Color(clrRecMenuKeyboardBack); + if (num==13) + clrBack = theme.Color(clrButtonRedKeyboard); + else if (num==14) + clrBack = theme.Color(clrButtonGreenKeyboard); + else if (num==15) + clrBack = theme.Color(clrButtonYellowKeyboard); + pixmapKeyboard->DrawRectangle(cRect(X+2, Y+2, gridWidth-4, gridHeight-4), clrBack); + pixmapKeyboard->DrawEllipse(cRect(X, Y, 20, 20), theme.Color(clrRecMenuKeyboardBorder),-2); + pixmapKeyboard->DrawEllipse(cRect(X, Y+gridHeight-20, 20, 20), theme.Color(clrRecMenuKeyboardBorder),-3); + pixmapKeyboard->DrawEllipse(cRect(X+gridWidth-20, Y+gridHeight-20, 20, 20), theme.Color(clrRecMenuKeyboardBorder),-4); + pixmapKeyboard->DrawEllipse(cRect(X+gridWidth-20, Y, 20, 20), theme.Color(clrRecMenuKeyboardBorder),-1); + bool draw = false; + bool drawIcon = false; + cString strNum; + cString strIcon; + cImageLoader imgLoader; + if (num<10) { + strNum = *cString::sprintf("%d", num); + draw = true; + } else if (num == 11) { + strNum = *cString::sprintf("%d", 0); + draw = true; + } else if (num==13) { + strIcon = "icon_shift"; + drawIcon = true; + } else if (num==14) { + strIcon = "icon_del_ins"; + drawIcon = true; + } else if (num==15) { + strIcon = "icon_backspace"; + drawIcon = true; + } + if (draw) { + int numX = X + (gridWidth - font->Width(*strNum))/2; + int numY = Y + font->Height() / 4; + pixmapKeyboard->DrawText(cPoint(numX, numY), *strNum, colorText, colorTextBack, font); + char *smsKeys = GetSMSKeys(num); + int smsKeysX = X + (gridWidth - fontSmall->Width(smsKeys))/2; + int smsKeysY = Y + gridHeight - fontSmall->Height() - 10; + pixmapKeyboard->DrawText(cPoint(smsKeysX, smsKeysY), smsKeys, theme.Color(clrRecMenuKeyboardBorder), colorTextBack, fontSmall); + delete[] smsKeys; + } + if (drawIcon) { + int iconSize = gridHeight - 10; + if (imgLoader.LoadIcon(strIcon, iconSize)) { + cImage icon = imgLoader.GetImage(); + int iconX = X + (gridWidth - iconSize) / 2; + pixmapKeyboardIcons->DrawImage(cPoint(iconX, Y + 5), icon); + } + } + num++; + } + } + keyboardDrawn = true; +} + +void cRecMenuItemText::DeactivateKeyboard(void) { + pixmapKeyboard->SetLayer(-1); + pixmapKeyboardHighlight->SetLayer(-1); + pixmapKeyboardIcons->SetLayer(-1); +} + +void cRecMenuItemText::HighlightSMSKey(int num) { + int x = 0; + int y = 0; + if (num == 0) { + x = gridWidth; + y = 3 * gridHeight; + } else { + x = (num-1)%3 * gridWidth; + y = (num-1)/3 * gridHeight; + } + pixmapKeyboardHighlight->DrawRectangle(cRect(x, y, gridWidth, gridHeight), theme.Color(clrRecMenuKeyboardHigh)); +} + +void cRecMenuItemText::ClearSMSKey(void) { + pixmapKeyboardHighlight->Fill(clrTransparent); +} + + +char *cRecMenuItemText::GetSMSKeys(int num) { + if (num == 11) + num = 0; + if (num > 9) + return NULL; + + currentCharUtf8 = charMapUtf8; + int pos = num; + while (pos > 0 && *currentCharUtf8) { + if (*currentCharUtf8++ == '\t') + pos--; + } + while (*currentCharUtf8 && *currentCharUtf8 != '\t' && !IsAllowed(*currentCharUtf8)) { + currentCharUtf8++; + } + uint *startCharUtf8 = currentCharUtf8; + int numChars = 0; + bool specialChar = false; + while(*currentCharUtf8 && *currentCharUtf8 != '\t' && IsAllowed(*currentCharUtf8)) { + currentCharUtf8++; + if (Utf8CharSet(*currentCharUtf8) > 1) + specialChar = true; + numChars++; + } + char buf[100]; + char *p = buf; + int addition = 0; + if (specialChar) + addition = 1; + Utf8FromArray(startCharUtf8, p, numChars+addition); + int maxChars = min(numChars+1+addition, 8); + char *smskey = new char[maxChars]; + Utf8Strn0Cpy(smskey, p, maxChars); + return smskey; +} + +void cRecMenuItemText::EnterEditMode(void) { + if (!valueUtf8) { + valueUtf8 = new uint[length]; + lengthUtf8 = Utf8ToArray(value, valueUtf8, length); + int l = strlen(allowed) + 1; + allowedUtf8 = new uint[l]; + Utf8ToArray(allowed, allowedUtf8, l); + const char *charMap = trVDR("CharMap$ 0\t-.,1#~\\^$[]|()*+?{}/:%@&\tabc2\tdef3\tghi4\tjkl5\tmno6\tpqrs7\ttuv8\twxyz9"); + l = strlen(charMap) + 1; + charMapUtf8 = new uint[l]; + Utf8ToArray(charMap, charMapUtf8, l); + currentCharUtf8 = charMapUtf8; + AdvancePos(); + } +} + +void cRecMenuItemText::LeaveEditMode(bool SaveValue) { + if (valueUtf8) { + if (SaveValue) { + Utf8FromArray(valueUtf8, value, length); + stripspace(value); + } + lengthUtf8 = 0; + delete[] valueUtf8; + valueUtf8 = NULL; + delete[] allowedUtf8; + allowedUtf8 = NULL; + delete[] charMapUtf8; + charMapUtf8 = NULL; + pos = -1; + offset = 0; + newchar = true; + } +} + +void cRecMenuItemText::AdvancePos(void) { + if (pos < length - 2 && pos < lengthUtf8) { + if (++pos >= lengthUtf8) { + if (pos >= 2 && valueUtf8[pos - 1] == ' ' && valueUtf8[pos - 2] == ' ') + pos--; // allow only two blanks at the end + else { + valueUtf8[pos] = ' '; + valueUtf8[pos + 1] = 0; + lengthUtf8++; + } + } + } + newchar = true; + if (!insert && Utf8is(alpha, valueUtf8[pos])) + uppercase = Utf8is(upper, valueUtf8[pos]); +} + +uint cRecMenuItemText::Inc(uint c, bool Up) { + uint *p = IsAllowed(c); + if (!p) + p = allowedUtf8; + if (Up) { + if (!*++p) + p = allowedUtf8; + } else if (--p < allowedUtf8) { + p = allowedUtf8; + while (*p && *(p + 1)) + p++; + } + return *p; +} + +void cRecMenuItemText::Type(uint c) { + if (insert && lengthUtf8 < length - 1) + Insert(); + valueUtf8[pos] = c; + if (pos < length - 2) + pos++; + if (pos >= lengthUtf8) { + valueUtf8[pos] = ' '; + valueUtf8[pos + 1] = 0; + lengthUtf8 = pos + 1; + } +} + +void cRecMenuItemText::Insert(void) { + memmove(valueUtf8 + pos + 1, valueUtf8 + pos, (lengthUtf8 - pos + 1) * sizeof(*valueUtf8)); + lengthUtf8++; + valueUtf8[pos] = ' '; +} + +void cRecMenuItemText::Delete(void) { + memmove(valueUtf8 + pos, valueUtf8 + pos + 1, (lengthUtf8 - pos) * sizeof(*valueUtf8)); + lengthUtf8--; +} + +uint *cRecMenuItemText::IsAllowed(uint c) { + if (allowedUtf8) { + for (uint *a = allowedUtf8; *a; a++) { + if (c == *a) + return a; + } + } + return NULL; +} + +void cRecMenuItemText::SetText(void) { + if (InEditMode()) { + int textAreaWidth = width - 20; + textAreaWidth -= font->Width("[]"); + textAreaWidth -= font->Width("<>"); // reserving this anyway make the whole thing simpler + if (pos < offset) + offset = pos; + int WidthFromOffset = 0; + int EndPos = lengthUtf8; + for (int i = offset; i < lengthUtf8; i++) { + WidthFromOffset += font->Width(valueUtf8[i]); + if (WidthFromOffset > textAreaWidth) { + if (pos >= i) { + do { + WidthFromOffset -= font->Width(valueUtf8[offset]); + offset++; + } while (WidthFromOffset > textAreaWidth && offset < pos); + EndPos = pos + 1; + } else { + EndPos = i; + break; + } + } + } + char buf[1000]; + char *p = buf; + if (offset) + *p++ = '<'; + p += Utf8FromArray(valueUtf8 + offset, p, sizeof(buf) - (p - buf), pos - offset); + *p++ = '['; + if (insert && newchar) + *p++ = ']'; + p += Utf8FromArray(&valueUtf8[pos], p, sizeof(buf) - (p - buf), 1); + if (!(insert && newchar)) + *p++ = ']'; + p += Utf8FromArray(&valueUtf8[pos + 1], p, sizeof(buf) - (p - buf), EndPos - pos - 1); + if (EndPos != lengthUtf8) + *p++ = '>'; + *p = 0; + DrawValue(buf); + } else { + DrawValue(value); + } +} + +eRecMenuState cRecMenuItemText::ProcessKey(eKeys Key) { + bool consumed = false; + bool SameKey = NORMALKEY(Key) == lastKey; + ClearSMSKey(); + if (Key != kNone) { + lastKey = NORMALKEY(Key); + } else if (!newchar && k0 <= lastKey && lastKey <= k9 && autoAdvanceTimeout.TimedOut()) { + AdvancePos(); + newchar = true; + currentCharUtf8 = NULL; + SetText(); + return rmsConsumed; + } + + switch ((int)Key) { + case kRed: // Switch between upper- and lowercase characters + if (InEditMode()) { + if (!insert || !newchar) { + uppercase = !uppercase; + valueUtf8[pos] = uppercase ? Utf8to(upper, valueUtf8[pos]) : Utf8to(lower, valueUtf8[pos]); + } + consumed = true; + } + break; + case kGreen: // Toggle insert/overwrite modes + if (InEditMode()) { + insert = !insert; + newchar = true; + consumed = true; + } + break; + case kYellow|k_Repeat: + case kYellow: // Remove the character at the current position; in insert mode it is the character to the right of the cursor + if (InEditMode()) { + if (lengthUtf8 > 1) { + if (!insert || pos < lengthUtf8 - 1) + Delete(); + else if (insert && pos == lengthUtf8 - 1) + valueUtf8[pos] = ' '; // in insert mode, deleting the last character replaces it with a blank to keep the cursor position + // reduce position, if we removed the last character + if (pos == lengthUtf8) + pos--; + } else if (lengthUtf8 == 1) + valueUtf8[0] = ' '; // This is the last character in the string, replace it with a blank + if (Utf8is(alpha, valueUtf8[pos])) + uppercase = Utf8is(upper, valueUtf8[pos]); + newchar = true; + consumed = true; + } + break; + case kLeft|k_Repeat: + case kLeft: + + if (pos > 0) { + if (!insert || newchar) + pos--; + newchar = true; + if (!insert && Utf8is(alpha, valueUtf8[pos])) + uppercase = Utf8is(upper, valueUtf8[pos]); + } + consumed = true; + break; + case kRight|k_Repeat: + case kRight: + if (InEditMode()) { + AdvancePos(); + } else { + EnterEditMode(); + ActivateKeyboard(); + } + consumed = true; + break; + case kUp|k_Repeat: + case kUp: + case kDown|k_Repeat: + case kDown: + if (InEditMode()) { + if (insert && newchar) { + // create a new character in insert mode + if (lengthUtf8 < length - 1) + Insert(); + } + if (uppercase) + valueUtf8[pos] = Utf8to(upper, Inc(Utf8to(lower, valueUtf8[pos]), NORMALKEY(Key) == kUp)); + else + valueUtf8[pos] = Inc( valueUtf8[pos], NORMALKEY(Key) == kUp); + newchar = false; + consumed = true; + } + break; + case k0|k_Repeat ... k9|k_Repeat: + case k0 ... k9: { + if (InEditMode()) { + if (Setup.NumberKeysForChars) { + HighlightSMSKey(NORMALKEY(Key) - k0); + if (!SameKey) { + if (!newchar) + AdvancePos(); + currentCharUtf8 = NULL; + } + if (!currentCharUtf8 || !*currentCharUtf8 || *currentCharUtf8 == '\t') { + // find the beginning of the character map entry for Key + int n = NORMALKEY(Key) - k0; + currentCharUtf8 = charMapUtf8; + while (n > 0 && *currentCharUtf8) { + if (*currentCharUtf8++ == '\t') + n--; + } + // find first allowed character + while (*currentCharUtf8 && *currentCharUtf8 != '\t' && !IsAllowed(*currentCharUtf8)) + currentCharUtf8++; + } + if (*currentCharUtf8 && *currentCharUtf8 != '\t') { + if (insert && newchar) { + // create a new character in insert mode + if (lengthUtf8 < length - 1) + Insert(); + } + valueUtf8[pos] = *currentCharUtf8; + if (uppercase) + valueUtf8[pos] = Utf8to(upper, valueUtf8[pos]); + // find next allowed character + do { + currentCharUtf8++; + } while (*currentCharUtf8 && *currentCharUtf8 != '\t' && !IsAllowed(*currentCharUtf8)); + newchar = false; + autoAdvanceTimeout.Set(AUTO_ADVANCE_TIMEOUT); + } + } else { + Type('0' + NORMALKEY(Key) - k0); + } + consumed = true; + } + break; } + case kBack: + case kOk: + if (InEditMode()) { + LeaveEditMode(Key == kOk); + DeactivateKeyboard(); + } else { + EnterEditMode(); + ActivateKeyboard(); + } + consumed = true; + break; + default: + if (InEditMode() && BASICKEY(Key) == kKbd) { + int c = KEYKBD(Key); + if (c <= 0xFF) { + if (IsAllowed(Utf8to(lower, c))) + Type(c); + else { + switch (c) { + case 0x7F: // backspace + if (pos > 0) { + pos--; + ProcessKey(kYellow); + } + break; + default: ; + } + } + } else { + switch (c) { + case kfHome: pos = 0; break; + case kfEnd: pos = lengthUtf8 - 1; break; + case kfIns: ProcessKey(kGreen); + case kfDel: ProcessKey(kYellow); + default: ; + } + } + consumed = true; + } + break; + } + SetText(); + if (consumed) + return rmsConsumed; + return rmsNotConsumed; +} + +// --- cRecMenuItemTime ------------------------------------------------------- +cRecMenuItemTime::cRecMenuItemTime(cString text, + int initialVal, + bool active) { + selectable = true; + this->text = text; + this->value = initialVal; + hh = value / 100; + mm = value % 100; + pos = 0; + fresh = true; + this->active = active; + height = 3 * font->Height() / 2; + pixmapVal = NULL; +} + +cRecMenuItemTime::~cRecMenuItemTime(void) { + if (pixmapVal) + osdManager.releasePixmap(pixmapVal); +} + +void cRecMenuItemTime::SetPixmaps(void) { + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapVal = osdManager.requestPixmap(5, cRect(x, y, width, height)); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapVal->SetViewPort(cRect(x, y, width, height)); + } +} + +void cRecMenuItemTime::Hide(void) { + pixmap->SetLayer(-1); + pixmapVal->SetLayer(-1); +} + +void cRecMenuItemTime::Show(void) { + pixmap->SetLayer(4); + pixmapVal->SetLayer(5); +} + +void cRecMenuItemTime::Draw(void) { + int textY = (height - font->Height()) / 2; + pixmap->DrawText(cPoint(10, textY), *text, colorText, colorTextBack, font); + DrawValue(); +} + +void cRecMenuItemTime::DrawValue(void) { + pixmapVal->Fill(clrTransparent); + char buf[10]; + switch (pos) { + case 1: snprintf(buf, sizeof(buf), "%01d-:--", hh / 10); break; + case 2: snprintf(buf, sizeof(buf), "%02d:--", hh); break; + case 3: snprintf(buf, sizeof(buf), "%02d:%01d-", hh, mm / 10); break; + default: snprintf(buf, sizeof(buf), "%02d:%02d", hh, mm); + } + int textX = width - font->Width(buf) - 10; + int textY = (height - font->Height()) / 2; + pixmapVal->DrawText(cPoint(textX, textY), buf, colorText, clrTransparent, font); +} + +eRecMenuState cRecMenuItemTime::ProcessKey(eKeys Key) { + switch (Key & ~k_Repeat) { + case kLeft|k_Repeat: + case kLeft: { + if (--mm < 0) { + mm = 59; + if (--hh < 0) + hh = 23; + } + fresh = true; + value = hh * 100 + mm; + DrawValue(); + return rmsConsumed; + break; } + case kRight|k_Repeat: + case kRight: { + if (++mm > 59) { + mm = 0; + if (++hh > 23) + hh = 0; + } + fresh = true; + value = hh * 100 + mm; + DrawValue(); + return rmsConsumed; + break; } + case k0|k_Repeat ... k9|k_Repeat: + case k0 ... k9: { + if (fresh || pos > 3) { + pos = 0; + fresh = false; + } + int n = Key - k0; + switch (pos) { + case 0: + if (n <= 2) { + hh = n * 10; + mm = 0; + pos++; + } + break; + case 1: + if (hh + n <= 23) { + hh += n; + pos++; + } + break; + case 2: + if (n <= 5) { + mm += n * 10; + pos++; + } + break; + case 3: + if (mm + n <= 59) { + mm += n; + pos++; + } + break; + default: ; + } + value = hh * 100 + mm; + DrawValue(); + return rmsConsumed; + break; } + default: + break; + } + return rmsNotConsumed; +} + +// --- cRecMenuItemDay ------------------------------------------------------- +cRecMenuItemDay::cRecMenuItemDay(cString text, + time_t initialVal, + bool active) { + selectable = true; + this->text = text; + this->currentVal = cTimer::SetTime(initialVal, 0); + this->active = active; + height = 3 * font->Height() / 2; + pixmapVal = NULL; +} + +cRecMenuItemDay::~cRecMenuItemDay(void) { + if (pixmapVal) + osdManager.releasePixmap(pixmapVal); +} + +void cRecMenuItemDay::SetPixmaps(void) { + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapVal = osdManager.requestPixmap(5, cRect(x, y, width, height)); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapVal->SetViewPort(cRect(x, y, width, height)); + } +} + +void cRecMenuItemDay::Hide(void) { + pixmap->SetLayer(-1); + pixmapVal->SetLayer(-1); +} + +void cRecMenuItemDay::Show(void) { + pixmap->SetLayer(4); + pixmapVal->SetLayer(5); +} + +void cRecMenuItemDay::Draw(void) { + int textY = (height - font->Height()) / 2; + pixmap->DrawText(cPoint(10, textY), *text, colorText, colorTextBack, font); + DrawValue(); +} + +void cRecMenuItemDay::DrawValue(void) { + pixmapVal->Fill(clrTransparent); + cString textVal = DateString(currentVal); + int textX = width - font->Width(*textVal) - 10; + int textY = (height - font->Height()) / 2; + pixmapVal->DrawText(cPoint(textX, textY), *textVal, colorText, clrTransparent, font); +} + +eRecMenuState cRecMenuItemDay::ProcessKey(eKeys Key) { + switch (Key & ~k_Repeat) { + case kLeft: + currentVal -= 60*60*24; + DrawValue(); + return rmsConsumed; + break; + case kRight: + currentVal += 60*60*24; + DrawValue(); + return rmsConsumed; + break; + default: + break; + } + return rmsNotConsumed; +} + +// --- cRecMenuItemTimer ------------------------------------------------------- +cRecMenuItemTimer::cRecMenuItemTimer(const cTimer *timer, + eRecMenuState action1, + eRecMenuState action2, + eRecMenuState action3, + time_t conflictStart, + time_t conflictStop, + time_t overlapStart, + time_t overlapStop, + bool active) { + selectable = true; + this->timer = timer; + this->action = action1; + this->action2 = action2; + this->action3 = action3; + iconActive = 0; + this->conflictStart = conflictStart; + this->conflictStop = conflictStop; + this->overlapStart = overlapStart; + this->overlapStop = overlapStop; + this->active = active; + height = 3 * font->Height(); + pixmapIcons = NULL; +} + +cRecMenuItemTimer::~cRecMenuItemTimer(void) { + if (pixmapIcons) + osdManager.releasePixmap(pixmapIcons); + if (pixmapStatus) + osdManager.releasePixmap(pixmapStatus); +} + +void cRecMenuItemTimer::SetPixmaps(void) { + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapStatus = osdManager.requestPixmap(5, cRect(x, y, width, height)); + pixmapIcons = osdManager.requestPixmap(6, cRect(x, y, width, height)); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapStatus->SetViewPort(cRect(x, y, width, height)); + pixmapIcons->SetViewPort(cRect(x, y, width, height)); + } + pixmapStatus->Fill(clrTransparent); + pixmapIcons->Fill(clrTransparent); +} + +void cRecMenuItemTimer::Hide(void) { + pixmap->SetLayer(-1); + pixmapStatus->SetLayer(-1); + pixmapIcons->SetLayer(-1); +} + +void cRecMenuItemTimer::Show(void) { + pixmap->SetLayer(4); + pixmapStatus->SetLayer(5); + pixmapIcons->SetLayer(6); +} + +void cRecMenuItemTimer::Draw(void) { + const cChannel *channel = timer->Channel(); + cString channelName(""); + int channelTransponder = 0; + if (channel) { + channelName = channel->Name(); + channelTransponder = channel->Transponder(); + } + int logoX = DrawIcons(); + int logoWidth = height * tvguideConfig.logoWidthRatio / tvguideConfig.logoHeightRatio; + cImageLoader imgLoader; + if (!tvguideConfig.hideChannelLogos) { + if (imgLoader.LoadLogo(*channelName, logoWidth, height)) { + cImage logo = imgLoader.GetImage(); + pixmapIcons->DrawImage(cPoint(logoX, 0), logo); + logoX += logoWidth + 5; + } + } + int textX = logoX; + int textHeightLine1 = (height/2 - font->Height()) / 2; + int textHeightLine2 = height/2 - 5 + (height/4 - fontSmall->Height()) / 2; + int textHeightLine3 = 3*height/4 - 5 + (height/4 - fontSmall->Height()) / 2; + const cEvent *event = timer->Event(); + cString timerTitle(""); + if (event) + timerTitle = event->Title(); + cString timeStart = DayDateTime(timer->StartTime()); + cString timeEnd = TimeString(timer->StopTime()); + cString timerTime = cString::sprintf("%s - %s", *timeStart, *timeEnd); + cString channelInfo = cString::sprintf("%s, %s %d", *channelName, tr("Transp."), channelTransponder); + pixmap->DrawText(cPoint(textX, textHeightLine1), *timerTitle, theme.Color(clrFont), clrTransparent, font); + pixmap->DrawText(cPoint(textX, textHeightLine2), *timerTime, theme.Color(clrFont), clrTransparent, fontSmall); + pixmap->DrawText(cPoint(textX, textHeightLine3), *channelInfo, theme.Color(clrFont), clrTransparent, fontSmall); + + DrawTimerConflict(); +} + +int cRecMenuItemTimer::DrawIcons(void) { + int iconsX = 10; + int iconSize = 64; + int iconY = (height - iconSize) / 2; + cString iconInfo, iconDelete, iconEdit; + if (active) { + iconInfo = (iconActive==0)?"info_active":"info_inactive"; + iconDelete = (iconActive==1)?"delete_active":"delete_inactive"; + iconEdit = (iconActive==2)?"edit_active":"edit_inactive"; + } else { + iconInfo = "info_inactive"; + iconDelete = "delete_inactive"; + iconEdit = "edit_inactive"; + } + cImageLoader imgLoader; + if (imgLoader.LoadIcon(iconInfo, iconSize)) { + cImage icon = imgLoader.GetImage(); + pixmapIcons->DrawImage(cPoint(iconsX, iconY), icon); + iconsX += iconSize + 5; + } + if (imgLoader.LoadIcon(iconDelete, iconSize)) { + cImage icon = imgLoader.GetImage(); + pixmapIcons->DrawImage(cPoint(iconsX, iconY), icon); + iconsX += iconSize + 5; + } + if (imgLoader.LoadIcon(iconEdit, iconSize)) { + cImage icon = imgLoader.GetImage(); + pixmapIcons->DrawImage(cPoint(iconsX, iconY), icon); + iconsX += iconSize + 5; + } + return iconsX; +} + +void cRecMenuItemTimer::DrawTimerConflict(void) { + int widthConfl = 30 * width / 100; + int xConfl = width - widthConfl; + int heightConflBar = height / 4; + int yConflBar = (height - heightConflBar) / 2; + pixmapStatus->DrawRectangle(cRect(xConfl, 0, widthConfl, height), theme.Color(clrRecMenuTimerConflictBackground)); + + int completeWidthSecs = conflictStop - conflictStart; + int xConfBar = xConfl + (timer->StartTime() - conflictStart) * widthConfl / completeWidthSecs; + int widthConfBar = (timer->StopTime() - timer->StartTime()) * widthConfl / completeWidthSecs; + pixmapStatus->DrawRectangle(cRect(xConfBar, yConflBar, widthConfBar, heightConflBar), theme.Color(clrRecMenuTimerConflictBar)); + + int xOverlap = xConfl + (overlapStart - conflictStart) * widthConfl / completeWidthSecs; + int widthOverlap = (overlapStop - overlapStart) * widthConfl / completeWidthSecs; + + pixmapIcons->DrawRectangle(cRect(xOverlap, 0, widthOverlap, height), theme.Color(clrRecMenuTimerConflictOverlap)); + + pixmapStatus->DrawRectangle(cRect(xConfl-3,2,1,height-4), theme.Color(clrBorder)); + pixmapStatus->DrawRectangle(cRect(xConfl-2,0,2,height), theme.Color(clrBackground)); +} + +eRecMenuState cRecMenuItemTimer::ProcessKey(eKeys Key) { + switch (Key & ~k_Repeat) { + case kLeft: + if (iconActive > 0) { + iconActive--; + DrawIcons(); + return rmsConsumed; + } else + return rmsNotConsumed; + break; + case kRight: + if (iconActive < 2) { + iconActive++; + DrawIcons(); + return rmsConsumed; + } else + return rmsNotConsumed; + break; + case kOk: + if (iconActive == 0) + return action; + else if (iconActive == 1) + return action2; + else if (iconActive == 2) + return action3; + break; + default: + break; + } + return rmsNotConsumed; +} + +// --- cRecMenuItemTimerConflictHeader ------------------------------------------------------- + +cRecMenuItemTimerConflictHeader::cRecMenuItemTimerConflictHeader(time_t conflictStart, + time_t conflictStop, + time_t overlapStart, + time_t overlapStop) { + selectable = false; + active = false; + this->conflictStart = conflictStart; + this->conflictStop = conflictStop; + this->overlapStart = overlapStart; + this->overlapStop = overlapStop; + height = 3*font->Height()/2; + pixmapStatus = NULL; +} + +cRecMenuItemTimerConflictHeader::~cRecMenuItemTimerConflictHeader(void) { + if (pixmapStatus) + osdManager.releasePixmap(pixmapStatus); +} + +void cRecMenuItemTimerConflictHeader::SetPixmaps(void) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapStatus = osdManager.requestPixmap(5, cRect(x, y, width, height)); + pixmapStatus->Fill(clrTransparent); +} + +void cRecMenuItemTimerConflictHeader::Hide(void) { + pixmap->SetLayer(-1); + pixmapStatus->SetLayer(-1); +} + +void cRecMenuItemTimerConflictHeader::Show(void) { + pixmap->SetLayer(4); + pixmapStatus->SetLayer(5); +} + +void cRecMenuItemTimerConflictHeader::setBackground(void) { + pixmap->Fill(clrTransparent); +} + +void cRecMenuItemTimerConflictHeader::Draw(void) { + int widthConfl = 30 * width / 100; + int xConfl = width - widthConfl; + cString headerText = tr("Timer Conflict"); + int xHeader = (xConfl - font->Width(*headerText)) / 2; + int yHeader = (height - font->Height()) / 2; + pixmap->DrawText(cPoint(xHeader, yHeader), *headerText, theme.Color(clrFont), clrTransparent, font); + + pixmap->DrawRectangle(cRect(xConfl, 0, widthConfl, height), theme.Color(clrRecMenuTimerConflictBackground)); + + int completeWidthSecs = conflictStop - conflictStart; + int xOverlap = xConfl + (overlapStart - conflictStart) * widthConfl / completeWidthSecs; + int yOverlap = height - fontSmall->Height(); + int widthOverlap = (overlapStop - overlapStart) * widthConfl / completeWidthSecs; + + pixmapStatus->DrawRectangle(cRect(xOverlap, yOverlap, widthOverlap, height), theme.Color(clrRecMenuTimerConflictOverlap)); + + cString strConflStart = TimeString(conflictStart); + cString strConflStop = TimeString(conflictStop); + cString strOverlapStart = TimeString(overlapStart); + cString strOverlapStop = TimeString(overlapStop); + int y1 = 5; + int y2 = yOverlap; + int xConflStart = xConfl + 2; + int xConflStop = width - fontSmall->Width(*strConflStop) - 2; + int xOverlapStart = xOverlap - fontSmall->Width(*strOverlapStart) - 2; + int xOverlapStop = xOverlap + widthOverlap + 2; + pixmap->DrawText(cPoint(xConflStart, y1), *strConflStart, theme.Color(clrRecMenuTimerConflictBar), clrTransparent, fontSmall); + pixmap->DrawText(cPoint(xConflStop, y1), *strConflStop, theme.Color(clrRecMenuTimerConflictBar), clrTransparent, fontSmall); + pixmap->DrawText(cPoint(xOverlapStart, y2), *strOverlapStart, theme.Color(clrRecMenuTimerConflictOverlap), clrTransparent, fontSmall); + pixmap->DrawText(cPoint(xOverlapStop, y2), *strOverlapStop, theme.Color(clrRecMenuTimerConflictOverlap), clrTransparent, fontSmall); +} + +// --- cRecMenuItemEvent ------------------------------------------------------- +cRecMenuItemEvent::cRecMenuItemEvent(const cEvent *event, + eRecMenuState action1, + eRecMenuState action2, + bool active) { + selectable = true; + this->event = event; + this->action = action1; + this->action2 = action2; + iconActive = 0; + this->active = active; + height = font->Height() + 2*fontSmall->Height() + 10; + pixmapText = NULL; + pixmapIcons = NULL; +} + +cRecMenuItemEvent::~cRecMenuItemEvent(void) { + if (pixmapIcons) + osdManager.releasePixmap(pixmapIcons); + if (pixmapText) + osdManager.releasePixmap(pixmapText); +} + +void cRecMenuItemEvent::SetPixmaps(void) { + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapText = osdManager.requestPixmap(5, cRect(x, y, width, height)); + pixmapText->Fill(clrTransparent); + pixmapIcons = osdManager.requestPixmap(6, cRect(x, y, width, height)); + pixmapIcons->Fill(clrTransparent); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapText->SetViewPort(cRect(x, y, width, height)); + pixmapIcons->SetViewPort(cRect(x, y, width, height)); + } +} + +void cRecMenuItemEvent::Draw(void) { + if (!event) + return; + int logoX = DrawIcons(); + const cChannel *channel = Channels.GetByChannelID(event->ChannelID()); + cString channelName(""); + if (channel) { + channelName = channel->Name(); + } + int logoWidth = height * tvguideConfig.logoWidthRatio / tvguideConfig.logoHeightRatio; + cImageLoader imgLoader; + if (!tvguideConfig.hideChannelLogos) { + if (imgLoader.LoadLogo(*channelName, logoWidth, height)) { + cImage logo = imgLoader.GetImage(); + pixmapText->DrawImage(cPoint(logoX, 0), logo); + logoX += logoWidth + 5; + } + } + + int textX = logoX; + int textHeightLine1 = 5; + int textHeightLine2 = 5 + fontSmall->Height(); + int textHeightLine3 = height - fontSmall->Height() - 5; + + cString title = event->Title(); + cString desc = event->ShortText(); + cString start = DayDateTime(event->StartTime()); + cString end = event->GetEndTimeString(); + + colorText = active?theme.Color(clrFontActive):theme.Color(clrFont); + cString info = cString::sprintf("%s - %s, %s", *start, *end, *channelName); + pixmapText->DrawText(cPoint(textX, textHeightLine1), *info, colorText, clrTransparent, fontSmall); + pixmapText->DrawText(cPoint(textX, textHeightLine2), *title, colorText, clrTransparent, font); + pixmapText->DrawText(cPoint(textX, textHeightLine3), *desc, colorText, clrTransparent, fontSmall); +} + +int cRecMenuItemEvent::DrawIcons(void) { + pixmapIcons->Fill(clrTransparent); + int iconsX = 10; + int iconSize = 64; + int iconY = (height - iconSize) / 2; + cString iconInfo, iconRecord; + if (active) { + iconInfo = (iconActive==0)?"info_active":"info_inactive"; + if (action2 != rmsDisabled) + iconRecord = (iconActive==1)?"record_active":"record_inactive"; + } else { + iconInfo = "info_inactive"; + if (action2 != rmsDisabled) + iconRecord = "record_inactive"; + } + cImageLoader imgLoader; + if (imgLoader.LoadIcon(iconInfo, iconSize)) { + cImage icon = imgLoader.GetImage(); + pixmapIcons->DrawImage(cPoint(iconsX, iconY), icon); + iconsX += iconSize + 5; + } + if ((action2 != rmsDisabled) && imgLoader.LoadIcon(iconRecord, iconSize)) { + cImage icon = imgLoader.GetImage(); + pixmapIcons->DrawImage(cPoint(iconsX, iconY), icon); + iconsX += iconSize + 5; + } + return iconsX; +} + +void cRecMenuItemEvent::Hide(void) { + pixmap->SetLayer(-1); + pixmapText->SetLayer(-1); + pixmapIcons->SetLayer(-1); +} + +void cRecMenuItemEvent::Show(void) { + pixmap->SetLayer(4); + pixmapText->SetLayer(5); + pixmapIcons->SetLayer(6); +} + +eRecMenuState cRecMenuItemEvent::ProcessKey(eKeys Key) { + bool consumed = false; + switch (Key & ~k_Repeat) { + case kLeft: + if (action2 == rmsDisabled) + return rmsNotConsumed; + if (iconActive == 1) { + iconActive = 0; + consumed = true; + } + DrawIcons(); + if (consumed) + return rmsConsumed; + else + return rmsNotConsumed; + break; + case kRight: { + if (action2 == rmsDisabled) + return rmsNotConsumed; + if (iconActive == 0) { + iconActive = 1; + consumed = true; + } + DrawIcons(); + if (consumed) + return rmsConsumed; + else + return rmsNotConsumed; + break; } + case kOk: + if (iconActive == 0) + return action; + else if (iconActive == 1) + return action2; + break; + default: + break; + } + return rmsNotConsumed; +} + +// --- cRecMenuItemChannelChooser ------------------------------------------------------- +cRecMenuItemChannelChooser::cRecMenuItemChannelChooser(cString text, + cChannel *initialChannel, + bool active) { + selectable = true; + this->text = text; + this->channel = initialChannel; + if (initialChannel) + initialChannelSet = true; + else + initialChannelSet = false; + channelNumber = 0; + fresh = true; + this->active = active; + height = 2 * font->Height(); + pixmapChannel = NULL; +} + +cRecMenuItemChannelChooser::~cRecMenuItemChannelChooser(void) { + if (pixmapChannel) + osdManager.releasePixmap(pixmapChannel); +} + +void cRecMenuItemChannelChooser::SetPixmaps(void) { + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapChannel = osdManager.requestPixmap(5, cRect(x, y, width, height)); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapChannel->SetViewPort(cRect(x, y, width, height)); + } +} + +void cRecMenuItemChannelChooser::Hide(void) { + pixmap->SetLayer(-1); + pixmapChannel->SetLayer(-1); +} + +void cRecMenuItemChannelChooser::Show(void) { + pixmap->SetLayer(4); + pixmapChannel->SetLayer(5); +} + +void cRecMenuItemChannelChooser::Draw(void) { + int textY = (height - font->Height()) / 2; + pixmap->DrawText(cPoint(10, textY), *text, colorText, colorTextBack, font); + DrawValue(); +} + +void cRecMenuItemChannelChooser::DrawValue(void) { + pixmapChannel->Fill(clrTransparent); + int textY = (height - font->Height()) / 2; + if (channel) { + cString textVal = cString::sprintf("%d - %s", channel->Number(), channel->Name()); + int textX = width - font->Width(*textVal) - 10; + pixmapChannel->DrawText(cPoint(textX, textY), *textVal, colorText, clrTransparent, font); + int logoWidth = height * tvguideConfig.logoWidthRatio / tvguideConfig.logoHeightRatio; + int logoX = textX - logoWidth - 10; + cImageLoader imgLoader; + if (imgLoader.LoadLogo(channel->Name(), logoWidth, height)) { + cImage logo = imgLoader.GetImage(); + pixmapChannel->DrawImage(cPoint(logoX, 0), logo); + } + } else { + cString textVal = tr("all Channels"); + int textX = width - font->Width(*textVal) - 10; + pixmapChannel->DrawText(cPoint(textX, textY), *textVal, colorText, clrTransparent, font); + } +} + +int cRecMenuItemChannelChooser::GetIntValue(void) { + if (channel) + return channel->Number(); + return 0; +} + + +eRecMenuState cRecMenuItemChannelChooser::ProcessKey(eKeys Key) { + switch (Key & ~k_Repeat) { + case kLeft: { + fresh = true; + if (!channel) + return rmsConsumed; + cChannel *prev = channel; + cChannel *firstChannel = Channels.First(); + if(firstChannel->GroupSep()) + firstChannel = Channels.Next(firstChannel); + if (prev == firstChannel) { + if (!initialChannelSet) + channel = NULL; + } else { + while (prev = Channels.Prev(prev)) { + if(!prev->GroupSep()) { + channel = prev; + break; + } + } + } + DrawValue(); + return rmsConsumed; + break; } + case kRight: { + fresh = true; + if (!channel) { + channel = Channels.First(); + if(channel->GroupSep()) + channel = Channels.Next(channel); + } else { + cChannel *next = channel; + while (next = Channels.Next(next)) { + if(!next->GroupSep()) { + channel = next; + break; + } + } + } + DrawValue(); + return rmsConsumed; + break; } + case k0 ... k9: { + if (fresh) { + channelNumber = 0; + fresh = false; + } + channelNumber = channelNumber * 10 + (Key - k0); + cChannel *chanNew = Channels.GetByNumber(channelNumber); + if (chanNew) { + channel = chanNew; + DrawValue(); + } + return rmsConsumed; + break; } + default: + break; + } + return rmsNotConsumed; +} + +// --- cRecMenuItemDayChooser ------------------------------------------------------- +cRecMenuItemDayChooser::cRecMenuItemDayChooser(cString text, + int weekdays, + bool active) { + selectable = true; + this->text = text; + this->weekdays = weekdays; + this->active = active; + height = 2 * font->Height(); + selectedDay = 0; + pixmapWeekdays = NULL; + pixmapWeekdaysSelect = NULL; +} + +cRecMenuItemDayChooser::~cRecMenuItemDayChooser(void) { + if (pixmapWeekdays) + osdManager.releasePixmap(pixmapWeekdays); + if (pixmapWeekdaysSelect) + osdManager.releasePixmap(pixmapWeekdaysSelect); +} + +void cRecMenuItemDayChooser::SetPixmaps(void) { + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapWeekdays = osdManager.requestPixmap(5, cRect(x, y, width, height)); + pixmapWeekdaysSelect = osdManager.requestPixmap(6, cRect(x, y, width, height)); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapWeekdays->SetViewPort(cRect(x, y, width, height)); + pixmapWeekdaysSelect->SetViewPort(cRect(x, y, width, height)); + } + SetSizes(); +} + +void cRecMenuItemDayChooser::Hide(void) { + pixmap->SetLayer(-1); + pixmapWeekdays->SetLayer(-1); + pixmapWeekdaysSelect->SetLayer(-1); +} + +void cRecMenuItemDayChooser::Show(void) { + pixmap->SetLayer(4); + pixmapWeekdays->SetLayer(5); + pixmapWeekdaysSelect->SetLayer(6); +} + +void cRecMenuItemDayChooser::SetSizes(void) { + days = trVDR("MTWTFSS"); + int maxWidth = 0; + for (unsigned i=0; iWidth(days.at(i)); + if (charWidth > maxWidth) + maxWidth = charWidth; + } + daysSize = min(maxWidth + 15, height-4); + daysX = width - 10 - 7*daysSize; + daysY = (height - daysSize) / 2; +} + +void cRecMenuItemDayChooser::setBackground() { + cRecMenuItem::setBackground(); + if (active) { + DrawHighlight(selectedDay); + } else { + DrawHighlight(-1); + } +} + +void cRecMenuItemDayChooser::Draw(void) { + int textY = (height - font->Height()) / 2; + pixmap->DrawText(cPoint(10, textY), *text, colorText, colorTextBack, font); + DrawDays(); +} + +void cRecMenuItemDayChooser::DrawDays(void) { + pixmapWeekdays->Fill(clrTransparent); + int textY = (height - font->Height()) / 2; + pixmapWeekdays->DrawRectangle(cRect(daysX, daysY, 7*daysSize, daysSize), theme.Color(clrBorder)); + int currentX = daysX; + for (unsigned day=0; dayDrawRectangle(cRect(currentX+2, daysY+2, daysSize-4, daysSize-4), theme.Color(clrBackground)); + tColor colorDay = WeekDaySet(day)?theme.Color(clrRecMenuDayActive) + :theme.Color(clrRecMenuDayInactive); + int textX = currentX + (daysSize - font->Width(*strDay)) / 2; + pixmapWeekdays->DrawText(cPoint(textX, textY), *strDay, colorDay, clrTransparent, font); + currentX += daysSize; + } +} + +void cRecMenuItemDayChooser::DrawHighlight(int day) { + pixmapWeekdaysSelect->Fill(clrTransparent); + if (day > -1) { + int currentX = daysX + day*daysSize; + pixmapWeekdaysSelect->DrawRectangle(cRect(currentX, daysY, daysSize, daysSize), theme.Color(clrRecMenuDayHighlight)); + } +} + +bool cRecMenuItemDayChooser::WeekDaySet(unsigned day) { + return weekdays & (1 << day); +} + +void cRecMenuItemDayChooser::ToggleDay(void) { + bool dayActive = WeekDaySet(selectedDay); + int dayBit = pow(2, selectedDay); + if (dayActive) { + weekdays -= dayBit; + } else { + weekdays += dayBit; + } +} + +eRecMenuState cRecMenuItemDayChooser::ProcessKey(eKeys Key) { + switch (Key & ~k_Repeat) { + case kLeft: { + selectedDay--; + if (selectedDay<0) + selectedDay += 7; + DrawHighlight(selectedDay); + return rmsConsumed; + break; } + case kRight: { + selectedDay = (selectedDay+1)%7; + DrawHighlight(selectedDay); + return rmsConsumed; + break; } + case kOk: + ToggleDay(); + DrawDays(); + return rmsConsumed; + break; + default: + break; + } + return rmsNotConsumed; +} + +// --- cRecMenuItemRecording ------------------------------------------------------- +cRecMenuItemRecording::cRecMenuItemRecording(cRecording *recording, bool active) { + selectable = true; + this->recording = recording; + this->active = active; + height = font->Height() + 2*fontSmall->Height() + 10; + pixmapText = NULL; +} + +cRecMenuItemRecording::~cRecMenuItemRecording(void) { + if (pixmapText) + osdManager.releasePixmap(pixmapText); +} + +void cRecMenuItemRecording::SetPixmaps(void) { + if (!pixmap) { + pixmap = osdManager.requestPixmap(4, cRect(x, y, width, height)); + pixmapText = osdManager.requestPixmap(5, cRect(x, y, width, height)); + pixmapText->Fill(clrTransparent); + } else { + pixmap->SetViewPort(cRect(x, y, width, height)); + pixmapText->SetViewPort(cRect(x, y, width, height)); + } +} + +void cRecMenuItemRecording::Draw(void) { + if (!recording) + return; + const cRecordingInfo *recInfo = recording->Info(); + cChannel *channel = Channels.GetByChannelID(recInfo->ChannelID()); + cString channelName = tr("unknown channel"); + if (channel) + channelName = channel->Name(); + cString name = recording->Name(); + cString dateTime = cString::sprintf("%s, %s", *DateString(recording->Start()), *TimeString(recording->Start())); + + int recDuration = recording->LengthInSeconds() / 60; + + cString recDetails = cString::sprintf("%s: %d %s, %s %s %s \"%s\"", tr("Duration"), recDuration, tr("min"), tr("recorded at"), *dateTime, tr("from"), *channelName); + recDetails = CutText(*recDetails, width - 40, fontSmall).c_str(); + int text1Y = (height/2 - font->Height()) / 2 + 5; + int text2Y = height/2 + (height/2 - fontSmall->Height())/2 - 5; + colorText = active?theme.Color(clrFontActive):theme.Color(clrFont); + pixmapText->DrawText(cPoint(10, text1Y), *name, colorText, clrTransparent, font); + pixmapText->DrawText(cPoint(10, text2Y), *recDetails, colorText, clrTransparent, fontSmall); +} + +void cRecMenuItemRecording::Hide(void) { + pixmap->SetLayer(-1); + pixmapText->SetLayer(-1); +} + +void cRecMenuItemRecording::Show(void) { + pixmap->SetLayer(4); + pixmapText->SetLayer(5); +} diff --git a/recmenuitem.h b/recmenuitem.h new file mode 100644 index 0000000..780eac9 --- /dev/null +++ b/recmenuitem.h @@ -0,0 +1,465 @@ +#ifndef __TVGUIDE_RECMENUITEM_H +#define __TVGUIDE_RECMENUITEM_H + +#define AUTO_ADVANCE_TIMEOUT 1500 + +enum eRecMenuState { + rmsConsumed, + rmsNotConsumed, + rmsRefresh, + rmsContinue, + rmsClose, + rmsInstantRecord, + rmsIgnoreTimerConflict, + rmsDeleteTimerConflictMenu, + rmsEditTimerConflictMenu, + rmsSaveTimerConflictMenu, + rmsTimerConflictShowInfo, + rmsDeleteTimer, + rmsDeleteTimerConfirmation, + rmsEditTimer, + rmsSaveTimer, + rmsSearch, + rmsSearchWithOptions, + rmsSearchPerform, + rmsSearchShowInfo, + rmsSearchRecord, + rmsSearchRecordConfirm, + rmsSearchNothingFoundConfirm, + rmsSeriesTimer, + rmsSeriesTimerCreate, + rmsSearchTimer, + rmsSearchTimerOptions, + rmsSearchTimerOptionsReload, + rmsSearchTimerUseTemplate, + rmsSearchTimerOptionsManually, + rmsSearchTimerTestManually, + rmsSearchTimerTestTemplate, + rmsSearchTimerNothingFoundConfirm, + rmsSearchTimerCreateManually, + rmsSearchTimerCreateTemplate, + rmsSwitchTimer, + rmsSwitchTimerCreate, + rmsSwitchTimerDelete, + rmsRecordingSearch, + rmsRecordingSearchResult, + rmsTimerConflict, + rmsTimerConflicts, + rmsDisabled, +}; + +enum eDependend { + eGreater, + eLower, +}; +// --- cRecMenuItem ------------------------------------------------------------- +class cRecMenuItem : public cListObject, public cStyledPixmap { +protected: + int x, y; + int width, height; + bool selectable; + bool active; + bool drawn; + eRecMenuState action; + tColor colorText; + tColor colorTextBack; + const cFont *font; + const cFont *fontSmall; +public: + cRecMenuItem(void); + virtual ~cRecMenuItem(void); + void SetGeometry(int x, int y, int width); + virtual void SetPixmaps(void); + virtual int GetHeight(void) { return height; }; + virtual int GetWidth(void) { return 0; }; + virtual void CalculateHeight(int textWidth) {}; + void setActive(void) { this->active = true; } + void setInactive(void) { this->active = false; } + bool isSelectable(void) { return selectable; } + bool isActive(void) { return active; } + virtual void setBackground(void); + virtual void Draw(void) {}; + virtual void Hide(void) { pixmap->SetLayer(-1);}; + virtual void Show(void) { pixmap->SetLayer(4);}; + virtual int GetIntValue(void) { return -1; }; + virtual time_t GetTimeValue(void) { return 0; }; + virtual bool GetBoolValue(void) { return false; }; + virtual cString GetStringValue(void) { return cString(""); }; + virtual const cEvent *GetEventValue(void) { return NULL; }; + virtual eRecMenuState ProcessKey(eKeys Key) { return rmsNotConsumed; }; + +}; + +// --- cRecMenuItemButton ------------------------------------------------------- +class cRecMenuItemButton : public cRecMenuItem { +private: + cString text; + bool halfWidth; +public: + cRecMenuItemButton(const char *text, eRecMenuState action, bool active, bool halfWidth = false); + virtual ~cRecMenuItemButton(void); + int GetWidth(void); + void SetPixmaps(void); + void Draw(void); + eRecMenuState ProcessKey(eKeys Key); +}; + +// --- cRecMenuItemButtonYesNo ------------------------------------------------------- +class cRecMenuItemButtonYesNo : public cRecMenuItem { +private: + cString textYes; + cString textNo; + eRecMenuState actionNo; + bool yesActive; + cStyledPixmap *pixmapNo; + tColor colorTextNo; +public: + cRecMenuItemButtonYesNo(cString textYes, + cString textNo, + eRecMenuState actionYes, + eRecMenuState actionNo, + bool active); + virtual ~cRecMenuItemButtonYesNo(void); + void SetPixmaps(void); + void setBackground(void); + void Hide(void); + void Show(void); + eRecMenuState ProcessKey(eKeys Key); + void Draw(void); +}; + +// --- cRecMenuItemInfo ------------------------------------------------------- +class cRecMenuItemInfo : public cRecMenuItem { +private: + cString text; + cTextWrapper wrapper; + int border; +public: + cRecMenuItemInfo(const char *text); + virtual ~cRecMenuItemInfo(void); + void setBackground(void); + void CalculateHeight(int textWidth); + void Draw(void); +}; + +// --- cRecMenuItemInt ------------------------------------------------------- +class cRecMenuItemInt : public cRecMenuItem { +private: + cString text; + int currentVal; + int minVal; + int maxVal; + cPixmap *pixmapVal; + bool fresh; + void DrawValue(void); +public: + cRecMenuItemInt(cString text, + int initialVal, + int minVal, + int maxVal, + bool active); + virtual ~cRecMenuItemInt(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + void setBackground(void); + eRecMenuState ProcessKey(eKeys Key); + void Draw(void); + int GetIntValue(void) { return currentVal; }; +}; + +// --- cRecMenuItemBool ------------------------------------------------------- +class cRecMenuItemBool : public cRecMenuItem { +private: + cString text; + bool yes; + cPixmap *pixmapVal; + bool refresh; + void DrawValue(void); +public: + cRecMenuItemBool(cString text, + bool initialVal, + bool refresh, + bool active); + virtual ~cRecMenuItemBool(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + eRecMenuState ProcessKey(eKeys Key); + void Draw(void); + bool GetBoolValue(void) { return yes; }; +}; + +// --- cRecMenuItemSelect ------------------------------------------------------- +class cRecMenuItemSelect : public cRecMenuItem { +private: + cString text; + int currentVal; + const char * const *strings; + int numValues; + cPixmap *pixmapVal; + void DrawValue(void); +public: + cRecMenuItemSelect(cString text, + const char * const *Strings, + int initialVal, + int numValues, + bool active); + virtual ~cRecMenuItemSelect(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + eRecMenuState ProcessKey(eKeys Key); + void Draw(void); + int GetIntValue(void) { return currentVal; }; + cString GetStringValue(void) { return strings[currentVal]; }; +}; + +// --- cRecMenuItemText ------------------------------------------------------- +class cRecMenuItemText : public cRecMenuItem { +private: + cString title; + char *value; + int length; + const char *allowed; + int pos, offset; + bool insert, newchar, uppercase; + int lengthUtf8; + uint *valueUtf8; + uint *allowedUtf8; + uint *charMapUtf8; + uint *currentCharUtf8; + eKeys lastKey; + cTimeMs autoAdvanceTimeout; + cPixmap *pixmapVal; + cStyledPixmap *pixmapKeyboard; + cPixmap *pixmapKeyboardHighlight; + cPixmap *pixmapKeyboardIcons; + int keyboardWidth; + int gridWidth; + int gridHeight; + int keyboardHeight; + bool keyboardDrawn; + uint *IsAllowed(uint c); + void AdvancePos(void); + uint Inc(uint c, bool Up); + void Type(uint c); + void Insert(void); + void Delete(void); + void EnterEditMode(void); + void LeaveEditMode(bool SaveValue = false); + bool InEditMode(void) { return valueUtf8 != NULL; }; + void SetText(void); + void ActivateKeyboard(void); + void DeactivateKeyboard(void); + void HighlightSMSKey(int num); + void ClearSMSKey(void); + char *GetSMSKeys(int num); + void DrawValue(char *newValue); +public: + cRecMenuItemText(cString title, + char *initialVal, + int length, + bool active); + virtual ~cRecMenuItemText(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + void setBackground(void); + eRecMenuState ProcessKey(eKeys Key); + void Draw(void); + cString GetStringValue(void) { return value; }; +}; + + +// --- cRecMenuItemTime ------------------------------------------------------- +class cRecMenuItemTime : public cRecMenuItem { +private: + cString text; + int value; + int mm; + int hh; + int pos; + bool fresh; + cPixmap *pixmapVal; + void DrawValue(void); +public: + cRecMenuItemTime(cString text, + int initialVal, + bool active); + virtual ~cRecMenuItemTime(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + eRecMenuState ProcessKey(eKeys Key); + void Draw(void); + int GetIntValue(void) { return value; }; +}; + +// --- cRecMenuItemDay ------------------------------------------------------- +class cRecMenuItemDay : public cRecMenuItem { +private: + cString text; + time_t currentVal; + cPixmap *pixmapVal; + void DrawValue(void); +public: + cRecMenuItemDay(cString text, + time_t initialVal, + bool active); + virtual ~cRecMenuItemDay(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + eRecMenuState ProcessKey(eKeys Key); + void Draw(void); + time_t GetTimeValue(void) { return currentVal; }; +}; + +// --- cRecMenuItemTimer ------------------------------------------------------- +class cRecMenuItemTimer : public cRecMenuItem { +private: + const cTimer *timer; + eRecMenuState action2; + eRecMenuState action3; + int iconActive; + cPixmap *pixmapIcons; + cPixmap *pixmapStatus; + time_t conflictStart; + time_t conflictStop; + time_t overlapStart; + time_t overlapStop; + int DrawIcons(void); + void DrawTimerConflict(void); +public: + cRecMenuItemTimer(const cTimer *timer, + eRecMenuState action1, + eRecMenuState action2, + eRecMenuState action3, + time_t conflictStart, + time_t conflictStop, + time_t overlapStart, + time_t overlapStop, + bool active); + virtual ~cRecMenuItemTimer(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + eRecMenuState ProcessKey(eKeys Key); + void Draw(void); +}; + +// --- cRecMenuItemTimerConflictHeader ------------------------------------------------------- + +class cRecMenuItemTimerConflictHeader: public cRecMenuItem { +private: + cPixmap *pixmapStatus; + time_t conflictStart; + time_t conflictStop; + time_t overlapStart; + time_t overlapStop; +public: + cRecMenuItemTimerConflictHeader(time_t conflictStart, + time_t conflictStop, + time_t overlapStart, + time_t overlapStop); + virtual ~cRecMenuItemTimerConflictHeader(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + void setBackground(void); + void Draw(void); +}; + +// --- cRecMenuItemEvent ------------------------------------------------------- +class cRecMenuItemEvent : public cRecMenuItem { +private: + const cEvent *event; + eRecMenuState action2; + int iconActive; + cPixmap *pixmapText; + cPixmap *pixmapIcons; + int DrawIcons(void); +public: + cRecMenuItemEvent(const cEvent *event, + eRecMenuState action1, + eRecMenuState action2, + bool active); + virtual ~cRecMenuItemEvent(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + const cEvent *GetEventValue(void) { return event; }; + eRecMenuState ProcessKey(eKeys Key); + void Draw(void); +}; + +// --- cRecMenuItemChannelChooser ------------------------------------------------------- +class cRecMenuItemChannelChooser : public cRecMenuItem { +private: + cString text; + cChannel *channel; + int channelNumber; + bool initialChannelSet; + bool fresh; + cPixmap *pixmapChannel; + void DrawValue(void); +public: + cRecMenuItemChannelChooser (cString text, + cChannel *initialChannel, + bool active); + virtual ~cRecMenuItemChannelChooser(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + eRecMenuState ProcessKey(eKeys Key); + void Draw(void); + int GetIntValue(void); +}; + +// --- cRecMenuItemDayChooser ------------------------------------------------------- +class cRecMenuItemDayChooser : public cRecMenuItem { +private: + cString text; + int weekdays; + std::string days; + int daysX; + int daysY; + int daysSize; + int selectedDay; + cPixmap *pixmapWeekdays; + cPixmap *pixmapWeekdaysSelect; + void SetSizes(void); + void DrawDays(void); + void DrawHighlight(int day); + void ToggleDay(void); + bool WeekDaySet(unsigned day); +public: + cRecMenuItemDayChooser (cString text, + int weekdays, + bool active); + virtual ~cRecMenuItemDayChooser(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + void setBackground(void); + eRecMenuState ProcessKey(eKeys Key); + void Draw(void); + int GetIntValue(void) { return weekdays;} ; +}; + +// --- cRecMenuItemRecording ------------------------------------------------------- +class cRecMenuItemRecording : public cRecMenuItem { +private: + cRecording *recording; + cPixmap *pixmapText; +public: + cRecMenuItemRecording(cRecording *recording, bool active); + virtual ~cRecMenuItemRecording(void); + void SetPixmaps(void); + void Hide(void); + void Show(void); + void Draw(void); +}; + +#endif //__TVGUIDE_RECMENUITEM_H \ No newline at end of file diff --git a/recmenumanager.c b/recmenumanager.c new file mode 100644 index 0000000..cd34c12 --- /dev/null +++ b/recmenumanager.c @@ -0,0 +1,521 @@ +#include "recmenumanager.h" + +cRecMenuManager::cRecMenuManager(void) { + active = false; + activeMenu = NULL; + activeMenuBuffer = NULL; + recManager = new cRecManager(); + recManager->SetEPGSearchPlugin(); + instantRecord = false; + currentConflict = -1; + templateID = -1; + timer = NULL; + searchWithOptions = false; + detailViewActive = false; +} + +cRecMenuManager::~cRecMenuManager(void) { + if (activeMenu) { + active = false; + delete activeMenu; + activeMenu = NULL; + } + delete recManager; +} + +void cRecMenuManager::Start(const cEvent *event) { + active = true; + activeMenuBuffer = NULL; + instantRecord = false; + currentConflict = -1; + templateID = -1; + timer = NULL; + searchWithOptions = false; + detailViewActive = false; + SetBackground(); + this->event = event; + activeMenu = new cRecMenuMain(recManager->EpgSearchAvailable(), event->HasTimer(), SwitchTimers.EventInSwitchList(event)); + activeMenu->Display(); + osdManager.flush(); +} + +void cRecMenuManager::Close(void) { + event = NULL; + active = false; + if (activeMenu) { + delete activeMenu; + activeMenu = NULL; + } + DeleteBackground(); +} + +void cRecMenuManager::SetBackground(void) { + int backgroundWidth = tvguideConfig.osdWidth; + int backgroundHeight = tvguideConfig.osdHeight; + pixmapBackground = osdManager.requestPixmap(3, cRect(0, 0, backgroundWidth, backgroundHeight)); + pixmapBackground->Fill(theme.Color(clrRecMenuBackground)); + if (tvguideConfig.scaleVideo) { + int tvHeight = tvguideConfig.statusHeaderHeight; + int tvWidth = tvHeight * 16 / 9; + int tvX = tvguideConfig.osdWidth - tvWidth; + pixmapBackground->DrawRectangle(cRect(tvX, 0, tvWidth, tvHeight), clrTransparent); + } +} + +void cRecMenuManager::DeleteBackground(void) { + osdManager.releasePixmap(pixmapBackground); +} + +eOSState cRecMenuManager::StateMachine(eRecMenuState nextState) { + eOSState state = osContinue; + switch (nextState) { + /* + * --------- INSTANT RECORDING --------------------------- + */ + case rmsInstantRecord: { + //Creating timer for active Event + //if no conflict, confirm and exit + instantRecord = true; + delete activeMenu; + cTimer *timer = recManager->createTimer(event); + if (!displayTimerConflict(timer)) { + activeMenu = new cRecMenuConfirmTimer(event); + activeMenu->Display(); + } + break; } + case rmsIgnoreTimerConflict: + //Confirming created Timer + if (instantRecord) { + delete activeMenu; + activeMenu = new cRecMenuConfirmTimer(event); + activeMenu->Display(); + } else { + state = osEnd; + Close(); + } + break; + case rmsTimerConflictShowInfo: { + int timerIndex = activeMenu->GetActive(true); + int timerID = conflictList[currentConflict].timerIDs[timerIndex]; + cTimer *t = Timers.Get(timerID); + if (t) { + const cEvent *ev = t->Event(); + if (ev) { + activeMenu->Hide(); + detailView = new cDetailView(ev); + detailView->drawHeader(); + detailView->drawContent(); + detailView->drawScrollbar(); + detailViewActive = true; + } + } + break;} + case rmsDeleteTimerConflictMenu: { + //delete timer out of current timer conflict + //active menu: cRecMenuTimerConflict + int timerIndex = activeMenu->GetActive(true); + int timerID = conflictList[currentConflict].timerIDs[timerIndex]; + recManager->DeleteTimer(timerID); + delete activeMenu; + if (!displayTimerConflict(timerID)) { + activeMenu = new cRecMenuConfirmTimer(event); + activeMenu->Display(); + } + break; } + case rmsEditTimerConflictMenu: { + //edit timer out of current timer conflict + //active menu: cRecMenuTimerConflict + int activeItem = activeMenu->GetActive(true); + int timerID = conflictList[currentConflict].timerIDs[activeItem]; + timer = Timers.Get(timerID); + if (timer) { + delete activeMenu; + activeMenu = new cRecMenuEditTimer(timer, rmsSaveTimerConflictMenu); + activeMenu->Display(); + } + break; } + case rmsSaveTimerConflictMenu: { + //save timer from current timer conflict + recManager->SaveTimer(timer, activeMenu); + delete activeMenu; + if (!displayTimerConflict(timer)) { + activeMenu = new cRecMenuConfirmTimer(event); + activeMenu->Display(); + } + break; } + case rmsDeleteTimer: + //delete timer for active event + delete activeMenu; + if (recManager->IsRecorded(event)) { + activeMenu = new cRecMenuAskDeleteTimer(event); + } else { + recManager->DeleteTimer(event); + activeMenu = new cRecMenuConfirmDeleteTimer(event); + } + activeMenu->Display(); + break; + case rmsDeleteTimerConfirmation: + //delete running timer for active event + recManager->DeleteTimer(event); + delete activeMenu; + activeMenu = new cRecMenuConfirmDeleteTimer(event); + activeMenu->Display(); + break; + case rmsEditTimer: { + //edit timer for active event + timer = Timers.GetMatch(event); + if (timer) { + delete activeMenu; + activeMenu = new cRecMenuEditTimer(timer, rmsSaveTimer); + activeMenu->Display(); + } + break; } + case rmsSaveTimer: { + //save timer for active event + recManager->SaveTimer(timer, activeMenu); + state = osEnd; + Close(); + break; } + /* + * --------- SERIES TIMER --------------------------------- + */ + case rmsSeriesTimer: { + delete activeMenu; + cChannel *channel = Channels.GetByChannelID(event->ChannelID()); + activeMenu = new cRecMenuSeriesTimer(channel, event); + activeMenu->Display(); + break; } + case rmsSeriesTimerCreate: { + cTimer *seriesTimer = recManager->CreateSeriesTimer(activeMenu); + delete activeMenu; + activeMenu = new cRecMenuConfirmSeriesTimer(seriesTimer); + activeMenu->Display(); + break; } + /* + * --------- SEARCH TIMER --------------------------------- + */ + case rmsSearchTimer: + delete activeMenu; + activeMenu = new cRecMenuSearchTimer(event); + activeMenu->Display(); + break; + case rmsSearchTimerOptions: { + searchString = *activeMenu->GetStringValue(1); + delete activeMenu; + if (isempty(*searchString)) { + activeMenu = new cRecMenuSearchTimer(event); + } else { + epgSearchTemplates = recManager->ReadEPGSearchTemplates(); + int numTemplates = epgSearchTemplates.size(); + if (numTemplates > 0) { + activeMenu = new cRecMenuSearchTimerTemplates(searchString, epgSearchTemplates); + } else { + activeMenu = new cRecMenuSearchTimerOptions(searchString); + } + } + activeMenu->Display(); + break; } + case rmsSearchTimerOptionsReload: { + int numTemplates = epgSearchTemplates.size(); + delete activeMenu; + activeMenu = new cRecMenuSearchTimerTemplates(searchString, epgSearchTemplates); + activeMenu->Display(); + break; } + case rmsSearchTimerUseTemplate: { + templateID = activeMenu->GetActive(true) - 1; + delete activeMenu; + activeMenu = new cRecMenuSearchTimerTemplatesCreate(searchString, epgSearchTemplates[templateID].name.c_str()); + activeMenu->Display(); + break; } + case rmsSearchTimerOptionsManually: + delete activeMenu; + activeMenu = new cRecMenuSearchTimerOptions(searchString); + activeMenu->Display(); + break; + case rmsSearchTimerTestTemplate: { + std::string epgSearchString = recManager->BuildEPGSearchString(searchString, epgSearchTemplates[templateID].templValue); + int numSearchResults = 0; + const cEvent **searchResult = recManager->PerformSearchTimerSearch(epgSearchString, numSearchResults); + if (searchResult) { + activeMenuBuffer = activeMenu; + activeMenuBuffer->Hide(); + activeMenu = new cRecMenuSearchTimerResults(searchString, searchResult, numSearchResults, epgSearchTemplates[templateID].name); + activeMenu->Display(); + } else { + activeMenuBuffer = activeMenu; + activeMenuBuffer->Hide(); + activeMenu = new cRecMenuSearchTimerNothingFound(searchString, epgSearchTemplates[templateID].name); + activeMenu->Display(); + } + break; } + case rmsSearchTimerTestManually: { + std::string epgSearchString = recManager->BuildEPGSearchString(searchString, activeMenu); + int numSearchResults = 0; + const cEvent **searchResult = recManager->PerformSearchTimerSearch(epgSearchString, numSearchResults); + if (searchResult) { + activeMenuBuffer = activeMenu; + activeMenuBuffer->Hide(); + activeMenu = new cRecMenuSearchTimerResults(searchString, searchResult, numSearchResults, ""); + activeMenu->Display(); + } else { + activeMenuBuffer = activeMenu; + activeMenuBuffer->Hide(); + activeMenu = new cRecMenuSearchTimerNothingFound(searchString, ""); + activeMenu->Display(); + } + break; } + case rmsSearchTimerNothingFoundConfirm: + delete activeMenu; + activeMenu = activeMenuBuffer; + activeMenuBuffer = NULL; + activeMenu->Show(); + break; + case rmsSearchTimerCreateManually: + case rmsSearchTimerCreateTemplate: { + std::string epgSearchString; + if (nextState == rmsSearchTimerCreateManually) { + epgSearchString = recManager->BuildEPGSearchString(searchString, activeMenu); + } else if (nextState = rmsSearchTimerCreateTemplate) { + epgSearchString = recManager->BuildEPGSearchString(searchString, epgSearchTemplates[templateID].templValue); + } + bool success = createSearchTimer(epgSearchString); + delete activeMenu; + activeMenu = new cRecMenuSearchTimerCreateConfirm(success); + activeMenu->Display(); + break; } + /* + * --------- SWITCH TIMER --------------------------------- + */ + case rmsSwitchTimer: + delete activeMenu; + activeMenu = new cRecMenuSwitchTimer(); + activeMenu->Display(); + break; + case rmsSwitchTimerCreate: { + bool success = recManager->CreateSwitchTimer(event, activeMenu); + delete activeMenu; + activeMenu = new cRecMenuSwitchTimerConfirm(success); + activeMenu->Display(); + break; } + case rmsSwitchTimerDelete: + recManager->DeleteSwitchTimer(event); + delete activeMenu; + activeMenu = new cRecMenuSwitchTimerDelete(); + activeMenu->Display(); + break; + /* + * --------- RECORDINGS SEARCH --------------------------------- + */ + case rmsRecordingSearch: + delete activeMenu; + activeMenu = new cRecMenuRecordingSearch(event); + activeMenu->Display(); + break; + case rmsRecordingSearchResult: { + searchString = activeMenu->GetStringValue(1); + delete activeMenu; + if (isempty(*searchString)) { + activeMenu = new cRecMenuRecordingSearch(event); + } else { + int numSearchResults = 0; + cRecording **searchResult = recManager->SearchForRecordings(searchString, numSearchResults); + if (numSearchResults == 0) { + activeMenu = new cRecMenuRecordingSearchNotFound(searchString); + } else { + activeMenu = new cRecMenuRecordingSearchResults(searchString, searchResult, numSearchResults); + } + } + activeMenu->Display(); + break; } + /* + * --------- SEARCH --------------------------------- + */ + case rmsSearch: + delete activeMenu; + activeMenu = new cRecMenuSearch(event); + activeMenu->Display(); + searchWithOptions = false; + break; + case rmsSearchWithOptions: { + cString searchString = activeMenu->GetStringValue(1); + delete activeMenu; + if (isempty(*searchString)) { + activeMenu = new cRecMenuSearch(event); + } else { + activeMenu = new cRecMenuSearch(event, *searchString); + searchWithOptions = true; + } + activeMenu->Display(); + break; } + case rmsSearchPerform: { + cString searchString = activeMenu->GetStringValue(1); + if (isempty(*searchString)) { + delete activeMenu; + activeMenu = new cRecMenuSearch(event); + } else { + int numSearchResults = 0; + const cEvent **searchResult = recManager->PerformSearch(activeMenu, searchWithOptions, numSearchResults); + if (searchResult) { + delete activeMenu; + activeMenu = new cRecMenuSearchResults(searchString, searchResult, numSearchResults); + } else { + activeMenuBuffer = activeMenu; + activeMenuBuffer->Hide(); + activeMenu = new cRecMenuSearchNothingFound(searchString); + } + } + activeMenu->Display(); + break; } + case rmsSearchNothingFoundConfirm: + delete activeMenu; + activeMenu = activeMenuBuffer; + activeMenuBuffer = NULL; + activeMenu->Show(); + break; + case rmsSearchShowInfo: { + const cEvent *ev = activeMenu->GetEventValue(activeMenu->GetActive(false)); + if (ev) { + activeMenu->Hide(); + detailView = new cDetailView(ev); + detailView->drawHeader(); + detailView->drawContent(); + detailView->drawScrollbar(); + detailViewActive = true; + } + break;} + case rmsSearchRecord: { + const cEvent *ev = activeMenu->GetEventValue(activeMenu->GetActive(false)); + cTimer *timer = recManager->createTimer(ev); + activeMenuBuffer = activeMenu; + activeMenuBuffer->Hide(); + activeMenu = new cRecMenuSearchConfirmTimer(ev); + activeMenu->Display(); + break;} + case rmsSearchRecordConfirm: + delete activeMenu; + activeMenu = activeMenuBuffer; + activeMenuBuffer = NULL; + activeMenu->Show(); + break; + /* + * --------- CHECK FOR TIMER CONFLICTS --------------------------------- + */ + case rmsTimerConflicts: { + //Show timer conflict + //active menu: cRecMenuTimerConflicts + conflictList = recManager->CheckTimerConflict(); + delete activeMenu; + int numConflicts = conflictList.size(); + if (numConflicts > 0) { + activeMenu = new cRecMenuTimerConflicts(conflictList); + } else { + activeMenu = new cRecMenuNoTimerConflict(); + } + activeMenu->Display(); + break; } + case rmsTimerConflict: + //Show timer conflict + //active menu: cRecMenuTimerConflicts + currentConflict = activeMenu->GetActive(true); + delete activeMenu; + activeMenu = new cRecMenuTimerConflict(conflictList[currentConflict]); + activeMenu->Display(); + break; + + /* + * --------- COMMON --------------------------------- + */ + case rmsClose: { + if (activeMenuBuffer == NULL) { + state = osEnd; + Close(); + } else { + delete activeMenu; + activeMenu = activeMenuBuffer; + activeMenuBuffer = NULL; + activeMenu->Show(); + state = osContinue; + } + break; } + default: + break; + } + return state; +} + +bool cRecMenuManager::displayTimerConflict(cTimer *timer) { + int timerID = 0; + for (cTimer *t = Timers.First(); t; t = Timers.Next(t)) { + if (t == timer) + return displayTimerConflict(timerID); + timerID++; + } + return false; +} + +bool cRecMenuManager::displayTimerConflict(int timerID) { + conflictList = recManager->CheckTimerConflict(); + int numConflicts = conflictList.size(); + int showTimerConflict = -1; + if (numConflicts > 0) { + for (int i=0; i -1) { + currentConflict = showTimerConflict; + activeMenu = new cRecMenuTimerConflict(conflictList[currentConflict]); + activeMenu->Display(); + return true; + } + return false; +} + +bool cRecMenuManager::createSearchTimer(std::string epgSearchString) { + int newTimerID = recManager->CreateSearchTimer(epgSearchString); + bool success = false; + if (newTimerID > -1) { + recManager->UpdateSearchTimers(); + success = true; + } + return success; +} + +eOSState cRecMenuManager::ProcessKey(eKeys Key) { + eOSState state = osContinue; + eRecMenuState nextState = rmsContinue; + if (!activeMenu) + return state; + if (detailViewActive) { + state = detailView->ProcessKey(Key); + if (state == osEnd) { + delete detailView; + detailView = NULL; + detailViewActive = false; + activeMenu->Show(); + state = osContinue; + } + } else { + nextState = activeMenu->ProcessKey(Key); + if ((nextState == rmsClose) || ((nextState == rmsNotConsumed)&&(Key == kBack))){ + if (activeMenuBuffer == NULL) { + state = osEnd; + Close(); + } else { + delete activeMenu; + activeMenu = activeMenuBuffer; + activeMenuBuffer = NULL; + activeMenu->Show(); + state = osContinue; + osdManager.flush(); + } + return state; + } + state = StateMachine(nextState); + } + osdManager.flush(); + return state; +} \ No newline at end of file diff --git a/recmenumanager.h b/recmenumanager.h new file mode 100644 index 0000000..cc9ac2c --- /dev/null +++ b/recmenumanager.h @@ -0,0 +1,38 @@ +#ifndef __TVGUIDE_RECMENUMANAGER_H +#define __TVGUIDE_RECMENUMANAGER_H + +// --- cRecMenuManager ------------------------------------------------------------- +class cRecMenuManager { +private: + bool active; + cRecMenu *activeMenu; + cRecMenu *activeMenuBuffer; + const cEvent *event; + cRecManager *recManager; + std::vector conflictList; + std::vector epgSearchTemplates; + bool instantRecord; + int currentConflict; + int templateID; + bool searchWithOptions; + cTimer *timer; + cString searchString; + cDetailView *detailView; + cPixmap *pixmapBackground; + bool detailViewActive; + void SetBackground(void); + void DeleteBackground(void); + bool displayTimerConflict(cTimer *timer); + bool displayTimerConflict(int timerID); + bool createSearchTimer(std::string epgSearchString); +public: + cRecMenuManager(void); + virtual ~cRecMenuManager(void); + bool isActive(void) { return active; }; + void Start(const cEvent *event); + void Close(void); + eOSState StateMachine(eRecMenuState nextState); + eOSState ProcessKey(eKeys Key); +}; + +#endif //__TVGUIDE_RECMENUMANAGER_H \ No newline at end of file diff --git a/services/epgsearch.h b/services/epgsearch.h index de29299..2669da4 100644 --- a/services/epgsearch.h +++ b/services/epgsearch.h @@ -1,5 +1,5 @@ -/* -Copyright (C) 2004-2007 Christian Wieninger +/* -*- c++ -*- +Copyright (C) 2004-2013 Christian Wieninger This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -24,10 +24,6 @@ The project's page is at http://winni.vdr-developer.org/epgsearch #ifndef EPGSEARCHSERVICES_INC #define EPGSEARCHSERVICES_INC -// Added by Andreas Mair (mail _AT_ andreas _DOT_ vdr-developer _DOT_ org) -#define EPGSEARCH_SEARCHRESULTS_SERVICE_STRING_ID "Epgsearch-searchresults-v1.0" -#define EPGSEARCH_LASTCONFLICTINFO_SERVICE_STRING_ID "Epgsearch-lastconflictinfo-v1.0" - #include #include #include @@ -59,6 +55,13 @@ struct Epgsearch_exttimeredit_v1_0 cOsdMenu* pTimerMenu; // pointer to the menu of results }; +// Data structure for service "Epgsearch-enablesearchtimers-v1.0" +struct Epgsearch_enablesearchtimers_v1_0 +{ +// in + bool enable; // enable search timer thread? +}; + // Data structure for service "Epgsearch-updatesearchtimers-v1.0" struct Epgsearch_updatesearchtimers_v1_0 { @@ -104,7 +107,7 @@ struct Epgsearch_searchresults_v1_0 bool useDescription; // search in description // out - class cServiceSearchResult : public cListObject + class cServiceSearchResult : public cListObject { public: const cEvent* event; @@ -119,11 +122,11 @@ struct Epgsearch_switchtimer_v1_0 { // in const cEvent* event; - int mode; // mode (0=query existance, 1=add/modify, 2=delete) + int mode; // mode (0=query existence, 1=add/modify, 2=delete) // in/out int switchMinsBefore; int announceOnly; -// out +// out bool success; // result }; @@ -132,7 +135,7 @@ class cServiceHandler { public: virtual std::list SearchTimerList() = 0; - // returns a list of search timer entries in the same format as used in epgsearch.conf + // returns a list of search timer entries in the same format as used in epgsearch.conf virtual int AddSearchTimer(const std::string&) = 0; // adds a new search timer and returns its ID (-1 on error) virtual bool ModSearchTimer(const std::string&) = 0; @@ -140,11 +143,11 @@ class cServiceHandler virtual bool DelSearchTimer(int) = 0; // deletes search timer with given ID and returns success virtual std::list QuerySearchTimer(int) = 0; - // returns the search result of the searchtimer with given ID in the same format as used in SVDRP command 'QRYS' (->MANUAL) + // returns the search result of the searchtimer with given ID in the same format as used in SVDRP command 'QRYS' (->MANUAL) virtual std::list QuerySearch(std::string) = 0; - // returns the search result of the searchtimer with given settings in the same format as used in SVDRP command 'QRYS' (->MANUAL) + // returns the search result of the searchtimer with given settings in the same format as used in SVDRP command 'QRYS' (->MANUAL) virtual std::list ExtEPGInfoList() = 0; - // returns a list of extended EPG categories in the same format as used in epgsearchcats.conf + // returns a list of extended EPG categories in the same format as used in epgsearchcats.conf virtual std::list ChanGrpList() = 0; // returns a list of channel groups maintained by epgsearch virtual std::list BlackList() = 0; @@ -164,4 +167,36 @@ struct Epgsearch_services_v1_0 std::auto_ptr handler; }; +// Data structures for service "Epgsearch-services-v1.1" +class cServiceHandler_v1_1 : public cServiceHandler +{ + public: + // Get timer conflicts + virtual std::list TimerConflictList(bool relOnly=false) = 0; + // Check if a conflict check is advised + virtual bool IsConflictCheckAdvised() = 0; +}; + +struct Epgsearch_services_v1_1 +{ +// in/out + std::auto_ptr handler; +}; + +// Data structures for service "Epgsearch-services-v1.2" +class cServiceHandler_v1_2 : public cServiceHandler_v1_1 +{ + public: + // List of all recording directories used in recordings, timers (and optionally search timers or in epgsearchdirs.conf) + virtual std::set ShortDirectoryList() = 0; + // Evaluate an expression against an event + virtual std::string Evaluate(const std::string& expr, const cEvent* event) = 0; +}; + +struct Epgsearch_services_v1_2 +{ +// in/out + std::auto_ptr handler; +}; + #endif diff --git a/setup.c b/setup.c index a49106e..7368bb7 100644 --- a/setup.c +++ b/setup.c @@ -102,6 +102,8 @@ void cTvguideSetup::Store(void) { SetupStore("FontGridHorizontalSmallDelta", tvguideConfig.FontGridHorizontalSmallDelta); SetupStore("FontTimeLineDateHorizontalDelta", tvguideConfig.FontTimeLineDateHorizontalDelta); SetupStore("FontTimeLineTimeHorizontalDelta", tvguideConfig.FontTimeLineTimeHorizontalDelta); + SetupStore("FontRecMenuItemDelta", tvguideConfig.FontRecMenuItemDelta); + SetupStore("FontRecMenuItemSmallDelta", tvguideConfig.FontRecMenuItemSmallDelta); SetupStore("displayRerunsDetailEPGView", tvguideConfig.displayRerunsDetailEPGView); SetupStore("numReruns", tvguideConfig.numReruns); SetupStore("useSubtitleRerun", tvguideConfig.useSubtitleRerun); @@ -301,7 +303,9 @@ void cMenuSetupFont::Set(void) { Add(new cMenuEditIntItem(tr("Timeline Date Font Size"), &tmpTvguideConfig->FontTimeLineDateHorizontalDelta, -30, 30)); Add(new cMenuEditIntItem(tr("Timeline Time Font Size"), &tmpTvguideConfig->FontTimeLineTimeHorizontalDelta, -30, 30)); } - + + Add(new cMenuEditIntItem(tr("Search & Recording Menu Font Size"), &tmpTvguideConfig->FontRecMenuItemDelta, -30, 30)); + Add(new cMenuEditIntItem(tr("Search & Recording Menu Small Font Size"), &tmpTvguideConfig->FontRecMenuItemSmallDelta, -30, 30)); SetCurrent(Get(currentItem)); Display(); diff --git a/styledpixmap.c b/styledpixmap.c index 77e6450..d57ee54 100644 --- a/styledpixmap.c +++ b/styledpixmap.c @@ -132,3 +132,11 @@ void cStyledPixmap::DrawImage(const cPoint &Point, const cImage &Image) { void cStyledPixmap::DrawRectangle(const cRect &Rect, tColor Color) { pixmap->DrawRectangle(Rect,Color); } + +void cStyledPixmap::DrawEllipse(const cRect &Rect, tColor Color, int Quadrant) { + pixmap->DrawEllipse(Rect,Color,Quadrant); +} + +void cStyledPixmap::SetViewPort(const cRect &Rect) { + pixmap->SetViewPort(Rect); +} \ No newline at end of file diff --git a/styledpixmap.h b/styledpixmap.h index ac974be..af09994 100644 --- a/styledpixmap.h +++ b/styledpixmap.h @@ -13,23 +13,26 @@ protected: tColor colorBlending; void setPixmap(cPixmap *pixmap); public: - cStyledPixmap(void); - cStyledPixmap(cPixmap *pixmap); - virtual ~cStyledPixmap(void); - void drawBackground(); - void drawBlendedBackground(); - void drawSparsedBackground(); - void drawBorder(); - void drawBoldBorder(); - void drawDefaultBorder(int width, int height); - void drawRoundedCorners(int width, int height, int radius); - void setColor(tColor color, tColor colorBlending) {this->color = color; this->colorBlending = colorBlending;}; - void SetAlpha(int alpha) {pixmap->SetAlpha(alpha);}; - void DrawText(const cPoint &Point, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font); - void DrawImage(const cPoint &Point, const cImage &Image); - void DrawRectangle(const cRect &Rect, tColor Color); - int Width() {return pixmap->ViewPort().Width();}; - int Height() {return pixmap->ViewPort().Height();}; + cStyledPixmap(void); + cStyledPixmap(cPixmap *pixmap); + virtual ~cStyledPixmap(void); + void drawBackground(); + void drawBlendedBackground(); + void drawSparsedBackground(); + void drawBorder(); + void drawBoldBorder(); + void drawDefaultBorder(int width, int height); + void drawRoundedCorners(int width, int height, int radius); + void setColor(tColor color, tColor colorBlending) {this->color = color; this->colorBlending = colorBlending;}; + void SetAlpha(int alpha) {pixmap->SetAlpha(alpha);}; + void SetLayer(int layer) {pixmap->SetLayer(layer);}; + void DrawText(const cPoint &Point, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font); + void DrawImage(const cPoint &Point, const cImage &Image); + void DrawRectangle(const cRect &Rect, tColor Color); + void DrawEllipse(const cRect &Rect, tColor Color, int Quadrant); + void SetViewPort(const cRect &Rect); + int Width() {return pixmap->ViewPort().Width();}; + int Height() {return pixmap->ViewPort().Height();}; }; #endif //__TVGUIDE_STYLEDPIXMAP_H \ No newline at end of file diff --git a/switchtimer.c b/switchtimer.c new file mode 100644 index 0000000..4ba7b45 --- /dev/null +++ b/switchtimer.c @@ -0,0 +1,109 @@ +#include "switchtimer.h" + +cSwitchTimers SwitchTimers; + +// -- cSwitchTimer ----------------------------------------------------------------- +cSwitchTimer::cSwitchTimer(void) { +} + +cSwitchTimer::cSwitchTimer(const cEvent* Event) { + eventID = 0; + startTime = 0; + if (Event) { + eventID = Event->EventID(); + channelID = Event->ChannelID(); + startTime = Event->StartTime(); + } +} + +bool cSwitchTimer::Parse(const char *s) { + char *line; + char *pos; + char *pos_next; + int parameter = 1; + int valuelen; +#define MAXVALUELEN (10 * MaxFileName) + + char value[MAXVALUELEN]; + startTime=0; + + pos = line = strdup(s); + pos_next = pos + strlen(pos); + if (*pos_next == '\n') *pos_next = 0; + while (*pos) { + while (*pos == ' ') + pos++; + if (*pos) { + if (*pos != ':') { + pos_next = strchr(pos, ':'); + if (!pos_next) + pos_next = pos + strlen(pos); + valuelen = pos_next - pos + 1; + if (valuelen > MAXVALUELEN) + valuelen = MAXVALUELEN; + strn0cpy(value, pos, valuelen); + pos = pos_next; + switch (parameter) { + case 1: + channelID = tChannelID::FromString(value); + break; + case 2: + eventID = atoi(value); + break; + case 3: + startTime = atol(value); + break; + default: + break; + } + } + parameter++; + } + if (*pos) + pos++; + } + free(line); + return (parameter >= 3) ? true : false; +} + +bool cSwitchTimers::EventInSwitchList(const cEvent* event) { + if (!event) return false; + cMutexLock SwitchTimersLock(this); + cSwitchTimer* switchTimer = SwitchTimers.First(); + while (switchTimer) { + if (switchTimer->eventID == event->EventID()) + return true; + switchTimer = SwitchTimers.Next(switchTimer); + } + return false; +} + +bool cSwitchTimers::ChannelInSwitchList(const cChannel* channel) { + if (!channel) return false; + cMutexLock SwitchTimersLock(this); + cSwitchTimer* switchTimer = SwitchTimers.First(); + while (switchTimer) { + if (switchTimer->channelID == channel->GetChannelID()) + return true; + switchTimer = SwitchTimers.Next(switchTimer); + } + return false; +} + +void cSwitchTimers::DeleteSwitchTimer(const cEvent *event) { + if (!event) return; + cMutexLock SwitchTimersLock(this); + cSwitchTimer* switchTimer = SwitchTimers.First(); + cSwitchTimer* delTimer = NULL; + + while (switchTimer) { + if (switchTimer->eventID == event->EventID()) { + delTimer = switchTimer; + break; + } + switchTimer = SwitchTimers.Next(switchTimer); + } + if (delTimer) { + SwitchTimers.Del(delTimer, true); + } +} diff --git a/switchtimer.h b/switchtimer.h new file mode 100644 index 0000000..8958b44 --- /dev/null +++ b/switchtimer.h @@ -0,0 +1,30 @@ +#ifndef __TVGUIDE_SWITCHTIMER_H +#define __TVGUIDE_SWITCHTIMER_H + +#include + +class cSwitchTimer : public cListObject +{ +public: + tEventID eventID; + time_t startTime; + tChannelID channelID; + + cSwitchTimer(void); + cSwitchTimer(const cEvent* Event); + bool Parse(const char *s); +}; + +class cSwitchTimers : public cConfig, public cMutex +{ +public: + cSwitchTimers(void) {} + ~cSwitchTimers(void) {} + bool EventInSwitchList(const cEvent* event); + bool ChannelInSwitchList(const cChannel* channel); + void DeleteSwitchTimer(const cEvent *event); +}; + +extern cSwitchTimers SwitchTimers; + +#endif //__TVGUIDE_SWITCHTIMER_H diff --git a/themes/tvguide-darkblue.theme b/themes/tvguide-darkblue.theme index 90285ce..ec0a1b1 100644 --- a/themes/tvguide-darkblue.theme +++ b/themes/tvguide-darkblue.theme @@ -30,3 +30,15 @@ clrButtonYellowBorder = FFBBBB00 clrButtonBlue = 990000BB clrButtonBlueBorder = FF0000BB clrButtonBlend = DD000000 +clrRecMenuBackground = AA000000 +clrRecMenuTimerConflictBackground = FFCCCCCC +clrRecMenuTimerConflictBar = FF222222 +clrRecMenuTimerConflictOverlap = AAFF0000 +clrRecMenuDayActive = FF00FF00 +clrRecMenuDayInactive = FFFF0000 +clrRecMenuDayHighlight = 44FFFFFF +clrRecMenuTextBack = FF000000 +clrRecMenuTextActiveBack = FF404749 +clrRecMenuKeyboardBack = FF000000 +clrRecMenuKeyboardBorder = FFFFFFFF +clrRecMenuKeyboardHigh = 40BB0000 \ No newline at end of file diff --git a/themes/tvguide-default.theme b/themes/tvguide-default.theme index 9704e65..05b2e47 100644 --- a/themes/tvguide-default.theme +++ b/themes/tvguide-default.theme @@ -30,3 +30,15 @@ clrButtonYellowBorder = FFBBBB00 clrButtonBlue = 990000BB clrButtonBlueBorder = FF0000BB clrButtonBlend = DD000000 +clrRecMenuBackground = AA000000 +clrRecMenuTimerConflictBackground = FFCCCCCC +clrRecMenuTimerConflictBar = FF222222 +clrRecMenuTimerConflictOverlap = AAFF0000 +clrRecMenuDayActive = FF00FF00 +clrRecMenuDayInactive = FFFF0000 +clrRecMenuDayHighlight = 44FFFFFF +clrRecMenuTextBack = FF000000 +clrRecMenuTextActiveBack = FF404749 +clrRecMenuKeyboardBack = FF000000 +clrRecMenuKeyboardBorder = FFFFFFFF +clrRecMenuKeyboardHigh = 40BB0000 \ No newline at end of file diff --git a/themes/tvguide-keepitsimple.theme b/themes/tvguide-keepitsimple.theme index f88aa92..852d7c3 100644 --- a/themes/tvguide-keepitsimple.theme +++ b/themes/tvguide-keepitsimple.theme @@ -25,4 +25,16 @@ clrButtonYellow = 99BBBB00 clrButtonYellowBorder = FFBBBB00 clrButtonBlue = 990000BB clrButtonBlueBorder = FF0000BB -clrButtonBlend = DD000000 \ No newline at end of file +clrButtonBlend = DD000000 +clrRecMenuBackground = AA000000 +clrRecMenuTimerConflictBackground = FFCCCCCC +clrRecMenuTimerConflictBar = FF222222 +clrRecMenuTimerConflictOverlap = AAFF0000 +clrRecMenuDayActive = FF00FF00 +clrRecMenuDayInactive = FFFF0000 +clrRecMenuDayHighlight = 44FFFFFF +clrRecMenuTextBack = FF000000 +clrRecMenuTextActiveBack = FF404749 +clrRecMenuKeyboardBack = FF000000 +clrRecMenuKeyboardBorder = BB555555 +clrRecMenuKeyboardHigh = 40BB0000 \ No newline at end of file diff --git a/themes/tvguide-nOpacity.theme b/themes/tvguide-nOpacity.theme index f492c2d..345d13a 100644 --- a/themes/tvguide-nOpacity.theme +++ b/themes/tvguide-nOpacity.theme @@ -30,3 +30,15 @@ clrButtonYellowBorder = FFBBBB00 clrButtonBlue = 990000BB clrButtonBlueBorder = FF0000BB clrButtonBlend = DD000000 +clrRecMenuBackground = AA000000 +clrRecMenuTimerConflictBackground = FFCCCCCC +clrRecMenuTimerConflictBar = FF222222 +clrRecMenuTimerConflictOverlap = AAFF0000 +clrRecMenuDayActive = FF00FF00 +clrRecMenuDayInactive = FFFF0000 +clrRecMenuDayHighlight = 44FFFFFF +clrRecMenuTextBack = FF000000 +clrRecMenuTextActiveBack = FF404749 +clrRecMenuKeyboardBack = FF000000 +clrRecMenuKeyboardBorder = FF003DF5 +clrRecMenuKeyboardHigh = 40BB0000 \ No newline at end of file diff --git a/themes/tvguide-nOpacitydarkred.theme b/themes/tvguide-nOpacitydarkred.theme index d0c73a0..b14f6ac 100644 --- a/themes/tvguide-nOpacitydarkred.theme +++ b/themes/tvguide-nOpacitydarkred.theme @@ -30,3 +30,15 @@ clrButtonYellowBorder = FFBBBB00 clrButtonBlue = 990000BB clrButtonBlueBorder = FF0000BB clrButtonBlend = DD000000 +clrRecMenuBackground = AA000000 +clrRecMenuTimerConflictBackground = FFCCCCCC +clrRecMenuTimerConflictBar = FF222222 +clrRecMenuTimerConflictOverlap = AAFF0000 +clrRecMenuDayActive = FF00FF00 +clrRecMenuDayInactive = FFFF0000 +clrRecMenuDayHighlight = 44FFFFFF +clrRecMenuTextBack = FF000000 +clrRecMenuTextActiveBack = FF404749 +clrRecMenuKeyboardBack = FF000000 +clrRecMenuKeyboardBorder = FF660000 +clrRecMenuKeyboardHigh = 40BB0000 \ No newline at end of file diff --git a/themes/tvguide-nOpacitygreen.theme b/themes/tvguide-nOpacitygreen.theme index e787d15..3042a84 100644 --- a/themes/tvguide-nOpacitygreen.theme +++ b/themes/tvguide-nOpacitygreen.theme @@ -30,3 +30,15 @@ clrButtonYellowBorder = FFBBBB00 clrButtonBlue = 990000BB clrButtonBlueBorder = FF0000BB clrButtonBlend = DD000000 +clrRecMenuBackground = AA000000 +clrRecMenuTimerConflictBackground = FFCCCCCC +clrRecMenuTimerConflictBar = FF222222 +clrRecMenuTimerConflictOverlap = AAFF0000 +clrRecMenuDayActive = FF00FF00 +clrRecMenuDayInactive = FFFF0000 +clrRecMenuDayHighlight = 44FFFFFF +clrRecMenuTextBack = FF000000 +clrRecMenuTextActiveBack = FF404749 +clrRecMenuKeyboardBack = FF000000 +clrRecMenuKeyboardBorder = EE006600 +clrRecMenuKeyboardHigh = 40BB0000 \ No newline at end of file diff --git a/themes/tvguide-nOpacityiceblue.theme b/themes/tvguide-nOpacityiceblue.theme index 2112974..ce5cb2c 100644 --- a/themes/tvguide-nOpacityiceblue.theme +++ b/themes/tvguide-nOpacityiceblue.theme @@ -25,3 +25,18 @@ clrButtonYellow = FFBBBB00 clrButtonYellowBorder = FFBBBB00 clrButtonBlue = FF0000BB clrButtonBlueBorder = FF0000BB +clrRecMenuBackground = AA000000 +clrRecMenuTimerConflictBackground = FFCCCCCC +clrRecMenuTimerConflictBar = FF222222 +clrRecMenuTimerConflictOverlap = AAFF0000 +clrRecMenuDayActive = FF00FF00 +clrRecMenuDayInactive = FFFF0000 +clrRecMenuDayHighlight = 77000000 +clrRecMenuTextBack = FF3C3C3C +clrRecMenuTextActiveBack = FF404749 +clrRecMenuKeyboardBack = FF000044 +clrRecMenuKeyboardBorder = FF3C3C3C +clrRecMenuKeyboardHigh = 55FFFFFF +clrButtonRedKeyboard = FFBB0000 +clrButtonGreenKeyboard = FF00BB00 +clrButtonYellowKeyboard = FFBBBB00 diff --git a/timeline.c b/timeline.c index 454aa9c..4355352 100644 --- a/timeline.c +++ b/timeline.c @@ -3,11 +3,11 @@ cTimeLine::cTimeLine(cMyTime *myTime) { this->myTime = myTime; if (tvguideConfig.displayMode == eVertical) { - dateViewer = new cStyledPixmap(osdManager.requestPixmap(3, cRect(0, + dateViewer = new cStyledPixmap(osdManager.requestPixmap(1, cRect(0, tvguideConfig.statusHeaderHeight, tvguideConfig.timeLineWidth, tvguideConfig.channelHeaderHeight + tvguideConfig.channelGroupsHeight))); - timeline = osdManager.requestPixmap(2, cRect(0, + timeline = osdManager.requestPixmap(1, cRect(0, tvguideConfig.statusHeaderHeight + tvguideConfig.channelHeaderHeight + tvguideConfig.channelGroupsHeight, tvguideConfig.timeLineWidth, tvguideConfig.osdHeight - tvguideConfig.statusHeaderHeight - tvguideConfig.channelHeaderHeight - tvguideConfig.channelGroupsHeight - tvguideConfig.footerHeight) @@ -16,11 +16,11 @@ cTimeLine::cTimeLine(cMyTime *myTime) { tvguideConfig.timeLineWidth, 1440*tvguideConfig.minutePixel)); } else if (tvguideConfig.displayMode == eHorizontal) { - dateViewer = new cStyledPixmap(osdManager.requestPixmap(3, cRect(0, + dateViewer = new cStyledPixmap(osdManager.requestPixmap(1, cRect(0, tvguideConfig.statusHeaderHeight, tvguideConfig.channelHeaderWidth + tvguideConfig.channelGroupsWidth, tvguideConfig.timeLineHeight))); - timeline = osdManager.requestPixmap(2, cRect(tvguideConfig.channelHeaderWidth + tvguideConfig.channelGroupsWidth, + timeline = osdManager.requestPixmap(1, cRect(tvguideConfig.channelHeaderWidth + tvguideConfig.channelGroupsWidth, tvguideConfig.statusHeaderHeight, tvguideConfig.osdWidth - tvguideConfig.channelHeaderWidth - tvguideConfig.channelGroupsWidth, tvguideConfig.timeLineHeight) @@ -29,7 +29,7 @@ cTimeLine::cTimeLine(cMyTime *myTime) { 1440*tvguideConfig.minutePixel, tvguideConfig.timeLineWidth)); } - clock = new cStyledPixmap(osdManager.requestPixmap(3, cRect(0, + clock = new cStyledPixmap(osdManager.requestPixmap(1, cRect(0, tvguideConfig.osdHeight- tvguideConfig.footerHeight, tvguideConfig.timeLineWidth, tvguideConfig.footerHeight-9))); diff --git a/timer.c b/timer.c index 8e60c0e..f6d9925 100644 --- a/timer.c +++ b/timer.c @@ -120,3 +120,49 @@ time_t cMyTime::GetRounded() { void cMyTime::debug() { esyslog("t: %s, tStart: %s, tEnd: %s", *TimeString(t), *TimeString(tStart), *TimeString(tEnd)); } + +// --- cTimeInterval ------------------------------------------------------------- + +cTimeInterval::cTimeInterval(time_t start, time_t stop) { + this->start = start; + this->stop = stop; +} + +cTimeInterval::~cTimeInterval(void) { +} + +cTimeInterval *cTimeInterval::Intersect(cTimeInterval *interval) { + time_t startIntersect, stopIntersect; + + if ((stop <= interval->Start()) || (interval->Stop() <= start)) { + return NULL; + } + + if (start <= interval->Start()) { + startIntersect = interval->Start(); + } else { + startIntersect = start; + } + if (stop <= interval->Stop()) { + stopIntersect = stop; + } else { + stopIntersect = interval->Stop(); + } + return new cTimeInterval(startIntersect, stopIntersect); +} + +cTimeInterval *cTimeInterval::Union(cTimeInterval *interval) { + time_t startUnion, stopUnion; + + if (start <= interval->Start()) { + startUnion = start; + } else { + startUnion = interval->Start(); + } + if (stop <= interval->Stop()) { + stopUnion = interval->Stop(); + } else { + stopUnion = stop; + } + return new cTimeInterval(startUnion, stopUnion); +} \ No newline at end of file diff --git a/timer.h b/timer.h index e84f74c..2ab5045 100644 --- a/timer.h +++ b/timer.h @@ -30,4 +30,19 @@ class cMyTime { void debug(); }; +// --- cTimeInterval ------------------------------------------------------------- + +class cTimeInterval { + private: + time_t start; + time_t stop; + public: + cTimeInterval(time_t start, time_t stop); + virtual ~cTimeInterval(void); + time_t Start(void) { return start; }; + time_t Stop(void) { return stop; }; + cTimeInterval *Intersect(cTimeInterval *interval); + cTimeInterval *Union(cTimeInterval *interval); +}; + #endif //__TVGUIDE_TIMER_H \ No newline at end of file diff --git a/tools.c b/tools.c new file mode 100644 index 0000000..2a3815e --- /dev/null +++ b/tools.c @@ -0,0 +1,370 @@ +#include +#include +#include + +/**************************************************************************************** +* CUTTEXT +****************************************************************************************/ +static std::string CutText(std::string text, int width, const cFont *font) { + if (width <= font->Size()) + return text.c_str(); + if (font->Width(text.c_str()) < width) + return text.c_str(); + cTextWrapper twText; + twText.Set(text.c_str(), font, width); + std::string cuttedTextNative = twText.GetLine(0); + std::stringstream sstrText; + sstrText << cuttedTextNative << "..."; + std::string cuttedText = sstrText.str(); + int actWidth = font->Width(cuttedText.c_str()); + if (actWidth > width) { + int overlap = actWidth - width; + int charWidth = font->Width("."); + if (charWidth == 0) + charWidth = 1; + int cutChars = overlap / charWidth; + if (cutChars > 0) { + cuttedTextNative = cuttedTextNative.substr(0, cuttedTextNative.length() - cutChars); + std::stringstream sstrText2; + sstrText2 << cuttedTextNative << "..."; + cuttedText = sstrText2.str(); + } + } + return cuttedText; +} +/**************************************************************************************** +* SPLTSTRING +****************************************************************************************/ +class splitstring : public std::string { + std::vector flds; +public: + splitstring(const char *s) : std::string(s) { }; + std::vector& split(char delim, int rep=0); +}; + +// split: receives a char delimiter; returns a vector of strings +// By default ignores repeated delimiters, unless argument rep == 1. +std::vector& splitstring::split(char delim, int rep) { + if (!flds.empty()) flds.clear(); // empty vector if necessary + std::string work = data(); + std::string buf = ""; + int i = 0; + while (i < work.length()) { + if (work[i] != delim) + buf += work[i]; + else if (rep == 1) { + flds.push_back(buf); + buf = ""; + } else if (buf.length() > 0) { + flds.push_back(buf); + buf = ""; + } + i++; + } + if (!buf.empty()) + flds.push_back(buf); + return flds; +} + +/**************************************************************************************** +* FINDIGNORECASE +****************************************************************************************/ +int FindIgnoreCase(const std::string& expr, const std::string& query) +{ + const char *p = expr.c_str(); + const char *r = strcasestr(p, query.c_str()); + + if (!r) + return -1; + return r - p; +} + +/**************************************************************************************** +* FUZZYSEARCH +****************************************************************************************/ +#include +#include +#include +#include + +#ifndef _AFUZZY_H +#define _AFUZZY_H + +// source from: +/* + Leonid Boitsov 2002. (itman@narod.ru) + C version of Stas Namin. + This code is a GPL software and is distributed under GNU + public licence without any warranty. +*/ + +typedef unsigned int Uint; + +#define MaxPatSize (sizeof(Uint) * 8) + +typedef struct +{ + Uint *R, + *R1, + *RP, + *S, + *RI; + Uint *FilterS; + + int Map[256]; + int FilterMap[256]; + int k; + Uint mask_ok; + Uint filter_ok; + Uint filter_shift; + int r_size; + int FilterSet; +} AFUZZY; + +void afuzzy_init(const char *p, int kerr, int UseFilter, AFUZZY *fuzzy); +void afuzzy_free(AFUZZY *fuzzy); +int afuzzy_checkSUB(const char *t, AFUZZY *fuzzy); + +#endif + + +static int afuzzy_checkFLT(const char *t, AFUZZY *fuzzy); + +/****************************************************************************** +FUNCTION afuzzy_init() + Initialization of the fuzzy search routine. This applies to the consequent + calls of the afuzzy_CheckRTR (whole string matching) and afuzzy_CheckSUB + (substring match) routines. afuzzy_init() should be called for each + new pattern or error length. The search is case sensitive + +ARGUMENTS: + p Pattern + kerr Number of possible errors. Shouldn't exceed pattern length + UseFilter Use agrep filter algorithm that speeds up search. + fuzzy pointer to the structure that will be later passes to Check* + (the first 6 elements should be NULLs for the first call) + +RETURN VALUE: + none + +ALGORITHM + see. the article on agrep algorithms. + The only change is accounting transpositions as one edit operation . +******************************************************************************/ +void afuzzy_init(const char *p, int kerr, int UseFilter, AFUZZY *fuzzy) +{ + int cnt, p_len, i, j, l, d, m, dd; + char PatFilter[sizeof(Uint)*8 + 1]; + + fuzzy->k = kerr; + m = strlen(p); + fuzzy->FilterSet = 0; + memset(fuzzy->Map, 0 , sizeof(fuzzy->Map) ); + + if (fuzzy->S) + free(fuzzy->S); + if (fuzzy->R) + free(fuzzy->R); + if (fuzzy->R1) + free(fuzzy->R1); + if (fuzzy->RP) + free(fuzzy->RP); + if (fuzzy->RI) + free(fuzzy->RI); + if (fuzzy->FilterS) + free(fuzzy->FilterS); + + fuzzy->FilterS = NULL; + fuzzy->S = (Uint *)calloc(m + 1, sizeof(Uint)); + fuzzy->R = (Uint *)calloc(fuzzy->k + 1, sizeof(Uint)); + fuzzy->R1 = (Uint *)calloc(fuzzy->k + 1, sizeof(Uint)); + fuzzy->RI = (Uint *)calloc(fuzzy->k + 1, sizeof(Uint)); + fuzzy->RP = (Uint *)calloc(fuzzy->k + 1, sizeof(Uint)); + + for (i = 0, cnt = 0; i < m; i++) + { + l = fuzzy->Map[(unsigned char)p[i]]; + if (!l) + { + l = fuzzy->Map[(unsigned char)p[i]] = ++cnt; + fuzzy->S[l] = 0; + } + fuzzy->S[l] |= 1 << i; + } + + + for (d = 0; d <= fuzzy->k; d++) + fuzzy->RI[d] = (1 << d) - 1; + + fuzzy->mask_ok = (1 << (m - 1)); + fuzzy->r_size = sizeof(Uint) * (fuzzy->k + 1); + p_len = m; + + if (p_len > (int) sizeof(Uint)*8) + p_len = (int) sizeof(Uint)*8; + + /* If k is zero then no filter is needed! */ + if (fuzzy->k && (p_len >= 2*(fuzzy->k + 1)) ) + { + if (UseFilter) + { + fuzzy->FilterSet = 1; + memset(fuzzy->FilterMap, 0 , sizeof(fuzzy->FilterMap) ); + fuzzy->FilterS = (Uint *)calloc(m + 1, sizeof(Uint)); + + /* Not let's fill the interleaved pattern */ + dd = p_len / (fuzzy->k + 1); + p_len = dd * (fuzzy->k + 1); + + for (i = 0, cnt = 0; i < dd; i++) + for (j = 0; j < fuzzy->k + 1; j++, cnt++) + PatFilter[cnt] = (unsigned char)p[j*dd + i]; + PatFilter[p_len] = 0; + + for (i = 0, cnt = 0; i < p_len; i++) + { + l = fuzzy->FilterMap[(unsigned char)PatFilter[i]]; + if (!l) + { + l = fuzzy->FilterMap[(unsigned char)PatFilter[i]] = ++cnt; + fuzzy->FilterS[l] = 0; + } + fuzzy->FilterS[l] |= 1 << i; + } + fuzzy->filter_ok = 0; + for (i = p_len - fuzzy->k - 1; i <= p_len - 1; i++) /* k+1 times */ + fuzzy->filter_ok |= 1 << i; + + /* k+1 first bits set to 1 */ + fuzzy->filter_shift = (1 << (fuzzy->k + 2)) - 1; + } + } +} + +/****************************************************************************** +FUNCTION afuzzy_free() + Cleaning up after previous afuzzy_init() call. + +ARGUMENTS: + fuzzy pointer to the afuzzy parameters structure + +RETURN VALUE: + none +******************************************************************************/ +void afuzzy_free(AFUZZY *fuzzy) +{ + if (fuzzy->S) + { + free(fuzzy->S); + fuzzy->S = NULL; + } + if (fuzzy->R) + { + free(fuzzy->R); + fuzzy->R = NULL; + } + if (fuzzy->R1) + { + free(fuzzy->R1); + fuzzy->R1 = NULL; + } + if (fuzzy->RP) + { + free(fuzzy->RP); + fuzzy->RP = NULL; + } + if (fuzzy->RI) + { + free(fuzzy->RI); + fuzzy->RI = NULL; + } + if (fuzzy->FilterS) + { + free(fuzzy->FilterS); + fuzzy->FilterS = NULL; + } +} + + +/****************************************************************************** +FUNCTION afuzzy_CheckSUB() + Perform a fuzzy pattern substring matching. afuzzy_init() should be + called previously to initialize the pattern and error length. + Positive result means that some part of the string given matches the + pattern with no more than afuzzy->k errors (1 error = 1 letter + replacement or transposition) + +ARGUMENTS: + t the string to test + fuzzy pointer to the afuzzy parameters structure + +RETURN VALUE: + 0 - no match + > 0 - strings match + +ALGORITHM + ???????????????? +******************************************************************************/ +int afuzzy_checkSUB(const char *t, AFUZZY *fuzzy) +{ + register char c; + register int j, d; + + /* For eficciency this case should be little bit optimized */ + if (!fuzzy->k) + { + Uint R = 0, R1; + + for (j = 0; (c = t[j]) != '\0'; j++) + { + R1 = ( ((R<<1) | 1) & fuzzy->S[fuzzy->Map[(unsigned char)c]]); + R = R1; + + if (R1 & fuzzy->mask_ok) + return 1; + } /* end for (register int j = 0 ... */ + return 0; + } + + if (fuzzy->FilterSet && !afuzzy_checkFLT(t, fuzzy)) + return 0; + + memcpy(fuzzy->R, fuzzy->RI, fuzzy->r_size); /* R = RI */ + + for (j = 0; (c = t[j]); j++) + { + for (d = 0; d <= fuzzy->k; d++) + { + fuzzy->R1[d] = (((fuzzy->R[d]<<1) | 1) & + fuzzy->S[fuzzy->Map[(unsigned char)c]]); + if (d > 0) + fuzzy->R1[d] |= ((fuzzy->R[d-1] | fuzzy->R1[d-1])<<1) | 1 | + fuzzy->R[d-1]; + } + if (fuzzy->R1[fuzzy->k] & fuzzy->mask_ok) + return j; + + memcpy(fuzzy->R, fuzzy->R1, fuzzy->r_size); + + } /* end for (register int j = 0 ... */ + + return 0; +} + +static int afuzzy_checkFLT(const char *t, AFUZZY *fuzzy) +{ + register Uint FilterR = 0; + register Uint FilterR1; + register int j; + + for (j = 0; t[j] != '\0'; j++) + { + FilterR1 = ( ((FilterR<<(fuzzy->k+1)) | fuzzy->filter_shift) & + fuzzy->FilterS[fuzzy->FilterMap[(unsigned char)t[j]]]); + if (FilterR1 & fuzzy->filter_ok) + return 1; + FilterR = FilterR1; + } /* end for (register int j = 0 ... */ + + return 0; +} diff --git a/tvguide.c b/tvguide.c index 806868b..f33c1e9 100644 --- a/tvguide.c +++ b/tvguide.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "tvguideosd.c" @@ -20,7 +21,7 @@ -static const char *VERSION = "0.0.5"; +static const char *VERSION = "0.0.6"; static const char *DESCRIPTION = "A fancy 2d EPG Viewer"; static const char *MAINMENUENTRY = "Tvguide"; @@ -28,6 +29,7 @@ class cPluginTvguide : public cPlugin { private: bool logoPathSet; bool imagesPathSet; + bool iconsPathSet; public: cPluginTvguide(void); virtual ~cPluginTvguide(); @@ -58,6 +60,7 @@ cPluginTvguide::cPluginTvguide(void) // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT! logoPathSet = false; imagesPathSet = false; + iconsPathSet = false; } cPluginTvguide::~cPluginTvguide() @@ -124,6 +127,13 @@ bool cPluginTvguide::Start(void) tvguideConfig.SetImagesPath(path); logoPathSet = true; } + + if (!iconsPathSet) { + cString path = cString::sprintf("%s/icons/", cPlugin::ConfigDirectory(PLUGIN_NAME_I18N)); + tvguideConfig.SetIconsPath(path); + iconsPathSet = true; + } + return true; } diff --git a/tvguideosd.c b/tvguideosd.c index 5aaa128..01fcbe6 100644 --- a/tvguideosd.c +++ b/tvguideosd.c @@ -37,6 +37,21 @@ THEME_CLR(theme, clrButtonYellowBorder, 0xFFBBBB00); THEME_CLR(theme, clrButtonBlue, 0x990000BB); THEME_CLR(theme, clrButtonBlueBorder, 0xFF0000BB); THEME_CLR(theme, clrButtonBlend, 0xDD000000); +THEME_CLR(theme, clrRecMenuBackground, 0xB0000000); +THEME_CLR(theme, clrRecMenuTimerConflictBackground, 0xFFCCCCCC); +THEME_CLR(theme, clrRecMenuTimerConflictBar, 0xFF222222); +THEME_CLR(theme, clrRecMenuTimerConflictOverlap, 0xAAFF0000); +THEME_CLR(theme, clrRecMenuDayActive, 0xFF00FF00); +THEME_CLR(theme, clrRecMenuDayInactive, 0xFFFF0000); +THEME_CLR(theme, clrRecMenuDayHighlight, 0x44FFFFFF); +THEME_CLR(theme, clrRecMenuTextBack, 0xFF000000); +THEME_CLR(theme, clrRecMenuTextActiveBack, 0xFF404749); +THEME_CLR(theme, clrRecMenuKeyboardBack, 0xFF000000); +THEME_CLR(theme, clrRecMenuKeyboardBorder, clrWhite); +THEME_CLR(theme, clrRecMenuKeyboardHigh, 0x55FFFFFF); +THEME_CLR(theme, clrButtonRedKeyboard, 0xFFBB0000); +THEME_CLR(theme, clrButtonGreenKeyboard, 0xFF00BB00); +THEME_CLR(theme, clrButtonYellowKeyboard, 0xFFBBBB00); #include "config.c" cTvguideConfig tvguideConfig; @@ -44,11 +59,12 @@ cTvguideConfig tvguideConfig; #include "osdmanager.c" cOsdManager osdManager; +#include "tools.c" +#include "switchtimer.c" #include "setup.c" #include "imageloader.c" #include "styledpixmap.c" #include "timer.c" -#include "messagebox.c" #include "timeline.c" #include "grid.c" #include "headergrid.c" @@ -60,6 +76,11 @@ cOsdManager osdManager; #include "channelgroup.c" #include "channelgroups.c" #include "footer.c" +#include "recmenuitem.c" +#include "recmenu.c" +#include "recmanager.c" +#include "recmenus.c" +#include "recmenumanager.c" #include "tvguideosd.h" #include @@ -69,6 +90,7 @@ cTvGuideOsd::cTvGuideOsd(void) { detailViewActive = false; activeGrid = NULL; timeLine = NULL; + recMenuManager = NULL; } cTvGuideOsd::~cTvGuideOsd() { @@ -82,7 +104,7 @@ cTvGuideOsd::~cTvGuideOsd() { delete timeLine; delete channelGroups; delete footer; - cMessageBox::Destroy(); + delete recMenuManager; osdManager.deleteOsd(); } @@ -97,6 +119,12 @@ void cTvGuideOsd::Show(void) { osdManager.setBackground(); myTime = new cMyTime(); myTime->Now(); + SwitchTimers.Load(AddDirectory(cPlugin::ConfigDirectory("epgsearch"), "epgsearchswitchtimers.conf")); + cSwitchTimer *st = NULL; + for (st = SwitchTimers.First(); st; st = SwitchTimers.Next(st)) { + esyslog("tvguide: switchtimer eventID %d time %ld", st->eventID, st->startTime); + } + recMenuManager = new cRecMenuManager(); drawOsd(); } esyslog("tvguide: Rendering took %d ms", int(cTimeMs::Now()-start)); @@ -392,15 +420,10 @@ void cTvGuideOsd::processKeyUp() { if (!activeGrid) { return; } - if (detailViewActive) { - detailView->scrollUp(); - osdManager.flush(); - } else { - if (tvguideConfig.displayMode == eVertical) { - timeBack(); - } else if (tvguideConfig.displayMode == eHorizontal) { - channelBack(); - } + if (tvguideConfig.displayMode == eVertical) { + timeBack(); + } else if (tvguideConfig.displayMode == eHorizontal) { + channelBack(); } } @@ -408,21 +431,14 @@ void cTvGuideOsd::processKeyDown() { if (!activeGrid) { return; } - if (detailViewActive) { - detailView->scrollDown(); - osdManager.flush(); - } else { - if (tvguideConfig.displayMode == eVertical) { - timeForward(); - } else if (tvguideConfig.displayMode == eHorizontal) { - channelForward(); - } + if (tvguideConfig.displayMode == eVertical) { + timeForward(); + } else if (tvguideConfig.displayMode == eHorizontal) { + channelForward(); } } void cTvGuideOsd::processKeyLeft() { - if (detailViewActive) - return; if (activeGrid == NULL) return; if (tvguideConfig.displayMode == eVertical) { @@ -433,8 +449,6 @@ void cTvGuideOsd::processKeyLeft() { } void cTvGuideOsd::processKeyRight() { - if (detailViewActive) - return; if (activeGrid == NULL) return; if (tvguideConfig.displayMode == eVertical) { @@ -447,26 +461,7 @@ void cTvGuideOsd::processKeyRight() { void cTvGuideOsd::processKeyRed() { if ((activeGrid == NULL) || activeGrid->isDummy()) return; - cTimer *timer = new cTimer(activeGrid->GetEvent()); - cTimer *t = Timers.GetTimer(timer); - cString msg; - if (t) { - isyslog("timer %s already exists", *timer->ToDescr()); - delete timer; - msg = cString::sprintf(tr("Timer not set! There is already a timer for this item.")); - } else { - Timers.Add(timer); - Timers.SetModified(); - msg = cString::sprintf("%s:\n%s (%s) %s - %s", tr("Timer set"), activeGrid->GetEvent()->Title(), timer->Channel()->Name(), *DayDateTime(timer->StartTime()), *TimeString(timer->StopTime())); - timer->SetEvent(activeGrid->GetEvent()); - activeGrid->setTimer(); - activeGrid->column->setTimer(); - activeGrid->SetDirty(); - activeGrid->Draw(); - osdManager.flush(); - isyslog("timer %s added (active)", *timer->ToDescr()); - } - cMessageBox::Start(4000, msg); + recMenuManager->Start(activeGrid->GetEvent()); } void cTvGuideOsd::processKeyGreen() { @@ -547,7 +542,7 @@ eOSState cTvGuideOsd::processKeyBlue() { } eOSState cTvGuideOsd::processKeyOk() { - if ((tvguideConfig.blueKeyMode == 0) || detailViewActive ) { + if (tvguideConfig.blueKeyMode == 0) { DetailedEPG(); } else if (tvguideConfig.blueKeyMode == 1) { return ChannelSwitch(); @@ -567,17 +562,13 @@ eOSState cTvGuideOsd::ChannelSwitch() { } void cTvGuideOsd::DetailedEPG() { - if (detailViewActive) { - delete detailView; - detailView = NULL; - detailViewActive = false; + if (!activeGrid->isDummy()) { + detailViewActive = true; + detailView = new cDetailView(activeGrid->GetEvent()); + detailView->drawHeader(); + detailView->drawContent(); + detailView->drawScrollbar(); osdManager.flush(); - } else { - if (!activeGrid->isDummy()) { - detailViewActive = true; - detailView = new cDetailView(activeGrid); - detailView->Start(); - } } } @@ -646,11 +637,32 @@ void cTvGuideOsd::processKey9() { osdManager.flush(); } +void cTvGuideOsd::SetTimers() { + for (cChannelColumn *column = columns.First(); column; column = columns.Next(column)) { + column->SetTimers(); + } +} + eOSState cTvGuideOsd::ProcessKey(eKeys Key) { - eOSState state = cOsdObject::ProcessKey(Key); - if (state == osUnknown) { - cPixmap::Lock(); + eOSState state = osContinue; + cPixmap::Lock(); + if (recMenuManager->isActive()) { + state = recMenuManager->ProcessKey(Key); + if (state == osEnd) { + SetTimers(); + osdManager.flush(); + } state = osContinue; + } else if (detailViewActive) { + state = detailView->ProcessKey(Key); + if (state == osEnd) { + delete detailView; + detailView = NULL; + detailViewActive = false; + osdManager.flush(); + state = osContinue; + } + } else { switch (Key & ~k_Repeat) { case kUp: processKeyUp(); break; case kDown: processKeyDown(); break; @@ -661,7 +673,7 @@ eOSState cTvGuideOsd::ProcessKey(eKeys Key) { case kYellow: processKeyYellow(); break; case kBlue: state = processKeyBlue(); break; case kOk: state = processKeyOk(); break; - case kBack: state=osEnd; break; + case kBack: state=osEnd; break; case k1: processKey1(); break; case k3: processKey3(); break; case k4: processKey4(); break; @@ -670,8 +682,8 @@ eOSState cTvGuideOsd::ProcessKey(eKeys Key) { case k9: processKey9(); break; default: break; } - cPixmap::Unlock(); } + cPixmap::Unlock(); return state; } diff --git a/tvguideosd.h b/tvguideosd.h index bc46728..717ce10 100644 --- a/tvguideosd.h +++ b/tvguideosd.h @@ -13,6 +13,7 @@ private: cTimeLine *timeLine; cChannelGroups *channelGroups; cFooter *footer; + cRecMenuManager *recMenuManager; bool detailViewActive; void drawOsd(); void readChannels(const cChannel *channelStart); @@ -42,6 +43,7 @@ private: void ScrollBack(); eOSState ChannelSwitch(); void DetailedEPG(); + void SetTimers(); void dump(); public: cTvGuideOsd(void);