remove shared copies of NTIVirtualKeyBoard
[enigma2-plugins.git] / epgsearch / src / EPGSearch.py
1 # for localized messages         
2 from . import _
3
4 from enigma import eEPGCache, eServiceReference, RT_HALIGN_LEFT, \
5                 RT_HALIGN_RIGHT, eListboxPythonMultiContent
6
7 from Tools.LoadPixmap import LoadPixmap
8 from Tools.Directories import fileExists, resolveFilename, SCOPE_PLUGINS
9 from ServiceReference import ServiceReference
10
11 from EPGSearchSetup import EPGSearchSetup
12 from Screens.ChannelSelection import SimpleChannelSelection
13 from Screens.ChoiceBox import ChoiceBox
14 from Screens.EpgSelection import EPGSelection
15 from Screens.MessageBox import MessageBox
16 from Screens.Screen import Screen
17 from Plugins.SystemPlugins.Toolkit.NTIVirtualKeyBoard import NTIVirtualKeyBoard
18
19 from Components.ActionMap import ActionMap
20 from Components.Button import Button
21 from Components.config import config
22 from Components.EpgList import EPGList, EPG_TYPE_SINGLE, EPG_TYPE_MULTI
23 from Components.TimerList import TimerList
24 from Components.Sources.ServiceEvent import ServiceEvent
25 from Components.Sources.Event import Event
26
27 from time import localtime
28 from operator import itemgetter
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, self.days[t[6]]),
103                         (eListboxPythonMultiContent.TYPE_TEXT, r2.left(), r2.top(), r2.width(), r1.height(), 0, RT_HALIGN_RIGHT, "%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(), r3.top(), 21, 21, clock_pic),
117                                         (eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, r3.left() + 25, r3.top(), 21, 21, clock_pic_partnerbox),
118                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.left() + 50, r3.top(), r3.width(), r3.height(), 0, RT_HALIGN_LEFT, serviceref.getServiceName() + ": " + EventName)))
119                         else:
120                                 res.extend((
121                                         (eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST, r3.left(), r3.top(), 21, 21, clock_pic),
122                                         (eListboxPythonMultiContent.TYPE_TEXT, r3.left() + 25, r3.top(), r3.width(), r3.height(), 0, RT_HALIGN_LEFT, serviceref.getServiceName() + ": " + EventName)))
123                 else:
124                         res.append((eListboxPythonMultiContent.TYPE_TEXT, r3.left(), r3.top(), r3.width(), r3.height(), 0, RT_HALIGN_LEFT, 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.searchEPG(*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 searchString:
343                         self.currSearch = searchString
344                         if searchSave:
345                                 # Maintain history
346                                 history = config.plugins.epgsearch.history.value
347                                 if searchString not in history:
348                                         history.insert(0, searchString)
349                                         maxLen = config.plugins.epgsearch.history_length.value
350                                         if len(history) > maxLen:
351                                                 del history[maxLen:]
352                                 else:
353                                         history.remove(searchString)
354                                         history.insert(0, searchString)
355
356                         # Workaround to allow search for umlauts if we know the encoding (pretty bad, I know...)
357                         encoding = config.plugins.epgsearch.encoding.value
358                         if encoding != 'UTF-8':
359                                 try:
360                                         searchString = searchString.decode('UTF-8', 'replace').encode(encoding, 'replace')
361                                 except (UnicodeDecodeError, UnicodeEncodeError):
362                                         pass
363
364                         # Search EPG, default to empty list
365                         epgcache = eEPGCache.getInstance() # XXX: the EPGList also keeps an instance of the cache but we better make sure that we get what we want :-)
366                         ret = epgcache.search(('RIBDT', 1000, eEPGCache.PARTIAL_TITLE_SEARCH, searchString, eEPGCache.NO_CASE_CHECK)) or []
367                         ret.sort(key=itemgetter(2)) # sort by time
368
369                         # Update List
370                         l = self["list"]
371                         l.recalcEntrySize()
372                         l.list = ret
373                         l.l.setList(ret)
374
375 class EPGSearchTimerImport(Screen):
376         def __init__(self, session):
377                 Screen.__init__(self, session)
378                 self.skinName = ["EPGSearchTimerImport", "TimerEditList"]
379
380                 self.list = []
381                 self.fillTimerList()
382
383                 self["timerlist"] = TimerList(self.list)
384
385                 self["key_red"] = Button(_("Cancel"))
386                 self["key_green"] = Button(_("OK"))
387                 self["key_yellow"] = Button("")
388                 self["key_blue"] = Button("")
389
390                 self["actions"] = ActionMap(["OkCancelActions", "ColorActions"],
391                 {
392                         "ok": self.search,
393                         "cancel": self.cancel,
394                         "green": self.search,
395                         "red": self.cancel
396                 }, -1)
397                 self.onLayoutFinish.append(self.setCustomTitle)
398
399         def setCustomTitle(self):
400                 self.setTitle(_("Select a timer to search"))
401
402         def fillTimerList(self):
403                 l = self.list
404                 del l[:]
405
406                 for timer in self.session.nav.RecordTimer.timer_list:
407                         l.append((timer, False))
408
409                 for timer in self.session.nav.RecordTimer.processed_timers:
410                         l.append((timer, True))
411                 l.sort(key = lambda x: x[0].begin)
412
413         def search(self):
414                 cur = self["timerlist"].getCurrent()
415                 if cur:
416                         self.close(cur.name)
417
418         def cancel(self):
419                 self.close(None)
420
421 class EPGSearchChannelSelection(SimpleChannelSelection):
422         def __init__(self, session):
423                 SimpleChannelSelection.__init__(self, session, _("Channel Selection"))
424                 self.skinName = ["EPGSearchChannelSelection", "SimpleChannelSelection"]
425
426                 self["ChannelSelectEPGActions"] = ActionMap(["ChannelSelectEPGActions"],
427                 {
428                                 "showEPGList": self.channelSelected
429                 })
430
431         def channelSelected(self):
432                 ref = self.getCurrentSelection()
433                 if (ref.flags & 7) == 7:
434                         self.enterPath(ref)
435                 elif not (ref.flags & eServiceReference.isMarker):
436                         self.session.openWithCallback(
437                                 self.epgClosed,
438                                 EPGSearchEPGSelection,
439                                 ref,
440                                 False
441                         )
442
443         def epgClosed(self, ret = None):
444                 if ret:
445                         self.close(ret)
446
447 class EPGSearchEPGSelection(EPGSelection):
448         def __init__(self, session, ref, openPlugin):
449                 EPGSelection.__init__(self, session, ref)
450                 self.skinName = ["EPGSearchEPGSelection", "EPGSelection"]
451                 self["key_green"].text = _("Search")
452                 self.openPlugin = openPlugin
453
454         def infoKeyPressed(self):
455                 self.timerAdd()
456
457         def timerAdd(self):
458                 cur = self["list"].getCurrent()
459                 evt = cur[0]
460                 sref = cur[1]
461                 if not evt:
462                         return
463
464                 if self.openPlugin:
465                         self.session.open(
466                                 EPGSearch,
467                                 evt.getEventName()
468                         )
469                 else:
470                         self.close(evt.getEventName())
471