/* -*- 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 as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html The author can be reached at cwieninger@gmx.de The project's page is at http://winni.vdr-developer.org/epgsearch */ #include #include "menu_searchresults.h" #include "blacklist.h" #include "epgsearchext.h" #include "menu_myedittimer.h" #include "menu_event.h" #include "menu_commands.h" #include "epgsearchcfg.h" #include "epgsearchtools.h" #include "changrp.h" #include "recdone.h" #include "epgsearchcats.h" #include #include "menu_conflictcheck.h" #include "uservars.h" #include "menu_deftimercheckmethod.h" #include "afuzzy.h" #include "timerstatus.h" #include "switchtimer.h" const char* ButtonBlue[3] = {NULL, NULL, NULL}; extern int gl_InfoConflict; extern bool isUTF8; static int CompareRecording(const void *p1, const void *p2) { return (int)((*(cRecording **)p1)->Start() - (*(cRecording **)p2)->Start()); } // --- cMenuSearchResultsItem ------------------------------------------------------- cMenuSearchResultsItem::cMenuSearchResultsItem(const cEvent *EventInfo, bool EpisodeOnly, bool PreviewTimer, cMenuTemplate* MenuTemplate, const cSearchExt* Search) { fileName = NULL; event = EventInfo; timerMatch = tmNone; episodeOnly = EpisodeOnly; previewTimer = PreviewTimer; menuTemplate = MenuTemplate ? MenuTemplate : cTemplFile::GetTemplateByName("MenuSearchResults"); search = Search; inSwitchList = false; timerActive = false; Update(true); } bool cMenuSearchResultsItem::Update(bool Force) { if (!menuTemplate) return false; bool result = false; eTimerMatch OldTimerMatch = timerMatch; bool OldInSwitchList = inSwitchList; bool OldtimerActive = timerActive; bool hasMatch = false; const cTimer* timer = NULL; LOCK_TIMERS_READ; if (event) timer = Timers->GetMatch(event, &timerMatch); if (event) inSwitchList = (SwitchTimers.InSwitchList(event) != NULL); if (timer) hasMatch = true; if (timer) timerActive = timer->HasFlags(tfActive); if (Force || timerMatch != OldTimerMatch || inSwitchList != OldInSwitchList || timerActive != OldtimerActive) { char t[Utf8BufSize(2)] = "", v[Utf8BufSize(2)] = "", r[Utf8BufSize(2)] = ""; char szStatus[Utf8BufSize(4)] = ""; if (EPGSearchConfig.WarEagle) { if (!isUTF8) { t[0] = event && hasMatch ? (timerMatch == tmFull) ? ((timer && timer->Recording()) ? ICON_REC : (timerActive ? ICON_CLOCK : ICON_TIMER_INACT)) : (timerActive ? ICON_CLOCK_HALF : ' ') : ' '; t[1] = '\0'; v[0] = event && event->Vps() && (event->Vps() - event->StartTime()) ? ICON_VPS : ' '; v[1] = '\0'; r[0] = event && event->IsRunning() ? ICON_RUNNING : ' '; r[1] = '\0'; } else { #if defined(__GNUC__) && __GNUC__ < 3 && __GNUC_MINOR__ < 96 #else sprintf(t, "%s", (event && hasMatch ? (timerMatch == tmFull) ? ((timer && timer->Recording()) ? ICON_REC_UTF8 : (timerActive ? ICON_CLOCK_UTF8 : ICON_TIMER_INACT_UTF8)) : (timerActive ? ICON_CLOCK_HALF_UTF8 : " ") : " ")); sprintf(v, "%s", event && event->Vps() && (event->Vps() - event->StartTime()) ? ICON_VPS_UTF8 : " "); sprintf(r, "%s", (event && event->IsRunning() ? ICON_RUNNING_UTF8 : " ")); #endif } } else { t[0] = event && hasMatch ? (timerMatch == tmFull) ? ((timer && timer->Recording()) ? 'R' : (timerActive ? 'T' : 'i')) : (timerActive ? 't' : ' ') : ' '; t[1] = '\0'; v[0] = event && event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' '; v[1] = '\0'; r[0] = event && event->IsRunning() ? '*' : ' '; r[1] = '\0'; } if (event && inSwitchList) { cSwitchTimer* s = SwitchTimers.InSwitchList(event); t[0] = (s && s->mode == 1) ? 's' : 'S'; } if (t[0] != 'T' && previewTimer) t[0] = 'P'; strcpy(szStatus, t); strcat(szStatus, v); strcat(szStatus, r); char* buffer = strdup(menuTemplate->MenuTemplate()); strreplace(buffer, '|', '\t'); if (!strcasestr(buffer, "%subtitle%") && cTemplFile::GetTemplateByName("MenuFavorites") != menuTemplate) // make sure, there is a subtitle buffer = strreplacei(buffer, "%title%", "%title% ~ %subtitle%"); if (episodeOnly) buffer = strreplacei(buffer, "%title%", ""); // parse the epxression and evaluate it cVarExpr varExpr(buffer); char* tmp = strdup(varExpr.Evaluate(event).c_str()); free(buffer); buffer = tmp; buffer = strreplacei(buffer, "$status$", szStatus); buffer = strreplacei(buffer, "$t_status$", t); buffer = strreplacei(buffer, "$v_status$", v); buffer = strreplacei(buffer, "$r_status$", r); buffer = FixSeparators(buffer, '~'); buffer = FixSeparators(buffer, ':'); buffer = FixSeparators(buffer, '-'); SetText(buffer, false); if (EPGSearchConfig.checkTimerConflAfterTimerProg && !Force && timer && timerMatch && timerMatch != OldTimerMatch) { cConflictCheck C; C.Check(); if (C.TimerInConflict(timer)) gl_InfoConflict = 1; } return true; } return result; } cMenuSearchResultsItem::cMenuSearchResultsItem(const cRecording *Recording) { previewTimer = false; episodeOnly = false; menuTemplate = NULL; timerMatch = tmNone; inSwitchList = false; event = NULL; search = NULL; fileName = strdup(Recording->FileName()); SetText(Recording->Title('\t')); } void cMenuSearchResultsItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable) { LOCK_CHANNELS_READ; const cChannel *channel = event ? Channels->GetByChannelID(event->ChannelID(), true, true) : NULL; if (!event) DisplayMenu->SetItem(Text(), Index, Current, Selectable); else if (!DisplayMenu->SetItemEvent(event, Index, Current, Selectable, channel, true, timerMatch, timerActive)) DisplayMenu->SetItem(Text(), Index, Current, Selectable); } // --- cMenuSearchResults ------------------------------------------------------- const cEvent *cMenuSearchResults::scheduleEventInfo = NULL; cMenuSearchResults::cMenuSearchResults(cMenuTemplate* MenuTemplate) : cOsdMenu("", MenuTemplate->Tab(0), MenuTemplate->Tab(1), MenuTemplate->Tab(2), MenuTemplate->Tab(3), MenuTemplate->Tab(4)) { SetMenuCategory(mcSchedule); helpKeys = -1; menuTemplate = MenuTemplate; modeYellow = showTitleEpisode; modeBlue = showAll; m_bSort = false; ignoreRunning = false; } int cMenuSearchResults::GetTab(int Tab) { if (!menuTemplate) menuTemplate = cTemplFile::GetTemplateByName("MenuSearchResults"); return menuTemplate->Tab(Tab - 1); } bool cMenuSearchResults::Update(void) { bool result = false; LOCK_TIMERS_READ; for (cOsdItem *item = First(); item; item = Next(item)) { if (((cMenuSearchResultsItem *)item)->Update(Timers)) result = true; } return result; } eOSState cMenuSearchResults::Record(void) { UpdateCurrent(); cMenuSearchResultsItem *item = (cMenuSearchResultsItem *)Get(Current()); if (item) { LOCK_TIMERS_WRITE; Timers->SetExplicitModify(); if (item->timerMatch == tmFull) { eTimerMatch tm = tmNone; cTimer *timer = Timers->GetMatch(item->event, &tm); if (timer) { if (EPGSearchConfig.useVDRTimerEditMenu) return AddSubMenu(new cMenuEditTimer(timer)); else return AddSubMenu(new cMenuMyEditTimer(timer, false, item->event)); } } cTimer *timer = new cTimer(item->event); PrepareTimerFile(item->event, timer); cTimer *t = Timers->GetTimer(timer); if (EPGSearchConfig.onePressTimerCreation == 0 || t || !item->event || (!t && item->event && item->event->StartTime() - (Setup.MarginStart + 2) * 60 < time(NULL))) { if (t) { delete timer; timer = t; } if (EPGSearchConfig.useVDRTimerEditMenu) return AddSubMenu(new cMenuEditTimer(timer, !t)); else return AddSubMenu(new cMenuMyEditTimer(timer, !t, item->event)); } else { string fullaux = ""; string aux = ""; if (item->event) { const cEvent* event = item->event; int bstart = event->StartTime() - timer->StartTime(); int bstop = timer->StopTime() - event->EndTime(); int checkmode = DefTimerCheckModes.GetMode(timer->Channel()); aux = UpdateAuxValue(aux, "channel", NumToString(timer->Channel()->Number()) + " - " + CHANNELNAME(timer->Channel())); aux = UpdateAuxValue(aux, "update", checkmode); aux = UpdateAuxValue(aux, "eventid", event->EventID()); aux = UpdateAuxValue(aux, "bstart", bstart); aux = UpdateAuxValue(aux, "bstop", bstop); fullaux = UpdateAuxValue(fullaux, "epgsearch", aux); } #ifdef USE_PINPLUGIN aux = ""; aux = UpdateAuxValue(aux, "protected", timer->FskProtection() ? "yes" : "no"); fullaux = UpdateAuxValue(fullaux, "pin-plugin", aux); #endif SetAux(timer, fullaux); if (Setup.SVDRPPeering && *Setup.SVDRPDefaultHost) timer->SetRemote(Setup.SVDRPDefaultHost); Timers->Add(timer); gl_timerStatusMonitor->SetConflictCheckAdvised(); timer->Matches(); Timers->SetModified(); if (!HandleRemoteTimerModifications(timer)) { ERROR(tr("Epgsearch: RemoteTimerModifications failed")); Timers->Del(timer); } if (HasSubMenu()) CloseSubMenu(); if (Update()) Display(); SetHelpKeys(); } } return osContinue; } eOSState cMenuSearchResults::Switch(void) { UpdateCurrent(); cMenuSearchResultsItem *item = (cMenuSearchResultsItem *)Get(Current()); if (item) { LOCK_CHANNELS_READ; const cChannel *channel = Channels->GetByChannelID(item->event->ChannelID(), true, true); if (channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true)) return osEnd; } INFO(trVDR("Can't switch channel!")); return osContinue; } eOSState cMenuSearchResults::Commands(eKeys Key, cSearchExt* SearchExt) { if (HasSubMenu() || Count() == 0) return osContinue; cMenuSearchResultsItem *mi = (cMenuSearchResultsItem *)Get(Current()); if (mi && mi->event) { cMenuSearchCommands *menu; eOSState state = AddSubMenu(menu = new cMenuSearchCommands(tr("EPG Commands"), mi->event, true, SearchExt)); if (Key != kNone) state = menu->ProcessKey(Key); return state; } return osContinue; } eOSState cMenuSearchResults::ShowSummary() { if (Count()) { const cEvent *ei = ((cMenuSearchResultsItem *)Get(Current()))->event; if (ei) { const cChannel *channel; { LOCK_CHANNELS_READ; channel = Channels->GetByChannelID(ei->ChannelID(), true, true); } if (channel) return AddSubMenu(new cMenuEventSearch(ei, eventObjects)); } } return osContinue; } eOSState cMenuSearchResults::OnRed(cSearchExt* searchExt) { eOSState state = osUnknown; if (HasSubMenu()) return Record(); if (Count()) { if (EPGSearchConfig.redkeymode == toggleKeys) state = Record(); else { cMenuSearchResultsItem* mi = (cMenuSearchResultsItem*)Get(Current()); if (mi) { if (mi->event) { state = AddSubMenu(new cMenuSearchCommands(tr("EPG Commands"), mi->event, false, searchExt)); } } } } return state; } eOSState cMenuSearchResults::OnGreen() { eOSState state = osUnknown; if (!HasSubMenu()) { m_bSort = !m_bSort; BuildList(); state = osContinue; } return state; } eOSState cMenuSearchResults::OnYellow() { eOSState state = osUnknown; if (!HasSubMenu()) { modeYellow = (modeYellow == showTitleEpisode ? showEpisode : showTitleEpisode); BuildList(); state = osContinue; } return state; } void cMenuSearchResults::UpdateCurrent() { cEventObj* cureventObj = eventObjects.GetCurrent(); if (cureventObj && cureventObj->Event()) for (cMenuSearchResultsItem* item = (cMenuSearchResultsItem*)First(); item; item = (cMenuSearchResultsItem*)Next(item)) if (item->Selectable() && item->event == cureventObj->Event()) { cureventObj->Select(false); SetCurrent(item); Display(); break; } } eOSState cMenuSearchResults::ProcessKey(eKeys Key) { bool HadSubMenu = HasSubMenu(); eOSState state = cOsdMenu::ProcessKey(Key); if (!HasSubMenu() && HadSubMenu) // navigation in summary could have changed current item, so update it UpdateCurrent(); if (state == osUnknown) { switch (Key) { case k0: if (!HasSubMenu()) { toggleKeys = 1 - toggleKeys; SetHelpKeys(true); } state = osContinue; break; case kGreen: state = OnGreen(); break; case kYellow: state = OnYellow(); break; case kOk: case kInfo: if (HasSubMenu()) { state = cOsdMenu::ProcessKey(Key); break; } if (Count()) state = ShowSummary(); else state = osBack; break; default: break; } } if (!HasSubMenu()) { if ((HadSubMenu || gl_TimerProgged) && Update()) { if (gl_TimerProgged) { // when using epgsearch's timer edit menu, update is delayed because of SVDRP gl_TimerProgged = 0; SetHelpKeys(); } Display(); } if (Key != kNone) SetHelpKeys(); if (gl_InfoConflict) { gl_InfoConflict = 0; if (Interface->Confirm(tr("Timer conflict! Show?"))) state = AddSubMenu(new cMenuConflictCheck()); } } return state; } // --- cMenuSearchResultsForSearch ------------------------------------------------------- cMenuSearchResultsForSearch::cMenuSearchResultsForSearch(cSearchExt* SearchExt, cMenuTemplate* MenuTemplate) : cMenuSearchResults(MenuTemplate) { ButtonBlue[0] = tr("Button$all channels"); ButtonBlue[1] = tr("Button$only FTA"); ButtonBlue[2] = tr("Button$Timer preview"); searchExt = SearchExt; m_bSort = true; if (searchExt) { modeBlue = searchExt->useChannel == 3 ? showNoPayTV : (EPGSearchConfig.ignorePayTV ? showNoPayTV : showAll); BuildList(); } } bool cMenuSearchResultsForSearch::BuildList() { bool hasResults = false; int current = Current(); Clear(); time_t now = time(NULL); cSearchResults* pSearchResults = searchExt->Run(modeBlue == showNoPayTV ? 1 : 0, false, 0, NULL, modeBlue != showTimerPreview); Clear(); eventObjects.Clear(); if (pSearchResults) { pSearchResults->SortBy(m_bSort ? CompareEventTime : CompareEventChannel); for (cSearchResult* pResultObj = pSearchResults->First(); pResultObj; pResultObj = pSearchResults->Next(pResultObj)) { if (ignoreRunning && now > pResultObj->event->StartTime()) continue; if (!(searchExt->useAsSearchTimer && searchExt->avoidRepeats && modeBlue == showTimerPreview)) pResultObj->needsTimer = false; hasResults = true; Add(new cMenuSearchResultsItem(pResultObj->event, modeYellow == showEpisode, pResultObj->needsTimer, menuTemplate)); eventObjects.Add(pResultObj->event); } delete pSearchResults; } if (Count()) SetCurrent(Get(0)); SetHelpKeys(true); cString szTitle = cString::sprintf("%d %s - %s", Count(), tr("Search results"), searchExt->search); SetTitle(szTitle); SetCurrent(Get(current)); Display(); return hasResults; } void cMenuSearchResultsForSearch::SetHelpKeys(bool Force) { cMenuSearchResultsItem *item = (cMenuSearchResultsItem *)Get(Current()); int NewHelpKeys = 0; if (item) { if (item->Selectable() && item->timerMatch == tmFull) NewHelpKeys = 2; else NewHelpKeys = 1; } bool hasTimer = (NewHelpKeys == 2); if (NewHelpKeys != helpKeys || Force) { ModeBlueSR nextModeBlue = (ModeBlueSR)(((int)modeBlue + 1) % 3); if (nextModeBlue == showTimerPreview && (searchExt->useAsSearchTimer == 0 || searchExt->avoidRepeats == 0)) nextModeBlue = (ModeBlueSR)(((int)nextModeBlue + 1) % 3); if (toggleKeys == 0) SetHelp((EPGSearchConfig.redkeymode == 0 ? (hasTimer ? trVDR("Button$Timer") : trVDR("Button$Record")) : tr("Button$Commands")), m_bSort ? tr("Button$by channel") : tr("Button$by time"), modeYellow == showTitleEpisode ? tr("Button$Episode") : tr("Button$Title"), ButtonBlue[(int)nextModeBlue]); else SetHelp((EPGSearchConfig.redkeymode == 1 ? (hasTimer ? trVDR("Button$Timer") : trVDR("Button$Record")) : tr("Button$Commands")), m_bSort ? tr("Button$by channel") : tr("Button$by time"), modeYellow == showTitleEpisode ? tr("Button$Episode") : tr("Button$Title"), ButtonBlue[(int)nextModeBlue]); helpKeys = NewHelpKeys; } } eOSState cMenuSearchResultsForSearch::ProcessKey(eKeys Key) { eOSState state = cMenuSearchResults::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kRecord: case kRed: state = OnRed(searchExt); break; case k1...k9: state = HasSubMenu() ? osContinue : Commands(Key, searchExt); break; case kBlue: if (HasSubMenu()) state = Switch(); else { modeBlue = (ModeBlueSR)(((int)modeBlue + 1) % 3); if (modeBlue == showTimerPreview && (!searchExt || (searchExt && (searchExt->useAsSearchTimer == 0 || searchExt->avoidRepeats == 0)))) modeBlue = (ModeBlueSR)(((int)modeBlue + 1) % 3); if (modeBlue == showTimerPreview) m_bSort = true; // show always sorted by channel BuildList(); state = osContinue; } break; default: break; } } return state; } // --- cMenuSearchResultsForBlacklist ------------------------------------------------------- cMenuSearchResultsForBlacklist::cMenuSearchResultsForBlacklist(cBlacklist* Blacklist) : cMenuSearchResults(cTemplFile::GetTemplateByName("MenuSearchResults")) { ButtonBlue[0] = tr("Button$all channels"); ButtonBlue[1] = tr("Button$only FTA"); ButtonBlue[2] = tr("Button$Timer preview"); blacklist = Blacklist; m_bSort = true; modeBlue = blacklist->useChannel == 3 ? showNoPayTV : (EPGSearchConfig.ignorePayTV ? showNoPayTV : showAll); BuildList(); } bool cMenuSearchResultsForBlacklist::BuildList() { int current = Current(); time_t now = time(NULL); cSearchResults* pSearchResults = blacklist->Run(); Clear(); eventObjects.Clear(); if (pSearchResults) { pSearchResults->SortBy(m_bSort ? CompareEventTime : CompareEventChannel); for (cSearchResult* pResultObj = pSearchResults->First(); pResultObj; pResultObj = pSearchResults->Next(pResultObj)) { if (ignoreRunning && now > pResultObj->event->StartTime()) continue; Add(new cMenuSearchResultsItem(pResultObj->event, modeYellow == showEpisode, false)); eventObjects.Add(pResultObj->event); } delete pSearchResults; } if (Count()) SetCurrent(Get(0)); SetHelpKeys(); cString szTitle = cString::sprintf("%d %s - %s", Count(), tr("Blacklist results"), blacklist->search); SetTitle(szTitle); SetCurrent(Get(current)); Display(); return true; } eOSState cMenuSearchResultsForBlacklist::ProcessKey(eKeys Key) { eOSState state = cMenuSearchResults::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case k1...k9: state = HasSubMenu() ? osContinue : Commands(Key); break; case kRecord: case kRed: state = OnRed(); break; case kBlue: if (HasSubMenu()) state = Switch(); else state = osContinue; break; default: break; } } return state; } void cMenuSearchResultsForBlacklist::SetHelpKeys(bool Force) { cMenuSearchResultsItem *item = (cMenuSearchResultsItem *)Get(Current()); int NewHelpKeys = 0; if (item) { if (item->Selectable() && item->timerMatch == tmFull) NewHelpKeys = 2; else NewHelpKeys = 1; } bool hasTimer = (NewHelpKeys == 2); if (NewHelpKeys != helpKeys || Force) { ModeBlueSR nextModeBlue = (ModeBlueSR)(((int)modeBlue + 1) % 3); if (nextModeBlue == showTimerPreview) nextModeBlue = (ModeBlueSR)(((int)nextModeBlue + 1) % 3); if (toggleKeys == 0) SetHelp((EPGSearchConfig.redkeymode == 0 ? (hasTimer ? trVDR("Button$Timer") : trVDR("Button$Record")) : tr("Button$Commands")), m_bSort ? tr("Button$by channel") : tr("Button$by time"), modeYellow == showTitleEpisode ? tr("Button$Episode") : tr("Button$Title"), NULL); else SetHelp((EPGSearchConfig.redkeymode == 1 ? (hasTimer ? trVDR("Button$Timer") : trVDR("Button$Record")) : tr("Button$Commands")), m_bSort ? tr("Button$by channel") : tr("Button$by time"), modeYellow == showTitleEpisode ? tr("Button$Episode") : tr("Button$Title"), NULL); helpKeys = NewHelpKeys; } } // --- cMenuSearchResultsForQuery ------------------------------------------------------- cMenuSearchResultsForQuery::cMenuSearchResultsForQuery(const char *query, bool IgnoreRunning) : cMenuSearchResultsForSearch(NULL, cTemplFile::GetTemplateByName("MenuSearchResults")) { modeBlue = EPGSearchConfig.ignorePayTV ? showNoPayTV : showAll; ignoreRunning = IgnoreRunning; // create a dummy search if (query) { searchExt = new cSearchExt; strcpy(searchExt->search, query); searchExt->mode = 0; // substring searchExt->useTitle = 1; searchExt->useSubtitle = 0; searchExt->useDescription = 0; searchExt->blacklistMode = blacklistsNone; BuildList(); } } cMenuSearchResultsForQuery::~cMenuSearchResultsForQuery() { delete searchExt; } bool cMenuSearchResultsForQuery::BuildList() { bool bRes = cMenuSearchResultsForSearch::BuildList(); /* if (!bRes) { char* szMessage = NULL; asprintf(&szMessage, tr("No results! Try again with tolerance %d?"), searchExt->mode == 5?searchExt->fuzzyTolerance+1:1); string sMessage = szMessage; free(szMessage); if (Interface->Confirm(sMessage.c_str())) { if (searchExt->mode == 5) // fuzzy searchExt->fuzzyTolerance++; searchExt->mode = 5; return BuildList(); } } */ return bRes; } // --- cMenuSearchResultsForRecs ------------------------------------------------------- cMenuSearchResultsForRecs::cMenuSearchResultsForRecs(const char *query) : cMenuSearchResultsForQuery(NULL) { SetMenuCategory(mcCommand); SetTitle(tr("found recordings")); if (query) { searchExt = new cSearchExt; strcpy(searchExt->search, query); searchExt->mode = 0; // substring searchExt->useTitle = 1; searchExt->useSubtitle = 0; searchExt->useDescription = 0; BuildList(); } } bool cMenuSearchResultsForRecs::BuildList() { const cRecording **pArray = NULL; int num = 0; int current = Current(); Clear(); { LOCK_RECORDINGS_READ; for (const cRecording *recording = Recordings->First(); recording; recording = Recordings->Next(recording)) { const cRecordingInfo *recInfo = recording->Info(); if (!recInfo) continue; string s1 = (recInfo && recInfo->Title()) ? recInfo->Title() : ""; string s2 = searchExt->search; 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) { const cRecording **tmp = (const cRecording **)realloc(pArray, (num + 1) * sizeof(cRecording *)); if (tmp) { pArray = tmp; pArray[num++] = recording; } } } qsort(pArray, num, sizeof(cRecording *), CompareRecording); for (int a = 0; a < num; a++) Add(new cMenuSearchResultsItem(pArray[a])); delete pArray; } // Recordinglock must be released before Display() is called SetHelp(NULL); SetCurrent(Get(current)); Display(); return true; } const cRecording *cMenuSearchResultsForRecs::GetRecording(cMenuSearchResultsItem *Item) { LOCK_RECORDINGS_READ; const cRecording *recording = Recordings->GetByName(Item->FileName()); if (!recording) ERROR(tr("Error while accessing recording!")); return recording; } eOSState cMenuSearchResultsForRecs::Play(void) { cMenuSearchResultsItem *ri = (cMenuSearchResultsItem*)Get(Current()); if (ri) { const cRecording *recording = GetRecording(ri); if (recording) { cReplayControl::SetRecording(recording->FileName()); return osReplay; } } return osContinue; } eOSState cMenuSearchResultsForRecs::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { if (Key == kOk) { if (Count() > 0) state = Play(); else state = osBack; } else state = osContinue; } return state; } // --- cMenuSearchResultsForList ------------------------------------------------------- cMenuSearchResultsForList::cMenuSearchResultsForList(cSearchResults& SearchResults, const char* Title, bool IgnoreRunning) : cMenuSearchResults(cTemplFile::GetTemplateByName("MenuSearchResults")) { ButtonBlue[0] = tr("Button$Setup"); searchResults = &SearchResults; m_bSort = true; ignoreRunning = IgnoreRunning; BuildList(); cString szTitle = cString::sprintf(Title, Count()); SetTitle(szTitle); } void cMenuSearchResultsForList::SetHelpKeys(bool Force) { cMenuSearchResultsItem *item = (cMenuSearchResultsItem *)Get(Current()); int NewHelpKeys = 0; if (item) { if (item->Selectable() && item->timerMatch == tmFull) NewHelpKeys = 2; else NewHelpKeys = 1; } bool hasTimer = (NewHelpKeys == 2); if (NewHelpKeys != helpKeys || Force) { if (toggleKeys == 0) SetHelp((EPGSearchConfig.redkeymode == 0 ? (hasTimer ? trVDR("Button$Timer") : trVDR("Button$Record")) : tr("Button$Commands")), m_bSort ? tr("Button$by channel") : tr("Button$by time"), modeYellow == showTitleEpisode ? tr("Button$Episode") : tr("Button$Title"), ButtonBlue[0]); else SetHelp((EPGSearchConfig.redkeymode == 1 ? (hasTimer ? trVDR("Button$Timer") : trVDR("Button$Record")) : tr("Button$Commands")), m_bSort ? tr("Button$by channel") : tr("Button$by time"), modeYellow == showTitleEpisode ? tr("Button$Episode") : tr("Button$Title"), ButtonBlue[0]); helpKeys = NewHelpKeys; } } bool cMenuSearchResultsForList::BuildList() { time_t now = time(NULL); int current = Current(); Clear(); eventObjects.Clear(); searchResults->SortBy(m_bSort ? CompareEventTime : CompareEventChannel); for (cSearchResult* pResultObj = searchResults->First(); pResultObj; pResultObj = searchResults->Next(pResultObj)) { if (ignoreRunning && now > pResultObj->event->StartTime()) continue; Add(new cMenuSearchResultsItem(pResultObj->event, modeYellow == showEpisode, false, menuTemplate, pResultObj->search)); eventObjects.Add(pResultObj->event); } if (Count()) SetCurrent(Get(0)); SetHelpKeys(); SetCurrent(Get(current)); Display(); return true; } eOSState cMenuSearchResultsForList::ProcessKey(eKeys Key) { eOSState state = cMenuSearchResults::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case k1...k9: state = HasSubMenu() ? osContinue : Commands(Key); break; case kRecord: case kRed: state = OnRed(); break; default: break; } } return state; }