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