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