make servicelist accessible in plugins
[vuplus_dvbapp] / lib / python / Screens / InfoBarGenerics.py
1 from ChannelSelection import ChannelSelection, BouquetSelector
2
3 from Components.ActionMap import ActionMap, HelpableActionMap
4 from Components.ActionMap import NumberActionMap
5 from Components.BlinkingPixmap import BlinkingPixmapConditional
6 from Components.Harddisk import harddiskmanager
7 from Components.Input import Input
8 from Components.Label import Label
9 from Components.Pixmap import Pixmap
10 from Components.PluginComponent import plugins
11 from Components.ServiceEventTracker import ServiceEventTracker
12 from Components.Sources.CurrentService import CurrentService
13 from Components.Sources.EventInfo import EventInfo
14 from Components.Sources.FrontendStatus import FrontendStatus
15 from Components.Sources.Boolean import Boolean
16 from Components.Sources.Clock import Clock
17 from Components.TimerList import TimerEntryComponent
18 from Components.config import config, ConfigBoolean, ConfigClock
19 from EpgSelection import EPGSelection
20 from Plugins.Plugin import PluginDescriptor
21
22 from Screen import Screen
23 from Screens.ChoiceBox import ChoiceBox
24 from Screens.Dish import Dish
25 from Screens.EventView import EventViewEPGSelect, EventViewSimple
26 from Screens.InputBox import InputBox
27 from Screens.MessageBox import MessageBox
28 from Screens.MinuteInput import MinuteInput
29 from Screens.TimerSelection import TimerSelection
30 from Screens.PictureInPicture import PictureInPicture
31 from Screens.SubtitleDisplay import SubtitleDisplay
32 from Screens.RdsDisplay import RdsInfoDisplay, RassInteractive
33 from Screens.SleepTimerEdit import SleepTimerEdit
34 from Screens.TimeDateInput import TimeDateInput
35 from ServiceReference import ServiceReference
36
37 from Tools import Notifications
38 from Tools.Directories import SCOPE_HDD, resolveFilename
39
40 from enigma import eTimer, eServiceCenter, eDVBServicePMTHandler, iServiceInformation, \
41         iPlayableService, eServiceReference, eDVBResourceManager, iFrontendInformation, eEPGCache
42
43 from time import time, localtime, strftime
44 from os import stat as os_stat
45 from bisect import insort
46
47 # hack alert!
48 from Menu import MainMenu, mdom
49
50 class InfoBarDish:
51         def __init__(self):
52                 self.dishDialog = self.session.instantiateDialog(Dish)
53                 self.onLayoutFinish.append(self.dishDialog.show)
54
55 class InfoBarShowHide:
56         """ InfoBar show/hide control, accepts toggleShow and hide actions, might start
57         fancy animations. """
58         STATE_HIDDEN = 0
59         STATE_HIDING = 1
60         STATE_SHOWING = 2
61         STATE_SHOWN = 3
62
63         def __init__(self):
64                 self["ShowHideActions"] = ActionMap( ["InfobarShowHideActions"] ,
65                         {
66                                 "toggleShow": self.toggleShow,
67                                 "hide": self.hide,
68                         }, 1) # lower prio to make it possible to override ok and cancel..
69
70                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
71                         {
72                                 iPlayableService.evStart: self.serviceStarted,
73                         })
74
75                 self.__state = self.STATE_SHOWN
76                 self.__locked = 0
77
78                 self.hideTimer = eTimer()
79                 self.hideTimer.timeout.get().append(self.doTimerHide)
80                 self.hideTimer.start(5000, True)
81
82                 self.onShow.append(self.__onShow)
83                 self.onHide.append(self.__onHide)
84
85         def serviceStarted(self):
86                 if self.execing:
87                         if config.usage.show_infobar_on_zap.value:
88                                 self.doShow()
89
90         def __onShow(self):
91                 self.__state = self.STATE_SHOWN
92                 self.startHideTimer()
93
94         def startHideTimer(self):
95                 if self.__state == self.STATE_SHOWN and not self.__locked:
96                         idx = config.usage.infobar_timeout.index
97                         if idx:
98                                 self.hideTimer.start(idx*1000, True)
99
100         def __onHide(self):
101                 self.__state = self.STATE_HIDDEN
102
103         def doShow(self):
104                 self.show()
105                 self.startHideTimer()
106
107         def doTimerHide(self):
108                 self.hideTimer.stop()
109                 if self.__state == self.STATE_SHOWN:
110                         self.hide()
111
112         def toggleShow(self):
113                 if self.__state == self.STATE_SHOWN:
114                         self.hide()
115                         self.hideTimer.stop()
116                 elif self.__state == self.STATE_HIDDEN:
117                         self.show()
118
119         def lockShow(self):
120                 self.__locked = self.__locked + 1
121                 if self.execing:
122                         self.show()
123                         self.hideTimer.stop()
124
125         def unlockShow(self):
126                 self.__locked = self.__locked - 1
127                 if self.execing:
128                         self.startHideTimer()
129
130 #       def startShow(self):
131 #               self.instance.m_animation.startMoveAnimation(ePoint(0, 600), ePoint(0, 380), 100)
132 #               self.__state = self.STATE_SHOWN
133 #
134 #       def startHide(self):
135 #               self.instance.m_animation.startMoveAnimation(ePoint(0, 380), ePoint(0, 600), 100)
136 #               self.__state = self.STATE_HIDDEN
137
138 class NumberZap(Screen):
139         def quit(self):
140                 self.Timer.stop()
141                 self.close(0)
142
143         def keyOK(self):
144                 self.Timer.stop()
145                 self.close(int(self["number"].getText()))
146
147         def keyNumberGlobal(self, number):
148                 self.Timer.start(3000, True)            #reset timer
149                 self.field = self.field + str(number)
150                 self["number"].setText(self.field)
151                 if len(self.field) >= 4:
152                         self.keyOK()
153
154         def __init__(self, session, number):
155                 Screen.__init__(self, session)
156                 self.field = str(number)
157
158                 self["channel"] = Label(_("Channel:"))
159
160                 self["number"] = Label(self.field)
161
162                 self["actions"] = NumberActionMap( [ "SetupActions" ], 
163                         {
164                                 "cancel": self.quit,
165                                 "ok": self.keyOK,
166                                 "1": self.keyNumberGlobal,
167                                 "2": self.keyNumberGlobal,
168                                 "3": self.keyNumberGlobal,
169                                 "4": self.keyNumberGlobal,
170                                 "5": self.keyNumberGlobal,
171                                 "6": self.keyNumberGlobal,
172                                 "7": self.keyNumberGlobal,
173                                 "8": self.keyNumberGlobal,
174                                 "9": self.keyNumberGlobal,
175                                 "0": self.keyNumberGlobal
176                         })
177
178                 self.Timer = eTimer()
179                 self.Timer.timeout.get().append(self.keyOK)
180                 self.Timer.start(3000, True)
181
182 class InfoBarNumberZap:
183         """ Handles an initial number for NumberZapping """
184         def __init__(self):
185                 self["NumberActions"] = NumberActionMap( [ "NumberActions"],
186                         {
187                                 "1": self.keyNumberGlobal,
188                                 "2": self.keyNumberGlobal,
189                                 "3": self.keyNumberGlobal,
190                                 "4": self.keyNumberGlobal,
191                                 "5": self.keyNumberGlobal,
192                                 "6": self.keyNumberGlobal,
193                                 "7": self.keyNumberGlobal,
194                                 "8": self.keyNumberGlobal,
195                                 "9": self.keyNumberGlobal,
196                                 "0": self.keyNumberGlobal,
197                         })
198
199         def keyNumberGlobal(self, number):
200 #               print "You pressed number " + str(number)
201                 if number == 0:
202                         self.servicelist.recallPrevService()
203                 else:
204                         self.session.openWithCallback(self.numberEntered, NumberZap, number)
205
206         def numberEntered(self, retval):
207 #               print self.servicelist
208                 if retval > 0:
209                         self.zapToNumber(retval)
210
211         def searchNumberHelper(self, serviceHandler, num, bouquet):
212                 servicelist = serviceHandler.list(bouquet)
213                 if not servicelist is None:
214                         while num:
215                                 serviceIterator = servicelist.getNext()
216                                 if not serviceIterator.valid(): #check end of list
217                                         break
218                                 playable = not (serviceIterator.flags & (eServiceReference.isMarker|eServiceReference.isDirectory))
219                                 if playable:
220                                         num -= 1;
221                         if not num: #found service with searched number ?
222                                 return serviceIterator, 0
223                 return None, num
224
225         def zapToNumber(self, number):
226                 bouquet = self.servicelist.bouquet_root
227                 service = None
228                 serviceHandler = eServiceCenter.getInstance()
229                 if not config.usage.multibouquet.value:
230                         service, number = self.searchNumberHelper(serviceHandler, number, bouquet)
231                 else:
232                         bouquetlist = serviceHandler.list(bouquet)
233                         if not bouquetlist is None:
234                                 while number:
235                                         bouquet = bouquetlist.getNext()
236                                         if not bouquet.valid(): #check end of list
237                                                 break
238                                         if bouquet.flags & eServiceReference.isDirectory:
239                                                 service, number = self.searchNumberHelper(serviceHandler, number, bouquet)
240                 if not service is None:
241                         if self.servicelist.getRoot() != bouquet: #already in correct bouquet?
242                                 self.servicelist.clearPath()
243                                 if self.servicelist.bouquet_root != bouquet:
244                                         self.servicelist.enterPath(self.servicelist.bouquet_root)
245                                 self.servicelist.enterPath(bouquet)
246                         self.servicelist.setCurrentSelection(service) #select the service in servicelist
247                         self.servicelist.zap()
248
249 config.misc.initialchannelselection = ConfigBoolean(default = True)
250
251 class InfoBarChannelSelection:
252         """ ChannelSelection - handles the channelSelection dialog and the initial 
253         channelChange actions which open the channelSelection dialog """
254         def __init__(self):
255                 #instantiate forever
256                 self.servicelist = self.session.instantiateDialog(ChannelSelection)
257
258                 if config.misc.initialchannelselection.value:
259                         self.onShown.append(self.firstRun)
260
261                 self["ChannelSelectActions"] = HelpableActionMap(self, "InfobarChannelSelection",
262                         {
263                                 "switchChannelUp": (self.switchChannelUp, _("open servicelist(up)")),
264                                 "switchChannelDown": (self.switchChannelDown, _("open servicelist(down)")),
265                                 "zapUp": (self.zapUp, _("previous channel")),
266                                 "zapDown": (self.zapDown, _("next channel")),
267                                 "historyBack": (self.historyBack, _("previous channel in history")),
268                                 "historyNext": (self.historyNext, _("next channel in history")),
269                                 "openServiceList": (self.openServiceList, _("open servicelist")),
270                         })
271
272         def showTvChannelList(self, zap=False):
273                 self.servicelist.setModeTv()
274                 if zap:
275                         self.servicelist.zap()
276                 self.session.execDialog(self.servicelist)
277
278         def showRadioChannelList(self, zap=False):
279                 self.servicelist.setModeRadio()
280                 if zap:
281                         self.servicelist.zap()
282                 self.session.execDialog(self.servicelist)
283
284         def firstRun(self):
285                 self.onShown.remove(self.firstRun)
286                 config.misc.initialchannelselection.value = False
287                 config.misc.initialchannelselection.save()
288                 self.switchChannelDown()
289
290         def historyBack(self):
291                 self.servicelist.historyBack()
292
293         def historyNext(self):
294                 self.servicelist.historyNext()
295
296         def switchChannelUp(self):
297                 self.servicelist.moveUp()
298                 self.session.execDialog(self.servicelist)
299
300         def switchChannelDown(self):
301                 self.servicelist.moveDown()
302                 self.session.execDialog(self.servicelist)
303
304         def openServiceList(self):
305                 self.session.execDialog(self.servicelist)
306
307         def zapUp(self):
308                 if self.servicelist.inBouquet():
309                         prev = self.servicelist.getCurrentSelection()
310                         if prev:
311                                 prev = prev.toString()
312                                 while True:
313                                         if config.usage.quickzap_bouquet_change.value:
314                                                 if self.servicelist.atBegin():
315                                                         self.servicelist.prevBouquet()
316                                         self.servicelist.moveUp()
317                                         cur = self.servicelist.getCurrentSelection()
318                                         if not cur or (not (cur.flags & 64)) or cur.toString() == prev:
319                                                 break
320                 else:
321                         self.servicelist.moveUp()
322                 self.servicelist.zap()
323
324         def zapDown(self):
325                 if self.servicelist.inBouquet():
326                         prev = self.servicelist.getCurrentSelection()
327                         if prev:
328                                 prev = prev.toString()
329                                 while True:
330                                         if config.usage.quickzap_bouquet_change.value and self.servicelist.atEnd():
331                                                 self.servicelist.nextBouquet()
332                                         else:
333                                                 self.servicelist.moveDown()
334                                         cur = self.servicelist.getCurrentSelection()
335                                         if not cur or (not (cur.flags & 64)) or cur.toString() == prev:
336                                                 break
337                 else:
338                         self.servicelist.moveDown()
339                 self.servicelist.zap()
340
341 class InfoBarMenu:
342         """ Handles a menu action, to open the (main) menu """
343         def __init__(self):
344                 self["MenuActions"] = HelpableActionMap(self, "InfobarMenuActions", 
345                         {
346                                 "mainMenu": (self.mainMenu, _("Enter main menu...")),
347                         })
348                 self.session.infobar = None
349
350         def mainMenu(self):
351                 print "loading mainmenu XML..."
352                 menu = mdom.childNodes[0]
353                 assert menu.tagName == "menu", "root element in menu must be 'menu'!"
354
355                 self.session.infobar = self
356                 # so we can access the currently active infobar from screens opened from within the mainmenu
357                 # at the moment used from the SubserviceSelection
358
359                 self.session.openWithCallback(self.mainMenuClosed, MainMenu, menu, menu.childNodes)
360
361         def mainMenuClosed(self, *val):
362                 self.session.infobar = None
363
364 class InfoBarSimpleEventView:
365         """ Opens the Eventview for now/next """
366         def __init__(self):
367                 self["EPGActions"] = HelpableActionMap(self, "InfobarEPGActions",
368                         {
369                                 "showEventInfo": (self.openEventView, _("show event details")),
370                         })
371
372         def openEventView(self):
373                 self.epglist = [ ]
374                 service = self.session.nav.getCurrentService()
375                 ref = self.session.nav.getCurrentlyPlayingServiceReference()
376                 info = service.info()
377                 ptr=info.getEvent(0)
378                 if ptr:
379                         self.epglist.append(ptr)
380                 ptr=info.getEvent(1)
381                 if ptr:
382                         self.epglist.append(ptr)
383                 if len(self.epglist) > 0:
384                         self.session.open(EventViewSimple, self.epglist[0], ServiceReference(ref), self.eventViewCallback)
385
386         def eventViewCallback(self, setEvent, setService, val): #used for now/next displaying
387                 if len(self.epglist) > 1:
388                         tmp = self.epglist[0]
389                         self.epglist[0]=self.epglist[1]
390                         self.epglist[1]=tmp
391                         setEvent(self.epglist[0])
392
393 class InfoBarEPG:
394         """ EPG - Opens an EPG list when the showEPGList action fires """
395         def __init__(self):
396                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
397                         {
398                                 iPlayableService.evUpdatedEventInfo: self.__evEventInfoChanged,
399                         })
400
401                 self.is_now_next = False
402                 self.dlg_stack = [ ]
403                 self.bouquetSel = None
404                 self.eventView = None
405                 self["EPGActions"] = HelpableActionMap(self, "InfobarEPGActions", 
406                         {
407                                 "showEventInfo": (self.openEventView, _("show EPG...")),
408                                 "showSingleServiceEPG": (self.openSingleServiceEPG, _("show single service EPG...")),
409                                 "showInfobarOrEpgWhenInfobarAlreadyVisible": self.showEventInfoWhenNotVisible,
410                         })
411
412         def showEventInfoWhenNotVisible(self):
413                 if self.shown:
414                         self.openEventView()
415                 else:
416                         self.toggleShow()
417                         return 1
418
419         def zapToService(self, service):
420                 if not service is None:
421                         if self.servicelist.getRoot() != self.epg_bouquet: #already in correct bouquet?
422                                 self.servicelist.clearPath()
423                                 if self.servicelist.bouquet_root != self.epg_bouquet:
424                                         self.servicelist.enterPath(self.servicelist.bouquet_root)
425                                 self.servicelist.enterPath(self.epg_bouquet)
426                         self.servicelist.setCurrentSelection(service) #select the service in servicelist
427                         self.servicelist.zap()
428
429         def getBouquetServices(self, bouquet):
430                 services = [ ]
431                 servicelist = eServiceCenter.getInstance().list(bouquet)
432                 if not servicelist is None:
433                         while True:
434                                 service = servicelist.getNext()
435                                 if not service.valid(): #check if end of list
436                                         break
437                                 if service.flags & (eServiceReference.isDirectory | eServiceReference.isMarker): #ignore non playable services
438                                         continue
439                                 services.append(ServiceReference(service))
440                 return services
441
442         def openBouquetEPG(self, bouquet, withCallback=True):
443                 services = self.getBouquetServices(bouquet)
444                 if len(services):
445                         self.epg_bouquet = bouquet
446                         if withCallback:
447                                 self.dlg_stack.append(self.session.openWithCallback(self.closed, EPGSelection, services, self.zapToService, None, self.changeBouquetCB))
448                         else:
449                                 self.session.open(EPGSelection, services, self.zapToService, None, self.changeBouquetCB)
450
451         def changeBouquetCB(self, direction, epg):
452                 if self.bouquetSel:
453                         if direction > 0:
454                                 self.bouquetSel.down()
455                         else:
456                                 self.bouquetSel.up()
457                         bouquet = self.bouquetSel.getCurrent()
458                         services = self.getBouquetServices(bouquet)
459                         if len(services):
460                                 self.epg_bouquet = bouquet
461                                 epg.setServices(services)
462
463         def closed(self, ret=False):
464                 closedScreen = self.dlg_stack.pop()
465                 if self.bouquetSel and closedScreen == self.bouquetSel:
466                         self.bouquetSel = None
467                 elif self.eventView and closedScreen == self.eventView:
468                         self.eventView = None
469                 if ret:
470                         dlgs=len(self.dlg_stack)
471                         if dlgs > 0:
472                                 self.dlg_stack[dlgs-1].close(dlgs > 1)
473
474         def openMultiServiceEPG(self, withCallback=True):
475                 bouquets = self.servicelist.getBouquetList()
476                 if bouquets is None:
477                         cnt = 0
478                 else:
479                         cnt = len(bouquets)
480                 if cnt > 1: # show bouquet list
481                         if withCallback:
482                                 self.bouquetSel = self.session.openWithCallback(self.closed, BouquetSelector, bouquets, self.openBouquetEPG, enableWrapAround=True)
483                                 self.dlg_stack.append(self.bouquetSel)
484                         else:
485                                 self.bouquetSel = self.session.open(BouquetSelector, bouquets, self.openBouquetEPG, enableWrapAround=True)
486                 elif cnt == 1: 
487                         self.openBouquetEPG(bouquets[0][1], withCallback)
488
489         def openSingleServiceEPG(self):
490                 ref=self.session.nav.getCurrentlyPlayingServiceReference()
491                 self.session.open(EPGSelection, ref)
492
493         def openSimilarList(self, eventid, refstr):
494                 self.session.open(EPGSelection, refstr, None, eventid)
495
496         def getNowNext(self):
497                 self.epglist = [ ]
498                 service = self.session.nav.getCurrentService()
499                 info = service and service.info()
500                 ptr = info and info.getEvent(0)
501                 if ptr:
502                         self.epglist.append(ptr)
503                 ptr = info and info.getEvent(1)
504                 if ptr:
505                         self.epglist.append(ptr)
506
507         def __evEventInfoChanged(self):
508                 if self.is_now_next and len(self.dlg_stack) == 1:
509                         self.getNowNext()
510                         assert self.eventView
511                         if len(self.epglist):
512                                 self.eventView.setEvent(self.epglist[0])
513
514         def openEventView(self):
515                 ref = self.session.nav.getCurrentlyPlayingServiceReference()
516                 self.getNowNext()
517                 if len(self.epglist) == 0:
518                         self.is_now_next = False
519                         epg = eEPGCache.getInstance()
520                         ptr = ref and ref.valid() and epg.lookupEventTime(ref, -1)
521                         if ptr:
522                                 self.epglist.append(ptr)
523                                 ptr = epg.lookupEventTime(ref, ptr.getBeginTime(), +1)
524                                 if ptr:
525                                         self.epglist.append(ptr)
526                 else:
527                         self.is_now_next = True
528                 if len(self.epglist) > 0:
529                         self.eventView = self.session.openWithCallback(self.closed, EventViewEPGSelect, self.epglist[0], ServiceReference(ref), self.eventViewCallback, self.openSingleServiceEPG, self.openMultiServiceEPG, self.openSimilarList)
530                         self.dlg_stack.append(self.eventView)
531                 else:
532                         print "no epg for the service avail.. so we show multiepg instead of eventinfo"
533                         self.openMultiServiceEPG(False)
534
535         def eventViewCallback(self, setEvent, setService, val): #used for now/next displaying
536                 if len(self.epglist) > 1:
537                         tmp = self.epglist[0]
538                         self.epglist[0]=self.epglist[1]
539                         self.epglist[1]=tmp
540                         setEvent(self.epglist[0])
541
542 class InfoBarTuner:
543         """provides a snr/agc/ber display"""
544         def __init__(self):
545                 self["FrontendStatus"] = FrontendStatus(service_source = self.session.nav.getCurrentService)
546
547 class InfoBarEvent:
548         """provides a current/next event info display"""
549         def __init__(self):
550                 self["Event_Now"] = EventInfo(self.session.nav, EventInfo.NOW)
551                 self["Event_Next"] = EventInfo(self.session.nav, EventInfo.NEXT)
552
553 class InfoBarRdsDecoder:
554         """provides RDS and Rass support/display"""
555         def __init__(self):
556                 self.rds_display = self.session.instantiateDialog(RdsInfoDisplay)
557                 self.rass_interactive = None
558
559                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
560                         {
561                                 iPlayableService.evEnd: self.__serviceStopped,
562                                 iPlayableService.evUpdatedRassSlidePic: self.RassSlidePicChanged
563                         })
564
565                 self["RdsActions"] = ActionMap(["InfobarRdsActions"],
566                 {
567                         "startRassInteractive": self.startRassInteractive
568                 },-1)
569
570                 self["RdsActions"].setEnabled(False)
571
572                 self.onLayoutFinish.append(self.rds_display.show)
573                 self.rds_display.onRassInteractivePossibilityChanged.append(self.RassInteractivePossibilityChanged)
574
575         def RassInteractivePossibilityChanged(self, state):
576                 self["RdsActions"].setEnabled(state)
577
578         def RassSlidePicChanged(self):
579                 if not self.rass_interactive:
580                         service = self.session.nav.getCurrentService()
581                         decoder = service and service.rdsDecoder()
582                         if decoder:
583                                 decoder.showRassSlidePicture()
584
585         def __serviceStopped(self):
586                 if self.rass_interactive is not None:
587                         rass_interactive = self.rass_interactive
588                         self.rass_interactive = None
589                         rass_interactive.close()
590
591         def startRassInteractive(self):
592                 self.rds_display.hide()
593                 self.rass_interactive = self.session.openWithCallback(self.RassInteractiveClosed, RassInteractive)
594
595         def RassInteractiveClosed(self, *val):
596                 if self.rass_interactive is not None:
597                         self.rass_interactive = None
598                         self.RassSlidePicChanged()
599                 self.rds_display.show()
600
601 class InfoBarServiceName:
602         def __init__(self):
603                 self["CurrentService"] = CurrentService(self.session.nav)
604
605 class InfoBarSeek:
606         """handles actions like seeking, pause"""
607
608         # ispause, isff, issm
609         SEEK_STATE_PLAY = (0, 0, 0, ">")
610         SEEK_STATE_PAUSE = (1, 0, 0, "||")
611         SEEK_STATE_FF_2X = (0, 2, 0, ">> 2x")
612         SEEK_STATE_FF_4X = (0, 4, 0, ">> 4x")
613         SEEK_STATE_FF_8X = (0, 8, 0, ">> 8x")
614         SEEK_STATE_FF_32X = (0, 32, 0, ">> 32x")
615         SEEK_STATE_FF_64X = (0, 64, 0, ">> 64x")
616         SEEK_STATE_FF_128X = (0, 128, 0, ">> 128x")
617
618         SEEK_STATE_BACK_16X = (0, -16, 0, "<< 16x")
619         SEEK_STATE_BACK_32X = (0, -32, 0, "<< 32x")
620         SEEK_STATE_BACK_64X = (0, -64, 0, "<< 64x")
621         SEEK_STATE_BACK_128X = (0, -128, 0, "<< 128x")
622
623         SEEK_STATE_SM_HALF = (0, 0, 2, "/2")
624         SEEK_STATE_SM_QUARTER = (0, 0, 4, "/4")
625         SEEK_STATE_SM_EIGHTH = (0, 0, 8, "/8")
626
627         SEEK_STATE_EOF = (1, 0, 0, "END")
628
629         def __init__(self, actionmap = "InfobarSeekActions"):
630                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
631                         {
632                                 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged,
633                                 iPlayableService.evStart: self.__serviceStarted,
634
635                                 iPlayableService.evEOF: self.__evEOF,
636                                 iPlayableService.evSOF: self.__evSOF,
637                         })
638
639                 class InfoBarSeekActionMap(HelpableActionMap):
640                         def __init__(self, screen, *args, **kwargs):
641                                 HelpableActionMap.__init__(self, screen, *args, **kwargs)
642                                 self.screen = screen
643
644                         def action(self, contexts, action):
645                                 print "action:", action
646                                 if action[:5] == "seek:":
647                                         time = int(action[5:])
648                                         self.screen.seekRelative(time * 90000)
649                                         self.screen.showAfterSeek()
650                                         return 1
651                                 else:
652                                         return HelpableActionMap.action(self, contexts, action)
653
654                 self["SeekActions"] = InfoBarSeekActionMap(self, actionmap, 
655                         {
656                                 "playpauseService": self.playpauseService,
657                                 "pauseService": (self.pauseService, _("pause")),
658                                 "unPauseService": (self.unPauseService, _("continue")),
659
660                                 "seekFwd": (self.seekFwd, _("skip forward")),
661                                 "seekFwdManual": (self.seekFwdManual, _("skip forward (enter time)")),
662                                 "seekBack": (self.seekBack, _("skip backward")),
663                                 "seekBackManual": (self.seekBackManual, _("skip backward (enter time)")),
664                                 
665                                 "seekFwdDef": (self.seekFwdDef, _("skip forward (self defined)")),
666                                 "seekBackDef": (self.seekBackDef, _("skip backward (self defined)"))
667                         }, prio=-1)
668                         # give them a little more priority to win over color buttons
669
670                 self["SeekActions"].setEnabled(False)
671
672                 self.seekstate = self.SEEK_STATE_PLAY
673                 
674                 self.seek_flag = True
675
676                 self.onPlayStateChanged = [ ]
677
678                 self.lockedBecauseOfSkipping = False
679
680                 self.__seekableStatusChanged()
681
682         def showAfterSeek(self):
683                 if isinstance(self, InfoBarShowHide):
684                         self.doShow()
685
686         def up(self):
687                 pass
688
689         def down(self):
690                 pass
691
692         def getSeek(self):
693                 service = self.session.nav.getCurrentService()
694                 if service is None:
695                         return None
696
697                 seek = service.seek()
698
699                 if seek is None or not seek.isCurrentlySeekable():
700                         return None
701
702                 return seek
703
704         def isSeekable(self):
705                 if self.getSeek() is None:
706                         return False
707                 return True
708
709         def __seekableStatusChanged(self):
710                 print "seekable status changed!"
711                 if not self.isSeekable():
712                         self["SeekActions"].setEnabled(False)
713                         print "not seekable, return to play"
714                         self.setSeekState(self.SEEK_STATE_PLAY)
715                 else:
716                         self["SeekActions"].setEnabled(True)
717                         print "seekable"
718
719         def __serviceStarted(self):
720                 self.seekstate = self.SEEK_STATE_PLAY
721                 self.__seekableStatusChanged()
722
723         def setSeekState(self, state):
724                 service = self.session.nav.getCurrentService()
725
726                 if service is None:
727                         return False
728
729                 if not self.isSeekable():
730                         if state not in [self.SEEK_STATE_PLAY, self.SEEK_STATE_PAUSE]:
731                                 state = self.SEEK_STATE_PLAY
732
733                 pauseable = service.pause()
734
735                 if pauseable is None:
736                         print "not pauseable."
737                         state = self.SEEK_STATE_PLAY
738
739                 oldstate = self.seekstate
740                 self.seekstate = state
741
742                 for i in range(3):
743                         if oldstate[i] != self.seekstate[i]:
744                                 (self.session.nav.pause, pauseable.setFastForward, pauseable.setSlowMotion)[i](self.seekstate[i])
745
746                 for c in self.onPlayStateChanged:
747                         c(self.seekstate)
748
749                 self.checkSkipShowHideLock()
750
751                 return True
752
753         def playpauseService(self):
754                 if self.seekstate != self.SEEK_STATE_PLAY:
755                         self.unPauseService()
756                 else:
757                         self.pauseService()
758
759         def pauseService(self):
760                 if self.seekstate == self.SEEK_STATE_PAUSE:
761                         print "pause, but in fact unpause"
762                         self.unPauseService()
763                 else:
764                         if self.seekstate == self.SEEK_STATE_PLAY:
765                                 print "yes, playing."
766                         else:
767                                 print "no", self.seekstate
768                         print "pause"
769                         self.setSeekState(self.SEEK_STATE_PAUSE);
770
771         def unPauseService(self):
772                 print "unpause"
773                 if self.seekstate == self.SEEK_STATE_PLAY:
774                         return 0
775                 self.setSeekState(self.SEEK_STATE_PLAY)
776
777         def doSeek(self, seektime):
778                 print "doseek", seektime
779                 service = self.session.nav.getCurrentService()
780                 if service is None:
781                         return
782
783                 seekable = self.getSeek()
784                 if seekable is None:
785                         return
786
787                 seekable.seekTo(90 * seektime)
788
789         def seekFwd(self):
790                 lookup = {
791                                 self.SEEK_STATE_PLAY: self.SEEK_STATE_FF_2X,
792                                 self.SEEK_STATE_PAUSE: self.SEEK_STATE_SM_EIGHTH,
793                                 self.SEEK_STATE_FF_2X: self.SEEK_STATE_FF_4X,
794                                 self.SEEK_STATE_FF_4X: self.SEEK_STATE_FF_8X,
795                                 self.SEEK_STATE_FF_8X: self.SEEK_STATE_FF_32X,
796                                 self.SEEK_STATE_FF_32X: self.SEEK_STATE_FF_64X,
797                                 self.SEEK_STATE_FF_64X: self.SEEK_STATE_FF_128X,
798                                 self.SEEK_STATE_FF_128X: self.SEEK_STATE_FF_128X,
799                                 self.SEEK_STATE_BACK_16X: self.SEEK_STATE_PLAY,
800                                 self.SEEK_STATE_BACK_32X: self.SEEK_STATE_BACK_16X,
801                                 self.SEEK_STATE_BACK_64X: self.SEEK_STATE_BACK_32X,
802                                 self.SEEK_STATE_BACK_128X: self.SEEK_STATE_BACK_64X,
803                                 self.SEEK_STATE_SM_HALF: self.SEEK_STATE_SM_HALF,
804                                 self.SEEK_STATE_SM_QUARTER: self.SEEK_STATE_SM_HALF,
805                                 self.SEEK_STATE_SM_EIGHTH: self.SEEK_STATE_SM_QUARTER,
806                                 self.SEEK_STATE_EOF: self.SEEK_STATE_EOF,
807                         }
808                 self.setSeekState(lookup[self.seekstate])
809
810         def seekBack(self):
811                 lookup = {
812                                 self.SEEK_STATE_PLAY: self.SEEK_STATE_BACK_16X,
813                                 self.SEEK_STATE_PAUSE: self.SEEK_STATE_PAUSE,
814                                 self.SEEK_STATE_FF_2X: self.SEEK_STATE_PLAY,
815                                 self.SEEK_STATE_FF_4X: self.SEEK_STATE_FF_2X,
816                                 self.SEEK_STATE_FF_8X: self.SEEK_STATE_FF_4X,
817                                 self.SEEK_STATE_FF_32X: self.SEEK_STATE_FF_8X,
818                                 self.SEEK_STATE_FF_64X: self.SEEK_STATE_FF_32X,
819                                 self.SEEK_STATE_FF_128X: self.SEEK_STATE_FF_64X,
820                                 self.SEEK_STATE_BACK_16X: self.SEEK_STATE_BACK_32X,
821                                 self.SEEK_STATE_BACK_32X: self.SEEK_STATE_BACK_64X,
822                                 self.SEEK_STATE_BACK_64X: self.SEEK_STATE_BACK_128X,
823                                 self.SEEK_STATE_BACK_128X: self.SEEK_STATE_BACK_128X,
824                                 self.SEEK_STATE_SM_HALF: self.SEEK_STATE_SM_QUARTER,
825                                 self.SEEK_STATE_SM_QUARTER: self.SEEK_STATE_SM_EIGHTH,
826                                 self.SEEK_STATE_SM_EIGHTH: self.SEEK_STATE_PAUSE,
827                                 self.SEEK_STATE_EOF: self.SEEK_STATE_BACK_16X,
828                         }
829                 self.setSeekState(lookup[self.seekstate])
830
831                 if self.seekstate == self.SEEK_STATE_PAUSE:
832                         seekable = self.getSeek()
833                         if seekable is not None:
834                                 seekable.seekRelative(-1, 3)
835
836         def seekFwdDef(self):
837                 self.seek_flag = False
838                 seconds = config.usage.self_defined_seek.value
839                 print "Seek", seconds, "seconds self defined forward"
840                 seekable = self.getSeek()
841                 if seekable is not None:
842                         seekable.seekRelative(1, seconds * 90000)
843                 
844         def seekBackDef(self):
845                 self.seek_flag = False
846                 seconds = config.usage.self_defined_seek.value
847                 print "Seek", seconds, "seconds self defined backward"
848                 seekable = self.getSeek()
849                 if seekable is not None:
850                         seekable.seekRelative(1, 0 - seconds * 90000)
851                 
852         def seekFwdManual(self):
853                 self.session.openWithCallback(self.fwdSeekTo, MinuteInput)
854
855         def fwdSeekTo(self, minutes):
856                 print "Seek", minutes, "minutes forward"
857                 if minutes != 0:
858                         seekable = self.getSeek()
859                         if seekable is not None:
860                                 seekable.seekRelative(1, minutes * 60 * 90000)
861
862         def seekBackManual(self):
863                 self.session.openWithCallback(self.rwdSeekTo, MinuteInput)
864
865         def rwdSeekTo(self, minutes):
866                 print "rwdSeekTo"
867                 self.fwdSeekTo(0 - minutes)
868
869         def checkSkipShowHideLock(self):
870                 wantlock = self.seekstate != self.SEEK_STATE_PLAY
871
872                 if config.usage.show_infobar_on_zap.value:
873                         if self.lockedBecauseOfSkipping and not wantlock:
874                                 self.unlockShow()
875                                 self.lockedBecauseOfSkipping = False
876
877                         if wantlock and not self.lockedBecauseOfSkipping:
878                                 self.lockShow()
879                                 self.lockedBecauseOfSkipping = True
880
881         def __evEOF(self):
882                 if self.seekstate == self.SEEK_STATE_EOF:
883                         return
884                 if self.seekstate[1] < 0: # SEEK_STATE_BACK_*X
885                         print "end of stream while seeking back, ignoring."
886                         return
887
888                 # if we are seeking, we try to end up ~1s before the end, and pause there.
889                 if not self.seekstate in [self.SEEK_STATE_PLAY, self.SEEK_STATE_PAUSE]:
890                         self.setSeekState(self.SEEK_STATE_EOF)
891                         self.seekRelativeToEnd(-90000)
892                 else:
893                         self.setSeekState(self.SEEK_STATE_EOF)
894
895         def __evSOF(self):
896                 self.setSeekState(self.SEEK_STATE_PLAY)
897                 self.doSeek(0)
898
899         def seekRelative(self, diff):
900                 if self.seek_flag == True:
901                         seekable = self.getSeek()
902                         if seekable is not None:
903                                 print "seekRelative: res:", seekable.seekRelative(1, diff)
904                         else:
905                                 print "seek failed!"
906                 else:
907                         self.seek_flag = True
908
909         def seekRelativeToEnd(self, diff):
910                 assert diff <= 0, "diff is expected to be negative!"
911
912                 # might sound like an evil hack, but:
913                 # if we seekRelativeToEnd(0), we expect to be at the end, which is what we want,
914                 # and we don't get that by passing 0 here (it would seek to begin).
915                 if diff == 0:
916                         diff = -1
917
918                 # relative-to-end seeking is implemented as absolutes seeks with negative time
919                 self.seekAbsolute(diff)
920
921         def seekAbsolute(self, abs):
922                 seekable = self.getSeek()
923                 if seekable is not None:
924                         seekable.seekTo(abs)
925
926 from Screens.PVRState import PVRState, TimeshiftState
927
928 class InfoBarPVRState:
929         def __init__(self, screen=PVRState):
930                 self.onPlayStateChanged.append(self.__playStateChanged)
931                 self.pvrStateDialog = self.session.instantiateDialog(screen)
932                 self.onShow.append(self._mayShow)
933                 self.onHide.append(self.pvrStateDialog.hide)
934
935         def _mayShow(self):
936                 if self.execing and self.seekstate != self.SEEK_STATE_PLAY:
937                         self.pvrStateDialog.show()
938
939         def __playStateChanged(self, state):
940                 playstateString = state[3]
941                 self.pvrStateDialog["state"].setText(playstateString)
942                 self._mayShow()
943
944 class InfoBarTimeshiftState(InfoBarPVRState):
945         def __init__(self):
946                 InfoBarPVRState.__init__(self, screen=TimeshiftState)
947
948         def _mayShow(self):
949                 if self.execing and self.timeshift_enabled:
950                         self.pvrStateDialog.show()
951
952 class InfoBarShowMovies:
953
954         # i don't really like this class. 
955         # it calls a not further specified "movie list" on up/down/movieList,
956         # so this is not more than an action map
957         def __init__(self):
958                 self["MovieListActions"] = HelpableActionMap(self, "InfobarMovieListActions", 
959                         {
960                                 "movieList": (self.showMovies, _("movie list")),
961                                 "up": (self.showMovies, _("movie list")),
962                                 "down": (self.showMovies, _("movie list"))
963                         })
964
965 # InfoBarTimeshift requires InfoBarSeek, instantiated BEFORE!
966
967 # Hrmf.
968 #
969 # Timeshift works the following way:
970 #                                         demux0   demux1                    "TimeshiftActions" "TimeshiftActivateActions" "SeekActions"
971 # - normal playback                       TUNER    unused      PLAY               enable                disable              disable
972 # - user presses "yellow" button.         FILE     record      PAUSE              enable                disable              enable
973 # - user presess pause again              FILE     record      PLAY               enable                disable              enable
974 # - user fast forwards                    FILE     record      FF                 enable                disable              enable
975 # - end of timeshift buffer reached       TUNER    record      PLAY               enable                enable               disable
976 # - user backwards                        FILE     record      BACK  # !!         enable                disable              enable
977 #
978
979 # in other words:
980 # - when a service is playing, pressing the "timeshiftStart" button ("yellow") enables recording ("enables timeshift"),
981 # freezes the picture (to indicate timeshift), sets timeshiftMode ("activates timeshift")
982 # now, the service becomes seekable, so "SeekActions" are enabled, "TimeshiftEnableActions" are disabled.
983 # - the user can now PVR around
984 # - if it hits the end, the service goes into live mode ("deactivates timeshift", it's of course still "enabled")
985 # the service looses it's "seekable" state. It can still be paused, but just to activate timeshift right
986 # after!
987 # the seek actions will be disabled, but the timeshiftActivateActions will be enabled
988 # - if the user rewinds, or press pause, timeshift will be activated again
989
990 # note that a timeshift can be enabled ("recording") and
991 # activated (currently time-shifting).
992
993 class InfoBarTimeshift:
994         def __init__(self):
995                 self["TimeshiftActions"] = HelpableActionMap(self, "InfobarTimeshiftActions", 
996                         {
997                                 "timeshiftStart": (self.startTimeshift, _("start timeshift")),  # the "yellow key"
998                                 "timeshiftStop": (self.stopTimeshift, _("stop timeshift"))      # currently undefined :), probably 'TV'
999                         }, prio=1)
1000                 self["TimeshiftActivateActions"] = ActionMap(["InfobarTimeshiftActivateActions"],
1001                         {
1002                                 "timeshiftActivateEnd": self.activateTimeshiftEnd, # something like "rewind key"
1003                                 "timeshiftActivateEndAndPause": self.activateTimeshiftEndAndPause  # something like "pause key"
1004                         }, prio=-1) # priority over record
1005
1006                 self.timeshift_enabled = 0
1007                 self.timeshift_state = 0
1008                 self.ts_rewind_timer = eTimer()
1009                 self.ts_rewind_timer.timeout.get().append(self.rewindService)
1010
1011                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1012                         {
1013                                 iPlayableService.evStart: self.__serviceStarted,
1014                                 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged
1015                         })
1016
1017         def getTimeshift(self):
1018                 service = self.session.nav.getCurrentService()
1019                 return service and service.timeshift()
1020
1021         def startTimeshift(self):
1022                 print "enable timeshift"
1023                 ts = self.getTimeshift()
1024                 if ts is None:
1025                         self.session.open(MessageBox, _("Timeshift not possible!"), MessageBox.TYPE_ERROR)
1026                         print "no ts interface"
1027                         return 0
1028
1029                 if self.timeshift_enabled:
1030                         print "hu, timeshift already enabled?"
1031                 else:
1032                         if not ts.startTimeshift():
1033                                 self.timeshift_enabled = 1
1034
1035                                 # we remove the "relative time" for now.
1036                                 #self.pvrStateDialog["timeshift"].setRelative(time.time())
1037
1038                                 # PAUSE.
1039                                 #self.setSeekState(self.SEEK_STATE_PAUSE)
1040                                 self.activateTimeshiftEnd(False)
1041
1042                                 # enable the "TimeshiftEnableActions", which will override
1043                                 # the startTimeshift actions
1044                                 self.__seekableStatusChanged()
1045                         else:
1046                                 print "timeshift failed"
1047
1048         def stopTimeshift(self):
1049                 if not self.timeshift_enabled:
1050                         return 0
1051                 print "disable timeshift"
1052                 ts = self.getTimeshift()
1053                 if ts is None:
1054                         return 0
1055                 self.session.openWithCallback(self.stopTimeshiftConfirmed, MessageBox, _("Stop Timeshift?"), MessageBox.TYPE_YESNO)
1056
1057         def stopTimeshiftConfirmed(self, confirmed):
1058                 if not confirmed:
1059                         return
1060
1061                 ts = self.getTimeshift()
1062                 if ts is None:
1063                         return
1064
1065                 ts.stopTimeshift()
1066                 self.timeshift_enabled = 0
1067
1068                 # disable actions
1069                 self.__seekableStatusChanged()
1070
1071         # activates timeshift, and seeks to (almost) the end
1072         def activateTimeshiftEnd(self, back = True):
1073                 ts = self.getTimeshift()
1074                 print "activateTimeshiftEnd"
1075
1076                 if ts is None:
1077                         return
1078
1079                 if ts.isTimeshiftActive():
1080                         print "!! activate timeshift called - but shouldn't this be a normal pause?"
1081                         self.pauseService()
1082                 else:
1083                         print "play, ..."
1084                         ts.activateTimeshift() # activate timeshift will automatically pause
1085                         self.setSeekState(self.SEEK_STATE_PAUSE)
1086                         self.seekRelativeToEnd(-90000) # seek approx. 1 sec before end
1087
1088                 if back:
1089                         self.ts_rewind_timer.start(200, 1)
1090
1091         def rewindService(self):
1092                 self.setSeekState(self.SEEK_STATE_BACK_16X)
1093
1094         # same as activateTimeshiftEnd, but pauses afterwards.
1095         def activateTimeshiftEndAndPause(self):
1096                 print "activateTimeshiftEndAndPause"
1097                 #state = self.seekstate
1098                 self.activateTimeshiftEnd(False)
1099
1100         def __seekableStatusChanged(self):
1101                 enabled = False
1102
1103                 print "self.isSeekable", self.isSeekable()
1104                 print "self.timeshift_enabled", self.timeshift_enabled
1105
1106                 # when this service is not seekable, but timeshift
1107                 # is enabled, this means we can activate
1108                 # the timeshift
1109                 if not self.isSeekable() and self.timeshift_enabled:
1110                         enabled = True
1111
1112                 print "timeshift activate:", enabled
1113                 self["TimeshiftActivateActions"].setEnabled(enabled)
1114
1115         def __serviceStarted(self):
1116                 self.timeshift_enabled = False
1117                 self.__seekableStatusChanged()
1118
1119 from Screens.PiPSetup import PiPSetup
1120
1121 class InfoBarExtensions:
1122         EXTENSION_SINGLE = 0
1123         EXTENSION_LIST = 1
1124
1125         def __init__(self):
1126                 self.list = []
1127
1128                 self["InstantExtensionsActions"] = HelpableActionMap(self, "InfobarExtensions",
1129                         {
1130                                 "extensions": (self.showExtensionSelection, _("view extensions...")),
1131                         })
1132
1133         def addExtension(self, extension, key = None, type = EXTENSION_SINGLE):
1134                 self.list.append((type, extension, key))
1135
1136         def updateExtension(self, extension, key = None):
1137                 self.extensionsList.append(extension)
1138                 if key is not None:
1139                         if self.extensionKeys.has_key(key):
1140                                 key = None
1141
1142                 if key is None:
1143                         for x in self.availableKeys:
1144                                 if not self.extensionKeys.has_key(x):
1145                                         key = x
1146                                         break
1147
1148                 if key is not None:
1149                         self.extensionKeys[key] = len(self.extensionsList) - 1
1150
1151         def updateExtensions(self):
1152                 self.extensionsList = []
1153                 self.availableKeys = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "red", "green", "yellow", "blue" ]
1154                 self.extensionKeys = {}
1155                 for x in self.list:
1156                         if x[0] == self.EXTENSION_SINGLE:
1157                                 self.updateExtension(x[1], x[2])
1158                         else:
1159                                 for y in x[1]():
1160                                         self.updateExtension(y[0], y[1])
1161
1162
1163         def showExtensionSelection(self):
1164                 self.updateExtensions()
1165                 extensionsList = self.extensionsList[:]
1166                 keys = []
1167                 list = []
1168                 for x in self.availableKeys:
1169                         if self.extensionKeys.has_key(x):
1170                                 entry = self.extensionKeys[x]
1171                                 extension = self.extensionsList[entry]
1172                                 if extension[2]():
1173                                         name = str(extension[0]())
1174                                         list.append((extension[0](), extension))
1175                                         keys.append(x)
1176                                         extensionsList.remove(extension)
1177                                 else:
1178                                         extensionsList.remove(extension)
1179                 for x in extensionsList:
1180                         list.append((x[0](), x))
1181                 keys += [""] * len(extensionsList)
1182                 self.session.openWithCallback(self.extensionCallback, ChoiceBox, title=_("Please choose an extension..."), list = list, keys = keys)
1183
1184         def extensionCallback(self, answer):
1185                 if answer is not None:
1186                         answer[1][1]()
1187
1188 from Tools.BoundFunction import boundFunction
1189
1190 # depends on InfoBarExtensions
1191 from Components.PluginComponent import plugins
1192
1193 class InfoBarPlugins:
1194         def __init__(self):
1195                 self.addExtension(extension = self.getPluginList, type = InfoBarExtensions.EXTENSION_LIST)
1196
1197         def getPluginName(self, name):
1198                 return name
1199
1200         def getPluginList(self):
1201                 list = []
1202                 for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EXTENSIONSMENU):
1203                         list.append(((boundFunction(self.getPluginName, p.name), boundFunction(self.runPlugin, p), lambda: True), None))
1204                 return list
1205
1206         def runPlugin(self, plugin):
1207                 self.session.servicelist = self.servicelist
1208                 plugin(session = self.session)
1209                 del self.session.servicelist
1210
1211 # depends on InfoBarExtensions
1212 class InfoBarSleepTimer:
1213         def __init__(self):
1214                 self.addExtension((self.getSleepTimerName, self.showSleepTimerSetup, self.available), "1")
1215
1216         def available(self):
1217                 return True
1218
1219         def getSleepTimerName(self):
1220                 return _("Sleep Timer")
1221
1222         def showSleepTimerSetup(self):
1223                 self.session.open(SleepTimerEdit)
1224
1225 # depends on InfoBarExtensions
1226 class InfoBarPiP:
1227         def __init__(self):
1228                 self.session.pipshown = False
1229
1230                 self.addExtension((self.getShowHideName, self.showPiP, self.available), "blue")
1231                 self.addExtension((self.getMoveName, self.movePiP, self.pipShown), "green")
1232                 self.addExtension((self.getSwapName, self.swapPiP, self.pipShown), "yellow")
1233
1234         def available(self):
1235                 return True
1236
1237         def pipShown(self):
1238                 return self.session.pipshown
1239
1240         def getShowHideName(self):
1241                 if self.session.pipshown:
1242                         return _("Disable Picture in Picture")
1243                 else:
1244                         return _("Activate Picture in Picture")
1245
1246         def getSwapName(self):
1247                 return _("Swap Services")
1248
1249         def getMoveName(self):
1250                 return _("Move Picture in Picture")
1251
1252         def showPiP(self):
1253                 if self.session.pipshown:
1254                         del self.session.pip
1255                         self.session.pipshown = False
1256                 else:
1257                         self.session.pip = self.session.instantiateDialog(PictureInPicture)
1258                         self.session.pip.show()
1259                         newservice = self.session.nav.getCurrentlyPlayingServiceReference()
1260                         if self.session.pip.playService(newservice):
1261                                 self.session.pipshown = True
1262                                 self.session.pip.servicePath = self.servicelist.getCurrentServicePath()
1263                         else:
1264                                 self.session.pipshown = False
1265                                 del self.session.pip
1266                         self.session.nav.playService(newservice)
1267
1268         def swapPiP(self):
1269                 swapservice = self.session.nav.getCurrentlyPlayingServiceReference()
1270                 if self.session.pip.servicePath:
1271                         servicepath = self.servicelist.getCurrentServicePath()
1272                         ref=servicepath[len(servicepath)-1]
1273                         pipref=self.session.pip.getCurrentService()
1274                         self.session.pip.playService(swapservice)
1275                         self.servicelist.setCurrentServicePath(self.session.pip.servicePath)
1276                         if pipref.toString() != ref.toString(): # is a subservice ?
1277                                 self.session.nav.stopService() # stop portal
1278                                 self.session.nav.playService(pipref) # start subservice
1279                         self.session.pip.servicePath=servicepath
1280
1281         def movePiP(self):
1282                 self.session.open(PiPSetup, pip = self.session.pip)
1283
1284 from RecordTimer import parseEvent
1285
1286 class InfoBarInstantRecord:
1287         """Instant Record - handles the instantRecord action in order to 
1288         start/stop instant records"""
1289         def __init__(self):
1290                 self["InstantRecordActions"] = HelpableActionMap(self, "InfobarInstantRecord",
1291                         {
1292                                 "instantRecord": (self.instantRecord, _("Instant Record...")),
1293                         })
1294                 self.recording = []
1295                 self["BlinkingPoint"] = BlinkingPixmapConditional()
1296                 self["BlinkingPoint"].hide()
1297                 self["BlinkingPoint"].setConnect(self.session.nav.RecordTimer.isRecording)
1298
1299         def stopCurrentRecording(self, entry = -1):
1300                 if entry is not None and entry != -1:
1301                         self.session.nav.RecordTimer.removeEntry(self.recording[entry])
1302                         self.recording.remove(self.recording[entry])
1303
1304         def startInstantRecording(self, limitEvent = False):
1305                 serviceref = self.session.nav.getCurrentlyPlayingServiceReference()
1306
1307                 # try to get event info
1308                 event = None
1309                 try:
1310                         service = self.session.nav.getCurrentService()
1311                         epg = eEPGCache.getInstance()
1312                         event = epg.lookupEventTime(serviceref, -1, 0)
1313                         if event is None:
1314                                 info = service.info()
1315                                 ev = info.getEvent(0)
1316                                 event = ev
1317                 except:
1318                         pass
1319
1320                 begin = time()
1321                 end = time() + 3600 * 10
1322                 name = "instant record"
1323                 description = ""
1324                 eventid = None
1325
1326                 if event is not None:
1327                         curEvent = parseEvent(event)
1328                         name = curEvent[2]
1329                         description = curEvent[3]
1330                         eventid = curEvent[4]
1331                         if limitEvent:
1332                                 end = curEvent[1]
1333                 else:
1334                         if limitEvent:
1335                                 self.session.open(MessageBox, _("No event info found, recording indefinitely."), MessageBox.TYPE_INFO)
1336
1337                 data = (begin, end, name, description, eventid)
1338
1339                 recording = self.session.nav.recordWithTimer(serviceref, *data)
1340                 recording.dontSave = True
1341                 self.recording.append(recording)
1342
1343                 #self["BlinkingPoint"].setConnect(lambda: self.recording.isRunning())
1344
1345         def isInstantRecordRunning(self):
1346                 print "self.recording:", self.recording
1347                 if len(self.recording) > 0:
1348                         for x in self.recording:
1349                                 if x.isRunning():
1350                                         return True
1351                 return False
1352
1353         def recordQuestionCallback(self, answer):
1354                 print "pre:\n", self.recording
1355
1356                 if answer is None or answer[1] == "no":
1357                         return
1358                 list = []
1359                 recording = self.recording[:]
1360                 for x in recording:
1361                         if not x in self.session.nav.RecordTimer.timer_list:
1362                                 self.recording.remove(x)
1363                         elif x.dontSave and x.isRunning():
1364                                 list.append(TimerEntryComponent(x, False))
1365
1366                 if answer[1] == "changeduration":
1367                         if len(self.recording) == 1:
1368                                 self.changeDuration(0)
1369                         else:
1370                                 self.session.openWithCallback(self.changeDuration, TimerSelection, list)
1371                 elif answer[1] == "changeendtime":
1372                         if len(self.recording) == 1:
1373                                 self.setEndtime(0)
1374                         else:
1375                                 self.session.openWithCallback(self.setEndTime, TimerSelection, list)
1376                 elif answer[1] == "stop":
1377                         if len(self.recording) == 1:
1378                                 self.stopCurrentRecording(0)
1379                         else:
1380                                 self.session.openWithCallback(self.stopCurrentRecording, TimerSelection, list)
1381                 elif answer[1] in ( "indefinitely" , "manualduration", "manualendtime", "event"):
1382                         self.startInstantRecording(limitEvent = answer[1] in ("event", "manualendtime") or False)
1383                         if answer[1] == "manualduration":
1384                                 self.changeDuration(len(self.recording)-1)
1385                         elif answer[1] == "manualendtime":
1386                                 self.setEndtime(len(self.recording)-1)
1387                 print "after:\n", self.recording
1388
1389         def setEndtime(self, entry):
1390                 if entry is not None:
1391                         self.selectedEntry = entry
1392                         self.endtime=ConfigClock(default = self.recording[self.selectedEntry].end)
1393                         dlg = self.session.openWithCallback(self.TimeDateInputClosed, TimeDateInput, self.endtime)
1394                         dlg.setTitle(_("Please change recording endtime"))
1395
1396         def TimeDateInputClosed(self, ret):
1397                 if len(ret) > 1:
1398                         if ret[0]:
1399                                 localendtime = localtime(ret[1])
1400                                 print "stopping recording at", strftime("%c", localendtime)
1401                                 self.recording[self.selectedEntry].end = ret[1]
1402                                 self.session.nav.RecordTimer.timeChanged(self.recording[self.selectedEntry])
1403
1404         def changeDuration(self, entry):
1405                 if entry is not None:
1406                         self.selectedEntry = entry
1407                         self.session.openWithCallback(self.inputCallback, InputBox, title=_("How many minutes do you want to record?"), text="5", maxSize=False, type=Input.NUMBER)
1408
1409         def inputCallback(self, value):
1410                 if value is not None:
1411                         print "stopping recording after", int(value), "minutes."
1412                         self.recording[self.selectedEntry].end = time() + 60 * int(value)
1413                         self.session.nav.RecordTimer.timeChanged(self.recording[self.selectedEntry])
1414
1415         def instantRecord(self):
1416                 try:
1417                         stat = os_stat(resolveFilename(SCOPE_HDD))
1418                 except:
1419                         self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
1420                         return
1421
1422                 if self.isInstantRecordRunning():
1423                         self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1424                                 title=_("A recording is currently running.\nWhat do you want to do?"), \
1425                                 list=[(_("stop recording"), "stop"), \
1426                                 (_("change recording (duration)"), "changeduration"), \
1427                                 (_("change recording (endtime)"), "changeendtime"), \
1428                                 (_("add recording (indefinitely)"), "indefinitely"), \
1429                                 (_("add recording (stop after current event)"), "event"), \
1430                                 (_("add recording (enter recording duration)"), "manualduration"), \
1431                                 (_("add recording (enter recording endtime)"), "manualendtime"), \
1432                                 (_("do nothing"), "no")])
1433                 else:
1434                         self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1435                                 title=_("Start recording?"), \
1436                                 list=[(_("add recording (indefinitely)"), "indefinitely"), \
1437                                 (_("add recording (stop after current event)"), "event"), \
1438                                 (_("add recording (enter recording duration)"), "manualduration"), \
1439                                 (_("add recording (enter recording endtime)"), "manualendtime"), \
1440                                 (_("don't record"), "no")])
1441
1442 from Tools.ISO639 import LanguageCodes
1443
1444 class InfoBarAudioSelection:
1445         def __init__(self):
1446                 self["AudioSelectionAction"] = HelpableActionMap(self, "InfobarAudioSelectionActions", 
1447                         {
1448                                 "audioSelection": (self.audioSelection, _("Audio Options...")),
1449                         })
1450
1451         def audioSelection(self):
1452                 service = self.session.nav.getCurrentService()
1453                 audio = service and service.audioTracks()
1454                 self.audioTracks = audio
1455                 n = audio and audio.getNumberOfTracks() or 0
1456                 keys = [ "red", "", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] + [""]*n
1457                 tlist = []
1458                 print "tlist:", tlist
1459                 if n > 0:
1460                         self.audioChannel = service.audioChannel()
1461
1462                         for x in range(n):
1463                                 i = audio.getTrackInfo(x)
1464                                 language = i.getLanguage()
1465                                 description = i.getDescription()
1466
1467                                 if LanguageCodes.has_key(language):
1468                                         language = LanguageCodes[language][0]
1469
1470                                 if len(description):
1471                                         description += " (" + language + ")"
1472                                 else:
1473                                         description = language
1474
1475                                 tlist.append((description, x))
1476
1477                         selectedAudio = tlist[0][1]
1478                         tlist.sort(lambda x,y : cmp(x[0], y[0]))
1479
1480                         selection = 2
1481                         for x in tlist:
1482                                 if x[1] != selectedAudio:
1483                                         selection += 1
1484                                 else:
1485                                         break
1486
1487                         tlist = [([_("Left"), _("Stereo"), _("Right")][self.audioChannel.getCurrentChannel()], "mode"), ("--", "")] + tlist
1488                         self.session.openWithCallback(self.audioSelected, ChoiceBox, title=_("Select audio track"), list = tlist, selection = selection, keys = keys)
1489                 else:
1490                         del self.audioTracks
1491
1492         def audioSelected(self, audio):
1493                 if audio is not None:
1494                         if isinstance(audio[1], str):
1495                                 if audio[1] == "mode":
1496                                         keys = ["red", "green", "yellow"]
1497                                         selection = self.audioChannel.getCurrentChannel()
1498                                         tlist = [(_("left"), 0), (_("stereo"), 1), (_("right"), 2)]
1499                                         self.session.openWithCallback(self.modeSelected, ChoiceBox, title=_("Select audio mode"), list = tlist, selection = selection, keys = keys)
1500                         else:
1501                                 del self.audioChannel
1502                                 if self.session.nav.getCurrentService().audioTracks().getNumberOfTracks() > audio[1]:
1503                                         self.audioTracks.selectTrack(audio[1])
1504                 else:
1505                         del self.audioChannel
1506                 del self.audioTracks
1507
1508         def modeSelected(self, mode):
1509                 if mode is not None:
1510                         self.audioChannel.selectChannel(mode[1])
1511                 del self.audioChannel
1512
1513 class InfoBarSubserviceSelection:
1514         def __init__(self):
1515                 self["SubserviceSelectionAction"] = HelpableActionMap(self, "InfobarSubserviceSelectionActions",
1516                         {
1517                                 "subserviceSelection": (self.subserviceSelection, _("Subservice list...")),
1518                         })
1519
1520                 self["SubserviceQuickzapAction"] = HelpableActionMap(self, "InfobarSubserviceQuickzapActions",
1521                         {
1522                                 "nextSubservice": (self.nextSubservice, _("Switch to next subservice")),
1523                                 "prevSubservice": (self.prevSubservice, _("Switch to previous subservice"))
1524                         }, -1)
1525                 self["SubserviceQuickzapAction"].setEnabled(False)
1526
1527                 self.session.nav.event.append(self.checkSubservicesAvail) # we like to get service events
1528
1529                 self.bsel = None
1530
1531         def checkSubservicesAvail(self, ev):
1532                 if ev == iPlayableService.evUpdatedEventInfo:
1533                         service = self.session.nav.getCurrentService()
1534                         subservices = service and service.subServices()
1535                         if not subservices or subservices.getNumberOfSubservices() == 0:
1536                                 self["SubserviceQuickzapAction"].setEnabled(False)
1537
1538         def nextSubservice(self):
1539                 self.changeSubservice(+1)
1540
1541         def prevSubservice(self):
1542                 self.changeSubservice(-1)
1543
1544         def changeSubservice(self, direction):
1545                 service = self.session.nav.getCurrentService()
1546                 subservices = service and service.subServices()
1547                 n = subservices and subservices.getNumberOfSubservices()
1548                 if n and n > 0:
1549                         selection = -1
1550                         ref = self.session.nav.getCurrentlyPlayingServiceReference()
1551                         for x in range(n):
1552                                 if subservices.getSubservice(x).toString() == ref.toString():
1553                                         selection = x
1554                         if selection != -1:
1555                                 selection += direction
1556                                 if selection >= n:
1557                                         selection=0
1558                                 elif selection < 0:
1559                                         selection=n-1
1560                                 newservice = subservices.getSubservice(selection)
1561                                 if newservice.valid():
1562                                         del subservices
1563                                         del service
1564                                         self.session.nav.playService(newservice)
1565
1566         def subserviceSelection(self):
1567                 service = self.session.nav.getCurrentService()
1568                 subservices = service and service.subServices()
1569                 self.bouquets = self.servicelist.getBouquetList()
1570                 n = subservices and subservices.getNumberOfSubservices()
1571                 selection = 0
1572                 if n and n > 0:
1573                         ref = self.session.nav.getCurrentlyPlayingServiceReference()
1574                         tlist = []
1575                         for x in range(n):
1576                                 i = subservices.getSubservice(x)
1577                                 if i.toString() == ref.toString():
1578                                         selection = x
1579                                 tlist.append((i.getName(), i))
1580
1581                         if self.bouquets and len(self.bouquets):
1582                                 keys = ["red", "green", "",  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1583                                 if config.usage.multibouquet.value:
1584                                         tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to bouquet"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1585                                 else:
1586                                         tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to favourites"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1587                                 selection += 3
1588                         else:
1589                                 tlist = [(_("Quickzap"), "quickzap", service.subServices()), ("--", "")] + tlist
1590                                 keys = ["red", "",  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1591                                 selection += 2
1592
1593                         self.session.openWithCallback(self.subserviceSelected, ChoiceBox, title=_("Please select a subservice..."), list = tlist, selection = selection, keys = keys)
1594
1595         def subserviceSelected(self, service):
1596                 del self.bouquets
1597                 if not service is None:
1598                         if isinstance(service[1], str):
1599                                 if service[1] == "quickzap":
1600                                         from Screens.SubservicesQuickzap import SubservicesQuickzap
1601                                         self.session.open(SubservicesQuickzap, service[2])
1602                         else:
1603                                 self["SubserviceQuickzapAction"].setEnabled(True)
1604                                 self.session.nav.playService(service[1])
1605
1606         def addSubserviceToBouquetCallback(self, service):
1607                 if len(service) > 1 and isinstance(service[1], eServiceReference):
1608                         self.selectedSubservice = service
1609                         if self.bouquets is None:
1610                                 cnt = 0
1611                         else:
1612                                 cnt = len(self.bouquets)
1613                         if cnt > 1: # show bouquet list
1614                                 self.bsel = self.session.openWithCallback(self.bouquetSelClosed, BouquetSelector, self.bouquets, self.addSubserviceToBouquet)
1615                         elif cnt == 1: # add to only one existing bouquet
1616                                 self.addSubserviceToBouquet(self.bouquets[0][1])
1617                                 self.session.open(MessageBox, _("Service has been added to the favourites."), MessageBox.TYPE_INFO)
1618
1619         def bouquetSelClosed(self, confirmed):
1620                 self.bsel = None
1621                 del self.selectedSubservice
1622                 if confirmed:
1623                         self.session.open(MessageBox, _("Service has been added to the selected bouquet."), MessageBox.TYPE_INFO)
1624
1625         def addSubserviceToBouquet(self, dest):
1626                 self.servicelist.addServiceToBouquet(dest, self.selectedSubservice[1])
1627                 if self.bsel:
1628                         self.bsel.close(True)
1629                 else:
1630                         del self.selectedSubservice
1631
1632 class InfoBarAdditionalInfo:
1633         def __init__(self):
1634                 self["NimA"] = Pixmap()
1635                 self["NimB"] = Pixmap()
1636                 self["NimA_Active"] = Pixmap()
1637                 self["NimB_Active"] = Pixmap()
1638
1639                 self["RecordingPossible"] = Boolean(fixed=harddiskmanager.HDDCount() > 0)
1640                 self["TimeshiftPossible"] = self["RecordingPossible"]
1641                 self["ExtensionsAvailable"] = Boolean(fixed=1)
1642
1643                 self.session.nav.event.append(self.gotServiceEvent) # we like to get service events
1644                 res_mgr = eDVBResourceManager.getInstance()
1645                 if res_mgr:
1646                         res_mgr.frontendUseMaskChanged.get().append(self.tunerUseMaskChanged)
1647
1648         def tunerUseMaskChanged(self, mask):
1649                 if mask&1:
1650                         self["NimA_Active"].show()
1651                 else:
1652                         self["NimA_Active"].hide()
1653                 if mask&2:
1654                         self["NimB_Active"].show()
1655                 else:
1656                         self["NimB_Active"].hide()
1657
1658         def checkTunerState(self, service):
1659                 info = service and service.frontendInfo()
1660                 feNumber = info and info.getFrontendInfo(iFrontendInformation.frontendNumber)
1661                 if feNumber is None:
1662                         self["NimA"].hide()
1663                         self["NimB"].hide()
1664                 elif feNumber == 0:
1665                         self["NimB"].hide()
1666                         self["NimA"].show()
1667                 elif feNumber == 1:
1668                         self["NimA"].hide()
1669                         self["NimB"].show()
1670
1671         def gotServiceEvent(self, ev):
1672                 service = self.session.nav.getCurrentService()
1673                 if ev == iPlayableService.evUpdatedInfo or ev == iPlayableService.evEnd:
1674                         self.checkTunerState(service)
1675
1676 class InfoBarNotifications:
1677         def __init__(self):
1678                 self.onExecBegin.append(self.checkNotifications)
1679                 Notifications.notificationAdded.append(self.checkNotificationsIfExecing)
1680                 self.onClose.append(self.__removeNotification)
1681
1682         def __removeNotification(self):
1683                 Notifications.notificationAdded.remove(self.checkNotificationsIfExecing)
1684
1685         def checkNotificationsIfExecing(self):
1686                 if self.execing:
1687                         self.checkNotifications()
1688
1689         def checkNotifications(self):
1690                 if len(Notifications.notifications):
1691                         n = Notifications.notifications[0]
1692
1693                         Notifications.notifications = Notifications.notifications[1:]
1694                         cb = n[0]
1695
1696                         if n[3].has_key("onSessionOpenCallback"):
1697                                 n[3]["onSessionOpenCallback"]()
1698                                 del n[3]["onSessionOpenCallback"]
1699
1700                         if cb is not None:
1701                                 dlg = self.session.openWithCallback(cb, n[1], *n[2], **n[3])
1702                         else:
1703                                 dlg = self.session.open(n[1], *n[2], **n[3])
1704
1705                         # remember that this notification is currently active
1706                         d = (n[4], dlg)
1707                         Notifications.current_notifications.append(d)
1708                         dlg.onClose.append(boundFunction(self.__notificationClosed, d))
1709
1710         def __notificationClosed(self, d):
1711                 Notifications.current_notifications.remove(d)
1712
1713 class InfoBarServiceNotifications:
1714         def __init__(self):
1715                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1716                         {
1717                                 iPlayableService.evEnd: self.serviceHasEnded
1718                         })
1719
1720         def serviceHasEnded(self):
1721                 print "service end!"
1722
1723                 try:
1724                         self.setSeekState(self.SEEK_STATE_PLAY)
1725                 except:
1726                         pass
1727
1728 class InfoBarCueSheetSupport:
1729         CUT_TYPE_IN = 0
1730         CUT_TYPE_OUT = 1
1731         CUT_TYPE_MARK = 2
1732         CUT_TYPE_LAST = 3
1733
1734         ENABLE_RESUME_SUPPORT = False
1735
1736         def __init__(self, actionmap = "InfobarCueSheetActions"):
1737                 self["CueSheetActions"] = HelpableActionMap(self, actionmap, 
1738                         {
1739                                 "jumpPreviousMark": (self.jumpPreviousMark, _("jump to previous marked position")),
1740                                 "jumpNextMark": (self.jumpNextMark, _("jump to next marked position")),
1741                                 "toggleMark": (self.toggleMark, _("toggle a cut mark at the current position"))
1742                         }, prio=1) 
1743
1744                 self.cut_list = [ ]
1745                 self.is_closing = False
1746                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1747                         {
1748                                 iPlayableService.evStart: self.__serviceStarted,
1749                         })
1750
1751         def __serviceStarted(self):
1752                 if self.is_closing:
1753                         return
1754                 print "new service started! trying to download cuts!"
1755                 self.downloadCuesheet()
1756
1757                 if self.ENABLE_RESUME_SUPPORT:
1758                         last = None
1759
1760                         for (pts, what) in self.cut_list:
1761                                 if what == self.CUT_TYPE_LAST:
1762                                         last = pts
1763
1764                         if last is not None:
1765                                 self.resume_point = last
1766                                 Notifications.AddNotificationWithCallback(self.playLastCB, MessageBox, _("Do you want to resume this playback?"), timeout=10)
1767
1768         def playLastCB(self, answer):
1769                 if answer == True:
1770                         seekable = self.__getSeekable()
1771                         if seekable is not None:
1772                                 seekable.seekTo(self.resume_point)
1773                 self.hideAfterResume()
1774         
1775         def hideAfterResume(self):
1776                 if isinstance(self, InfoBarShowHide):
1777                         self.hide()
1778
1779         def __getSeekable(self):
1780                 service = self.session.nav.getCurrentService()
1781                 if service is None:
1782                         return None
1783                 return service.seek()
1784
1785         def cueGetCurrentPosition(self):
1786                 seek = self.__getSeekable()
1787                 if seek is None:
1788                         return None
1789                 r = seek.getPlayPosition()
1790                 if r[0]:
1791                         return None
1792                 return long(r[1])
1793
1794         def jumpPreviousNextMark(self, cmp, alternative=None):
1795                 current_pos = self.cueGetCurrentPosition()
1796                 if current_pos is None:
1797                         return
1798                 mark = self.getNearestCutPoint(current_pos, cmp=cmp)
1799                 if mark is not None:
1800                         pts = mark[0]
1801                 elif alternative is not None:
1802                         pts = alternative
1803                 else:
1804                         return
1805
1806                 seekable = self.__getSeekable()
1807                 if seekable is not None:
1808                         seekable.seekTo(pts)
1809
1810         def jumpPreviousMark(self):
1811                 # we add 2 seconds, so if the play position is <2s after
1812                 # the mark, the mark before will be used
1813                 self.jumpPreviousNextMark(lambda x: -x-5*90000, alternative=0)
1814
1815         def jumpNextMark(self):
1816                 self.jumpPreviousNextMark(lambda x: x)
1817
1818         def getNearestCutPoint(self, pts, cmp=abs):
1819                 # can be optimized
1820                 nearest = None
1821                 for cp in self.cut_list:
1822                         diff = cmp(cp[0] - pts)
1823                         if cp[1] == self.CUT_TYPE_MARK and diff >= 0 and (nearest is None or cmp(nearest[0] - pts) > diff):
1824                                 nearest = cp
1825                 return nearest
1826
1827         def toggleMark(self, onlyremove=False, onlyadd=False, tolerance=5*90000, onlyreturn=False):
1828                 current_pos = self.cueGetCurrentPosition()
1829                 if current_pos is None:
1830                         print "not seekable"
1831                         return
1832
1833                 nearest_cutpoint = self.getNearestCutPoint(current_pos)
1834
1835                 if nearest_cutpoint is not None and abs(nearest_cutpoint[0] - current_pos) < tolerance:
1836                         if onlyreturn:
1837                                 return nearest_cutpoint
1838                         if not onlyadd:
1839                                 self.removeMark(nearest_cutpoint)
1840                 elif not onlyremove and not onlyreturn:
1841                         self.addMark((current_pos, self.CUT_TYPE_MARK))
1842
1843                 if onlyreturn:
1844                         return None
1845
1846         def addMark(self, point):
1847                 insort(self.cut_list, point)
1848                 self.uploadCuesheet()
1849                 self.showAfterCuesheetOperation()
1850
1851         def removeMark(self, point):
1852                 self.cut_list.remove(point)
1853                 self.uploadCuesheet()
1854                 self.showAfterCuesheetOperation()
1855
1856         def showAfterCuesheetOperation(self):
1857                 if isinstance(self, InfoBarShowHide):
1858                         self.doShow()
1859
1860         def __getCuesheet(self):
1861                 service = self.session.nav.getCurrentService()
1862                 if service is None:
1863                         return None
1864                 return service.cueSheet()
1865
1866         def uploadCuesheet(self):
1867                 cue = self.__getCuesheet()
1868
1869                 if cue is None:
1870                         print "upload failed, no cuesheet interface"
1871                         return
1872                 cue.setCutList(self.cut_list)
1873
1874         def downloadCuesheet(self):
1875                 cue = self.__getCuesheet()
1876
1877                 if cue is None:
1878                         print "download failed, no cuesheet interface"
1879                         self.cut_list = [ ]
1880                 else:
1881                         self.cut_list = cue.getCutList()
1882
1883 class InfoBarSummary(Screen):
1884         skin = """
1885         <screen position="0,0" size="132,64">
1886                 <widget source="CurrentTime" render="Label" position="56,46" size="82,18" font="Regular;16" >
1887                         <convert type="ClockToText">WithSeconds</convert>
1888                 </widget>
1889                 <widget source="CurrentService" render="Label" position="6,4" size="120,42" font="Regular;18" >
1890                         <convert type="ServiceName">Name</convert>
1891                 </widget>
1892         </screen>"""
1893
1894         def __init__(self, session, parent):
1895                 Screen.__init__(self, session)
1896                 self["CurrentService"] = CurrentService(self.session.nav)
1897                 self["CurrentTime"] = Clock()
1898
1899 class InfoBarSummarySupport:
1900         def __init__(self):
1901                 pass
1902
1903         def createSummary(self):
1904                 return InfoBarSummary
1905
1906 class InfoBarTeletextPlugin:
1907         def __init__(self):
1908                 self.teletext_plugin = None
1909
1910                 for p in plugins.getPlugins(PluginDescriptor.WHERE_TELETEXT):
1911                         self.teletext_plugin = p
1912
1913                 if self.teletext_plugin is not None:
1914                         self["TeletextActions"] = HelpableActionMap(self, "InfobarTeletextActions",
1915                                 {
1916                                         "startTeletext": (self.startTeletext, _("View teletext..."))
1917                                 })
1918                 else:
1919                         print "no teletext plugin found!"
1920
1921         def startTeletext(self):
1922                 self.teletext_plugin(session=self.session, service=self.session.nav.getCurrentService())
1923
1924 class InfoBarSubtitleSupport(object):
1925         def __init__(self):
1926                 object.__init__(self)
1927                 self.subtitle_window = self.session.instantiateDialog(SubtitleDisplay)
1928                 self.__subtitles_enabled = False
1929
1930                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1931                         {
1932                                 iPlayableService.evEnd: self.__serviceStopped,
1933                                 iPlayableService.evUpdatedInfo: self.__updatedInfo
1934                         })
1935                 self.cached_subtitle_checked = False
1936                 self.__selected_subtitle = None
1937
1938         def __serviceStopped(self):
1939                 self.subtitle_window.hide()
1940                 self.__subtitles_enabled = False
1941                 self.cached_subtitle_checked = False
1942
1943         def __updatedInfo(self):
1944                 if not self.cached_subtitle_checked:
1945                         subtitle = self.getCurrentServiceSubtitle()
1946                         self.cached_subtitle_checked = True
1947                         self.__selected_subtitle = subtitle and subtitle.getCachedSubtitle()
1948                         if self.__selected_subtitle:
1949                                 subtitle.enableSubtitles(self.subtitle_window.instance, self.selected_subtitle)
1950                                 self.subtitle_window.show()
1951                                 self.__subtitles_enabled = True
1952
1953         def getCurrentServiceSubtitle(self):
1954                 service = self.session.nav.getCurrentService()
1955                 return service and service.subtitle()
1956
1957         def setSubtitlesEnable(self, enable=True):
1958                 subtitle = self.getCurrentServiceSubtitle()
1959                 if enable and self.__selected_subtitle is not None:
1960                         if subtitle and not self.__subtitles_enabled:
1961                                 subtitle.enableSubtitles(self.subtitle_window.instance, self.selected_subtitle)
1962                                 self.subtitle_window.show()
1963                                 self.__subtitles_enabled = True
1964                 else:
1965                         if subtitle:
1966                                 subtitle.disableSubtitles(self.subtitle_window.instance)
1967                         self.__subtitles_enabled = False
1968                         self.subtitle_window.hide()
1969
1970         def setSelectedSubtitle(self, subtitle):
1971                 self.__selected_subtitle = subtitle
1972
1973         subtitles_enabled = property(lambda self: self.__subtitles_enabled, setSubtitlesEnable)
1974         selected_subtitle = property(lambda self: self.__selected_subtitle, setSelectedSubtitle)
1975
1976 class InfoBarServiceErrorPopupSupport:
1977         def __init__(self):
1978                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1979                         {
1980                                 iPlayableService.evTuneFailed: self.__tuneFailed,
1981                                 iPlayableService.evStart: self.__serviceStarted
1982                         })
1983                 self.__serviceStarted()
1984
1985         def __serviceStarted(self):
1986                 self.last_error = None
1987                 Notifications.RemovePopup(id = "ZapError")
1988
1989         def __tuneFailed(self):
1990                 service = self.session.nav.getCurrentService()
1991                 info = service and service.info()
1992                 error = info and info.getInfo(iServiceInformation.sDVBState)
1993
1994                 if error == self.last_error:
1995                         error = None
1996                 else:
1997                         self.last_error = error
1998
1999                 errors = {
2000                         eDVBServicePMTHandler.eventNoResources: _("No free tuner!"),
2001                         eDVBServicePMTHandler.eventTuneFailed: _("Tune failed!"),
2002                         eDVBServicePMTHandler.eventNoPAT: _("No data on transponder!\n(Timeout reading PAT)"),
2003                         eDVBServicePMTHandler.eventNoPATEntry: _("Service not found!\n(SID not found in PAT)"),
2004                         eDVBServicePMTHandler.eventNoPMT: _("Service invalid!\n(Timeout reading PMT)"),
2005                         eDVBServicePMTHandler.eventNewProgramInfo: None,
2006                         eDVBServicePMTHandler.eventTuned: None,
2007                         eDVBServicePMTHandler.eventSOF: None,
2008                         eDVBServicePMTHandler.eventEOF: None
2009                 }
2010
2011                 error = errors.get(error) #this returns None when the key not exist in the dict
2012
2013                 if error is not None:
2014                         Notifications.AddPopup(text = error, type = MessageBox.TYPE_ERROR, timeout = 5, id = "ZapError")
2015                 else:
2016                         Notifications.RemovePopup(id = "ZapError")