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