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