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