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