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