EPGSearch: allow preconfiguring sources to be searched, improve setup skin
[enigma2-plugins.git] / epgsearch / src / EPGSearch.py
1 # for localized messages         
2 from enigma import eEPGCache, eServiceReference, RT_HALIGN_LEFT, \
3                 RT_HALIGN_RIGHT, RT_VALIGN_CENTER, eListboxPythonMultiContent
4
5 from Tools.LoadPixmap import LoadPixmap
6 from Tools.Directories import fileExists, resolveFilename, SCOPE_PLUGINS
7 from ServiceReference import ServiceReference
8
9 from EPGSearchSetup import EPGSearchSetup
10 from Screens.ChannelSelection import SimpleChannelSelection
11 from Screens.ChoiceBox import ChoiceBox
12 from Screens.EpgSelection import EPGSelection
13 from Screens.MessageBox import MessageBox
14 from Screens.Screen import Screen
15 from Plugins.SystemPlugins.Toolkit.NTIVirtualKeyBoard import NTIVirtualKeyBoard
16
17 from Components.ActionMap import ActionMap
18 from Components.Button import Button
19 from Components.config import config
20 from Components.EpgList import EPGList, EPG_TYPE_SINGLE, EPG_TYPE_MULTI
21 from Components.TimerList import TimerList
22 from Components.Sources.ServiceEvent import ServiceEvent
23 from Components.Sources.Event import Event
24
25 from . import SearchType
26 from time import localtime
27 from operator import itemgetter
28 from Tools.BoundFunction import boundFunction
29
30 # Partnerbox installed and icons in epglist enabled?
31 try:
32         from Plugins.Extensions.Partnerbox.PartnerboxEPGList import \
33                         isInRemoteTimer, getRemoteClockPixmap
34         from Plugins.Extensions.Partnerbox.plugin import \
35                         showPartnerboxIconsinEPGList
36         PartnerBoxIconsEnabled = showPartnerboxIconsinEPGList()
37 except ImportError:
38         PartnerBoxIconsEnabled = False
39
40 # AutoTimer installed?
41 try:
42         from Plugins.Extensions.AutoTimer.AutoTimerEditor import \
43                         addAutotimerFromEvent, addAutotimerFromSearchString
44         autoTimerAvailable = True
45 except ImportError:
46         autoTimerAvailable = False
47
48 # Overwrite EPGSelection.__init__ with our modified one
49 baseEPGSelection__init__ = None
50 def EPGSelectionInit():
51         global baseEPGSelection__init__
52         if baseEPGSelection__init__ is None:
53                 baseEPGSelection__init__ = EPGSelection.__init__
54         EPGSelection.__init__ = EPGSelection__init__
55
56 # Modified EPGSelection __init__
57 def EPGSelection__init__(self, session, service, zapFunc=None, eventid=None, bouquetChangeCB=None, serviceChangeCB=None):
58         baseEPGSelection__init__(self, session, service, zapFunc, eventid, bouquetChangeCB, serviceChangeCB)
59         if self.type != EPG_TYPE_MULTI and config.plugins.epgsearch.add_search_to_epg.value:
60                 def bluePressed():
61                         cur = self["list"].getCurrent()
62                         if cur[0] is not None:
63                                 name = cur[0].getEventName()
64                         else:
65                                 name = ''
66                         self.session.open(EPGSearch, name, False)
67
68                 self["epgsearch_actions"] = ActionMap(["EPGSelectActions"],
69                                 {
70                                         "blue": bluePressed,
71                                 })
72                 self["key_blue"].text = _("Search")
73
74 # Modified EPGSearchList with support for PartnerBox
75 class EPGSearchList(EPGList):
76         def __init__(self, type=EPG_TYPE_SINGLE, selChangedCB=None, timer=None):
77                 EPGList.__init__(self, type, selChangedCB, timer)
78                 self.l.setBuildFunc(self.buildEPGSearchEntry)
79
80                 if PartnerBoxIconsEnabled:
81                         # Partnerbox Clock Icons
82                         self.remote_clock_pixmap = LoadPixmap('/usr/lib/enigma2/python/Plugins/Extensions/Partnerbox/icons/remote_epgclock.png')
83                         self.remote_clock_add_pixmap = LoadPixmap('/usr/lib/enigma2/python/Plugins/Extensions/Partnerbox/icons/remote_epgclock_add.png')
84                         self.remote_clock_pre_pixmap = LoadPixmap('/usr/lib/enigma2/python/Plugins/Extensions/Partnerbox/icons/remote_epgclock_pre.png')
85                         self.remote_clock_post_pixmap = LoadPixmap('/usr/lib/enigma2/python/Plugins/Extensions/Partnerbox/icons/remote_epgclock_post.png')
86                         self.remote_clock_prepost_pixmap = LoadPixmap('/usr/lib/enigma2/python/Plugins/Extensions/Partnerbox/icons/remote_epgclock_prepost.png')
87
88         def buildEPGSearchEntry(self, service, eventId, beginTime, duration, EventName):
89                 rec1 = beginTime and self.timer.isInTimer(eventId, beginTime, duration, service)
90                 # Partnerbox 
91                 if PartnerBoxIconsEnabled:
92                         rec2 = beginTime and isInRemoteTimer(self,beginTime, duration, service)
93                 else:
94                         rec2 = False
95                 r1 = self.weekday_rect
96                 r2 = self.datetime_rect
97                 r3 = self.descr_rect
98                 t = localtime(beginTime)
99                 serviceref = ServiceReference(service) # for Servicename
100                 res = [
101                         None, # no private data needed
102                         (eListboxPythonMultiContent.TYPE_TEXT, r1.left(), r1.top(), r1.width(), r1.height(), 0, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, self.days[t[6]]),
103                         (eListboxPythonMultiContent.TYPE_TEXT, r2.left(), r2.top(), r2.width(), r1.height(), 0, RT_HALIGN_RIGHT|RT_VALIGN_CENTER, "%02d.%02d, %02d:%02d"%(t[2],t[1],t[3],t[4]))
104                 ]
105                 if rec1 or rec2:
106                         if rec1:                        
107                                 clock_pic = self.getClockPixmap(service, beginTime, duration, eventId)
108                                 # maybe Partnerbox too
109                                 if rec2:
110                                         clock_pic_partnerbox = getRemoteClockPixmap(self,service, beginTime, duration, eventId)
111                         else:
112                                 clock_pic = getRemoteClockPixmap(self,service, beginTime, duration, eventId)
113                         if rec1 and rec2:
114                                 # Partnerbox and local
115                                 res.extend((
116                                         (eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, r3.left(), 4, 21, 21, clock_pic),
117                                         (eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, r3.left() + 30, 4, 21, 21, clock_pic_partnerbox),
118                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.left() + 60, r3.top(), r3.width(), r3.height(), 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, serviceref.getServiceName() + ": " + EventName)))
119                         else:
120                                 res.extend((
121                                         (eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, r3.left(), 4, 21, 21, clock_pic),
122                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.left() + 30, r3.top(), r3.width(), r3.height(), 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, serviceref.getServiceName() + ": " + EventName)))
123                 else:
124                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r3.left(), r3.top(), r3.width(), r3.height(), 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, serviceref.getServiceName() + ": " + EventName))
125                 return res
126
127 # main class of plugin
128 class EPGSearch(EPGSelection):
129         def __init__(self, session, *args):
130                 Screen.__init__(self, session)
131                 self.skinName = ["EPGSearch", "EPGSelection"]
132
133                 self.searchargs = args
134                 self.currSearch = ""
135
136                 # XXX: we lose sort begin/end here
137                 self["key_yellow"] = Button(_("New Search"))
138                 self["key_blue"] = Button(_("History"))
139
140 # begin stripped copy of EPGSelection.__init__
141                 self.bouquetChangeCB = None
142                 self.serviceChangeCB = None
143                 self.ask_time = -1 #now
144                 self["key_red"] = Button("")
145                 self.closeRecursive = False
146                 self.saved_title = None
147                 self["Service"] = ServiceEvent()
148                 self["Event"] = Event()
149                 self.type = EPG_TYPE_SINGLE
150                 self.currentService=None
151                 self.zapFunc = None
152                 self.sort_type = 0
153                 self["key_green"] = Button(_("Add timer"))
154                 self.key_green_choice = self.ADD_TIMER
155                 self.key_red_choice = self.EMPTY
156                 self["list"] = EPGSearchList(type = self.type, selChangedCB = self.onSelectionChanged, timer = session.nav.RecordTimer)
157                 self["actions"] = ActionMap(["EPGSelectActions", "OkCancelActions", "MenuActions"],
158                         {
159                                 "menu": self.menu,
160                                 "cancel": self.closeScreen,
161                                 "ok": self.eventSelected,
162                                 "timerAdd": self.timerAdd,
163                                 "yellow": self.yellowButtonPressed,
164                                 "blue": self.blueButtonPressed,
165                                 "info": self.infoKeyPressed,
166                                 "red": self.zapTo, # needed --> Partnerbox
167                                 "nextBouquet": self.nextBouquet, # just used in multi epg yet
168                                 "prevBouquet": self.prevBouquet, # just used in multi epg yet
169                                 "nextService": self.nextService, # just used in single epg yet
170                                 "prevService": self.prevService, # just used in single epg yet
171                         })
172
173                 self["actions"].csel = self
174                 self.onLayoutFinish.append(self.onCreate)
175 # end stripped copy of EPGSelection.__init__
176
177                 # Partnerbox
178                 if PartnerBoxIconsEnabled:
179                         EPGSelection.PartnerboxInit(self, False)
180
181                 # Hook up actions for yttrailer if installed
182                 try:
183                         from Plugins.Extensions.YTTrailer.plugin import baseEPGSelection__init__
184                 except ImportError as ie:
185                         pass
186                 else:
187                         if baseEPGSelection__init__ is not None:
188                                 self["trailerActions"] = ActionMap(["InfobarActions", "InfobarTeletextActions"],
189                                 {
190                                         "showTv": self.showTrailer,
191                                         "showRadio": self.showTrailerList,
192                                         "startTeletext": self.showConfig
193                                 })
194
195         def onCreate(self):
196                 self.setTitle(_("EPG Search"))
197
198                 if self.searchargs:
199                         self.doSearchEPG(*self.searchargs)
200                 else:
201                         l = self["list"]
202                         l.recalcEntrySize()
203                         l.list = []
204                         l.l.setList(l.list)
205                 del self.searchargs
206
207                 # Partnerbox
208                 if PartnerBoxIconsEnabled:
209                         EPGSelection.GetPartnerboxTimerlist(self)
210
211         def closeScreen(self):
212                 # Save our history
213                 config.plugins.epgsearch.save()
214                 EPGSelection.closeScreen(self)
215
216         def yellowButtonPressed(self):
217                 self.session.openWithCallback(
218                         self.searchEPG,
219                         NTIVirtualKeyBoard,
220                         title = _("Enter text to search for")
221                 )
222
223         def menu(self):
224                 options = [
225                         (_("Import from Timer"), self.importFromTimer),
226                         (_("Import from EPG"), self.importFromEPG),
227                 ]
228
229                 if autoTimerAvailable:
230                         options.extend((
231                                 (_("Import from AutoTimer"), self.importFromAutoTimer),
232                                 (_("Save search as AutoTimer"), self.addAutoTimer),
233                                 (_("Export selected as AutoTimer"), self.exportAutoTimer),
234                         ))
235                 if fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/IMDb/plugin.py")):
236                         options.append((_("Open selected in IMDb"), self.openImdb))
237                 options.append(
238                                 (_("Setup"), self.setup)
239                 )
240
241                 self.session.openWithCallback(
242                         self.menuCallback,
243                         ChoiceBox,
244                         list = options
245                 )
246
247         def menuCallback(self, ret):
248                 ret and ret[1]()
249
250         def importFromTimer(self):
251                 self.session.openWithCallback(
252                         self.searchEPG,
253                         EPGSearchTimerImport
254                 )
255
256         def importFromEPG(self):
257                 self.session.openWithCallback(
258                         self.searchEPG,
259                         EPGSearchChannelSelection
260                 )
261
262         def importFromAutoTimer(self):
263                 removeInstance = False
264                 try:
265                         # Import Instance
266                         from Plugins.Extensions.AutoTimer.plugin import autotimer
267
268                         if autotimer is None:
269                                 removeInstance = True
270                                 # Create an instance
271                                 from Plugins.Extensions.AutoTimer.AutoTimer import AutoTimer
272                                 autotimer = AutoTimer()
273
274                         # Read in configuration
275                         autotimer.readXml()
276                 except Exception as e:
277                         self.session.open(
278                                 MessageBox,
279                                 _("Could not read AutoTimer timer list: %s") % e,
280                                 type = MessageBox.TYPE_ERROR
281                         )
282                 else:
283                         # Fetch match strings
284                         # XXX: we could use the timer title as description
285                         options = [(x.match, x.match) for x in autotimer.getTimerList()]
286
287                         self.session.openWithCallback(
288                                 self.searchEPGWrapper,
289                                 ChoiceBox,
290                                 title = _("Select text to search for"),
291                                 list = options
292                         )
293                 finally:
294                         # Remove instance if there wasn't one before
295                         if removeInstance:
296                                 autotimer = None
297
298         def addAutoTimer(self):
299                 addAutotimerFromSearchString(self.session, self.currSearch)
300
301         def exportAutoTimer(self):
302                 cur = self['list'].getCurrent()
303                 if cur is None:
304                         return
305                 addAutotimerFromEvent(self.session, cur[0], cur[1])
306
307         def openImdb(self):
308                 cur = self['list'].getCurrent()
309                 if cur is None:
310                         return
311                 try:
312                         from Plugins.Extensions.IMDb.plugin import IMDB
313                         self.session.open(IMDB, cur[0].getEventName())
314                 except ImportError as ie:
315                         pass
316
317         def setup(self):
318                 self.session.open(EPGSearchSetup)
319
320         def blueButtonPressed(self):
321                 options = [(x, x) for x in config.plugins.epgsearch.history.value]
322
323                 if options:
324                         self.session.openWithCallback(
325                                 self.searchEPGWrapper,
326                                 ChoiceBox,
327                                 title = _("Select text to search for"),
328                                 list = options
329                         )
330                 else:
331                         self.session.open(
332                                 MessageBox,
333                                 _("No history"),
334                                 type = MessageBox.TYPE_INFO
335                         )
336
337         def searchEPGWrapper(self, ret):
338                 if ret:
339                         self.searchEPG(ret[1])
340
341         def searchEPG(self, searchString = None, searchSave = True):
342                 if not searchString:
343                         return
344                 searchType = config.plugins.epgsearch.search_type.value
345                 if  searchType == SearchType.ASK:
346                         boundCallback = boundFunction(self.onSearchEPGCallback, searchString=searchString, searchSave=searchSave)
347                         choices = [ (_("Title only"), False),
348                                                 (_("Title and Description"), True) ]
349                         self.session.openWithCallback(boundCallback, ChoiceBox, list=choices, title=_("Where to search for '%s'?") %(searchString), windowTitle=_("EPG Search"))
350                 else:
351                         searchDescription = config.plugins.epgsearch.search_type.value == SearchType.TITLE_DESCRIPTION
352                         self.doSearchEPG(searchString, searchSave, searchDescription)
353
354         def onSearchEPGCallback(self, answer, searchString=None, searchSave=True):
355                 searchDescription = answer and answer[1]
356                 self.doSearchEPG(searchString, searchSave, searchDescription)
357
358         def doSearchEPG(self, searchString = None, searchSave = True, searchDescription=False):
359                 if searchString:
360                         self.currSearch = searchString
361                         if searchSave:
362                                 # Maintain history
363                                 history = config.plugins.epgsearch.history.value
364                                 if searchString not in history:
365                                         history.insert(0, searchString)
366                                         maxLen = config.plugins.epgsearch.history_length.value
367                                         if len(history) > maxLen:
368                                                 del history[maxLen:]
369                                 else:
370                                         history.remove(searchString)
371                                         history.insert(0, searchString)
372
373                         # Search EPG, default to empty list
374                         epgcache = eEPGCache.getInstance() # XXX: the EPGList also keeps an instance of the cache but we better make sure that we get what we want :-)
375                         ret = epgcache.search(('RIBDT', 1000, eEPGCache.PARTIAL_TITLE_SEARCH, searchString, eEPGCache.NO_CASE_CHECK)) or []
376                         if searchDescription:
377                                 ret += epgcache.search(('RIBDT', 1000, eEPGCache.PARTIAL_DESCRIPTION_SEARCH, searchString, eEPGCache.NO_CASE_CHECK)) or []
378                                 #condense by eventids
379                                 condensed = {}
380                                 for item in ret:
381                                         condensed[item[1]] = item
382                                 ret = condensed.values()
383                         ret.sort(key=itemgetter(2)) # sort by time
384
385                         # Update List
386                         l = self["list"]
387                         l.recalcEntrySize()
388                         l.list = ret
389                         l.l.setList(ret)
390
391 class EPGSearchTimerImport(Screen):
392         def __init__(self, session):
393                 Screen.__init__(self, session)
394                 self.skinName = ["EPGSearchTimerImport", "TimerEditList"]
395
396                 self.list = []
397                 self.fillTimerList()
398
399                 self["timerlist"] = TimerList(self.list)
400
401                 self["key_red"] = Button(_("Cancel"))
402                 self["key_green"] = Button(_("OK"))
403                 self["key_yellow"] = Button("")
404                 self["key_blue"] = Button("")
405
406                 self["actions"] = ActionMap(["OkCancelActions", "ColorActions"],
407                 {
408                         "ok": self.search,
409                         "cancel": self.cancel,
410                         "green": self.search,
411                         "red": self.cancel
412                 }, -1)
413                 self.onLayoutFinish.append(self.setCustomTitle)
414
415         def setCustomTitle(self):
416                 self.setTitle(_("Select a timer to search"))
417
418         def fillTimerList(self):
419                 l = self.list
420                 del l[:]
421
422                 for timer in self.session.nav.RecordTimer.timer_list:
423                         l.append((timer, False))
424
425                 for timer in self.session.nav.RecordTimer.processed_timers:
426                         l.append((timer, True))
427                 l.sort(key = lambda x: x[0].begin)
428
429         def search(self):
430                 cur = self["timerlist"].getCurrent()
431                 if cur:
432                         self.close(cur.name)
433
434         def cancel(self):
435                 self.close(None)
436
437 class EPGSearchChannelSelection(SimpleChannelSelection):
438         def __init__(self, session):
439                 SimpleChannelSelection.__init__(self, session, _("Channel Selection"))
440                 self.skinName = ["EPGSearchChannelSelection", "SimpleChannelSelection"]
441
442                 self["ChannelSelectEPGActions"] = ActionMap(["ChannelSelectEPGActions"],
443                 {
444                                 "showEPGList": self.channelSelected
445                 })
446
447         def channelSelected(self):
448                 ref = self.getCurrentSelection()
449                 if (ref.flags & 7) == 7:
450                         self.enterPath(ref)
451                 elif not (ref.flags & eServiceReference.isMarker):
452                         self.session.openWithCallback(
453                                 self.epgClosed,
454                                 EPGSearchEPGSelection,
455                                 ref,
456                                 False
457                         )
458
459         def epgClosed(self, ret = None):
460                 if ret:
461                         self.close(ret)
462
463 class EPGSearchEPGSelection(EPGSelection):
464         def __init__(self, session, ref, openPlugin):
465                 EPGSelection.__init__(self, session, ref)
466                 self.skinName = ["EPGSearchEPGSelection", "EPGSelection"]
467                 self["key_green"].text = _("Search")
468                 self.openPlugin = openPlugin
469
470         def infoKeyPressed(self):
471                 self.timerAdd()
472
473         def timerAdd(self):
474                 cur = self["list"].getCurrent()
475                 evt = cur[0]
476                 sref = cur[1]
477                 if not evt:
478                         return
479
480                 if self.openPlugin:
481                         self.session.open(
482                                 EPGSearch,
483                                 evt.getEventName()
484                         )
485                 else:
486                         self.close(evt.getEventName())
487