add eDVBCiInterfaces::readCICaids
[enigma2.git] / lib / python / Screens / InfoBarGenerics.py
1 from ChannelSelection import ChannelSelection, BouquetSelector
2
3 from Components.ActionMap import ActionMap, HelpableActionMap
4 from Components.ActionMap import NumberActionMap
5 from Components.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                 self.eofState = 0
627                 self.eofTimer = eTimer()
628                 self.eofTimer.timeout.get().append(self.doEof)
629                 self.eofInhibitTimer = eTimer()
630                 self.eofInhibitTimer.timeout.get().append(self.inhibitEof)
631
632                 self.minSpeedBackward = useSeekBackHack and 16 or 0
633
634                 class InfoBarSeekActionMap(HelpableActionMap):
635                         def __init__(self, screen, *args, **kwargs):
636                                 HelpableActionMap.__init__(self, screen, *args, **kwargs)
637                                 self.screen = screen
638
639                         def action(self, contexts, action):
640                                 print "action:", action
641                                 if action[:5] == "seek:":
642                                         time = int(action[5:])
643                                         self.screen.doSeekRelative(time * 90000)
644                                         return 1
645                                 elif action[:8] == "seekdef:":
646                                         key = int(action[8:])
647                                         time = (-config.seek.selfdefined_13.value, False, config.seek.selfdefined_13.value,
648                                                 -config.seek.selfdefined_46.value, False, config.seek.selfdefined_46.value,
649                                                 -config.seek.selfdefined_79.value, False, config.seek.selfdefined_79.value)[key-1]
650                                         self.screen.doSeekRelative(time * 90000)
651                                         return 1                                        
652                                 else:
653                                         return HelpableActionMap.action(self, contexts, action)
654
655                 self["SeekActions"] = InfoBarSeekActionMap(self, actionmap,
656                         {
657                                 "playpauseService": self.playpauseService,
658                                 "pauseService": (self.pauseService, _("pause")),
659                                 "unPauseService": (self.unPauseService, _("continue")),
660
661                                 "seekFwd": (self.seekFwd, _("skip forward")),
662                                 "seekFwdManual": (self.seekFwdManual, _("skip forward (enter time)")),
663                                 "seekBack": (self.seekBack, _("skip backward")),
664                                 "seekBackManual": (self.seekBackManual, _("skip backward (enter time)"))
665                         }, prio=-1)
666                         # give them a little more priority to win over color buttons
667
668                 self["SeekActions"].setEnabled(False)
669
670                 self.seekstate = self.SEEK_STATE_PLAY
671                 self.lastseekstate = self.SEEK_STATE_PLAY
672
673                 self.onPlayStateChanged = [ ]
674
675                 self.lockedBecauseOfSkipping = False
676
677                 self.__seekableStatusChanged()
678
679         def makeStateForward(self, n):
680                 minspeed = config.seek.stepwise_minspeed.value
681                 repeat = int(config.seek.stepwise_repeat.value)
682                 if minspeed != "Never" and n >= int(minspeed) and repeat > 1:
683                         return (0, n * repeat, repeat, ">> %dx" % n)
684                 else:
685                         return (0, n, 0, ">> %dx" % n)
686
687         def makeStateBackward(self, n):
688                 minspeed = config.seek.stepwise_minspeed.value
689                 repeat = int(config.seek.stepwise_repeat.value)
690                 if self.minSpeedBackward and n < self.minSpeedBackward:
691                         r = (self.minSpeedBackward - 1)/ n + 1
692                         if minspeed != "Never" and n >= int(minspeed) and repeat > 1:
693                                 r = max(r, repeat)
694                         return (0, -n * r, r, "<< %dx" % n)
695                 elif minspeed != "Never" and n >= int(minspeed) and repeat > 1:
696                         return (0, -n * repeat, repeat, "<< %dx" % n)
697                 else:
698                         return (0, -n, 0, "<< %dx" % n)
699
700         def makeStateSlowMotion(self, n):
701                 return (0, 0, n, "/%d" % n)
702
703         def isStateForward(self, state):
704                 return state[1] > 1
705
706         def isStateBackward(self, state):
707                 return state[1] < 0
708
709         def isStateSlowMotion(self, state):
710                 return state[1] == 0 and state[2] > 1
711
712         def getHigher(self, n, lst):
713                 for x in lst:
714                         if x > n:
715                                 return x
716                 return False
717
718         def getLower(self, n, lst):
719                 lst = lst[:]
720                 lst.reverse()
721                 for x in lst:
722                         if x < n:
723                                 return x
724                 return False
725
726         def showAfterSeek(self):
727                 if isinstance(self, InfoBarShowHide):
728                         self.doShow()
729
730         def up(self):
731                 pass
732
733         def down(self):
734                 pass
735
736         def getSeek(self):
737                 service = self.session.nav.getCurrentService()
738                 if service is None:
739                         return None
740
741                 seek = service.seek()
742
743                 if seek is None or not seek.isCurrentlySeekable():
744                         return None
745
746                 return seek
747
748         def isSeekable(self):
749                 if self.getSeek() is None:
750                         return False
751                 return True
752
753         def __seekableStatusChanged(self):
754 #               print "seekable status changed!"
755                 if not self.isSeekable():
756                         self["SeekActions"].setEnabled(False)
757 #                       print "not seekable, return to play"
758                         self.setSeekState(self.SEEK_STATE_PLAY)
759                 else:
760                         self["SeekActions"].setEnabled(True)
761 #                       print "seekable"
762
763         def __serviceStarted(self):
764                 self.seekstate = self.SEEK_STATE_PLAY
765                 self.__seekableStatusChanged()
766                 if self.eofState != 0:
767                         self.eofTimer.stop()
768                 self.eofState = 0
769
770         def setSeekState(self, state):
771                 service = self.session.nav.getCurrentService()
772
773                 if service is None:
774                         return False
775
776                 if not self.isSeekable():
777                         if state not in (self.SEEK_STATE_PLAY, self.SEEK_STATE_PAUSE):
778                                 state = self.SEEK_STATE_PLAY
779
780                 pauseable = service.pause()
781
782                 if pauseable is None:
783                         print "not pauseable."
784                         state = self.SEEK_STATE_PLAY
785
786                 oldstate = self.seekstate
787                 self.seekstate = state
788
789                 for i in (0, 1, 2):
790                         if oldstate[i] != self.seekstate[i]:
791                                 (self.session.nav.pause, pauseable.setFastForward, pauseable.setSlowMotion)[i](self.seekstate[i])
792
793                 for c in self.onPlayStateChanged:
794                         c(self.seekstate)
795
796                 self.checkSkipShowHideLock()
797
798                 return True
799
800         def playpauseService(self):
801                 if self.seekstate != self.SEEK_STATE_PLAY:
802                         self.unPauseService()
803                 else:
804                         self.pauseService()
805
806         def pauseService(self):
807                 if self.seekstate == self.SEEK_STATE_PAUSE:
808                         if config.seek.on_pause.value == "play":
809                                 self.unPauseService()
810                         elif config.seek.on_pause.value == "step":
811                                 self.doSeekRelative(0)
812                         elif config.seek.on_pause.value == "last":
813                                 self.setSeekState(self.lastseekstate)
814                                 self.lastseekstate = self.SEEK_STATE_PLAY
815                 else:
816                         if self.seekstate != self.SEEK_STATE_EOF:
817                                 self.lastseekstate = self.seekstate
818                         self.setSeekState(self.SEEK_STATE_PAUSE);
819
820         def unPauseService(self):
821                 print "unpause"
822                 if self.seekstate == self.SEEK_STATE_PLAY:
823                         return 0
824                 self.setSeekState(self.SEEK_STATE_PLAY)
825
826         def doSeek(self, pts):
827                 seekable = self.getSeek()
828                 if seekable is None:
829                         return
830                 prevstate = self.seekstate
831                 if self.eofState == 1:
832                         self.eofState = 2
833                         self.inhibitEof()
834                 if self.seekstate == self.SEEK_STATE_EOF:
835                         if prevstate == self.SEEK_STATE_PAUSE:
836                                 self.setSeekState(self.SEEK_STATE_PAUSE)
837                         else:
838                                 self.setSeekState(self.SEEK_STATE_PLAY)
839                 self.eofInhibitTimer.start(200, True)
840                 seekable.seekTo(pts)
841
842         def doSeekRelative(self, pts):
843                 seekable = self.getSeek()
844                 if seekable is None:
845                         return
846                 prevstate = self.seekstate
847                 if self.eofState == 1:
848                         self.eofState = 2
849                         self.inhibitEof()
850                 if self.seekstate == self.SEEK_STATE_EOF:
851                         if prevstate == self.SEEK_STATE_PAUSE:
852                                 self.setSeekState(self.SEEK_STATE_PAUSE)
853                         else:
854                                 self.setSeekState(self.SEEK_STATE_PLAY)
855                 self.eofInhibitTimer.start(200, True)
856                 seekable.seekRelative(pts<0 and -1 or 1, abs(pts))
857                 if abs(pts) > 100 and config.usage.show_infobar_on_skip.value:
858                         self.showAfterSeek()
859
860         def seekFwd(self):
861                 if self.seekstate == self.SEEK_STATE_PLAY:
862                         self.setSeekState(self.makeStateForward(int(config.seek.enter_forward.value)))
863                 elif self.seekstate == self.SEEK_STATE_PAUSE:
864                         if len(config.seek.speeds_slowmotion.value):
865                                 self.setSeekState(self.makeStateSlowMotion(config.seek.speeds_slowmotion.value[-1]))
866                         else:
867                                 self.setSeekState(self.makeStateForward(int(config.seek.enter_forward.value)))
868                 elif self.seekstate == self.SEEK_STATE_EOF:
869                         pass
870                 elif self.isStateForward(self.seekstate):
871                         speed = self.seekstate[1]
872                         if self.seekstate[2]:
873                                 speed /= self.seekstate[2]
874                         speed = self.getHigher(speed, config.seek.speeds_forward.value) or config.seek.speeds_forward.value[-1]
875                         self.setSeekState(self.makeStateForward(speed))
876                 elif self.isStateBackward(self.seekstate):
877                         speed = -self.seekstate[1]
878                         if self.seekstate[2]:
879                                 speed /= self.seekstate[2]
880                         speed = self.getLower(speed, config.seek.speeds_backward.value)
881                         if speed:
882                                 self.setSeekState(self.makeStateBackward(speed))
883                         else:
884                                 self.setSeekState(self.SEEK_STATE_PLAY)
885                 elif self.isStateSlowMotion(self.seekstate):
886                         speed = self.getLower(self.seekstate[2], config.seek.speeds_slowmotion.value) or config.seek.speeds_slowmotion.value[0]
887                         self.setSeekState(self.makeStateSlowMotion(speed))
888
889         def seekBack(self):
890                 seekstate = self.seekstate
891                 if seekstate == self.SEEK_STATE_PLAY:
892                         self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
893                 elif seekstate == self.SEEK_STATE_EOF:
894                         self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
895                         self.doSeekRelative(-6)
896                 elif seekstate == self.SEEK_STATE_PAUSE:
897                         self.doSeekRelative(-3)
898                 elif self.isStateForward(seekstate):
899                         speed = seekstate[1]
900                         if seekstate[2]:
901                                 speed /= seekstate[2]
902                         speed = self.getLower(speed, config.seek.speeds_forward.value)
903                         if speed:
904                                 self.setSeekState(self.makeStateForward(speed))
905                         else:
906                                 self.setSeekState(self.SEEK_STATE_PLAY)
907                 elif self.isStateBackward(seekstate):
908                         speed = -seekstate[1]
909                         if seekstate[2]:
910                                 speed /= seekstate[2]
911                         speed = self.getHigher(speed, config.seek.speeds_backward.value) or config.seek.speeds_backward.value[-1]
912                         self.setSeekState(self.makeStateBackward(speed))
913                 elif self.isStateSlowMotion(seekstate):
914                         speed = self.getHigher(seekstate[2], config.seek.speeds_slowmotion.value)
915                         if speed:
916                                 self.setSeekState(self.makeStateSlowMotion(speed))
917                         else:
918                                 self.setSeekState(self.SEEK_STATE_PAUSE)
919
920         def seekFwdManual(self):
921                 self.session.openWithCallback(self.fwdSeekTo, MinuteInput)
922
923         def fwdSeekTo(self, minutes):
924                 print "Seek", minutes, "minutes forward"
925                 self.doSeekRelative(minutes * 60 * 90000)
926
927         def seekBackManual(self):
928                 self.session.openWithCallback(self.rwdSeekTo, MinuteInput)
929
930         def rwdSeekTo(self, minutes):
931                 print "rwdSeekTo"
932                 self.doSeekRelative(-minutes * 60 * 90000)
933
934         def checkSkipShowHideLock(self):
935                 wantlock = self.seekstate != self.SEEK_STATE_PLAY
936
937                 if config.usage.show_infobar_on_skip.value:
938                         if self.lockedBecauseOfSkipping and not wantlock:
939                                 self.unlockShow()
940                                 self.lockedBecauseOfSkipping = False
941
942                         if wantlock and not self.lockedBecauseOfSkipping:
943                                 self.lockShow()
944                                 self.lockedBecauseOfSkipping = True
945
946         def calcRemainingTime(self):
947                 seekable = self.getSeek()
948                 if seekable is not None:
949                         len = seekable.getLength()
950                         try:
951                                 tmp = self.cueGetEndCutPosition()
952                                 if tmp:
953                                         len = [False, tmp]
954                         except:
955                                 pass
956                         pos = seekable.getPlayPosition()
957                         speednom = self.seekstate[1] or 1
958                         speedden = self.seekstate[2] or 1
959                         if not len[0] and not pos[0]:
960                                 if len[1] <= pos[1]:
961                                         return 0
962                                 time = (len[1] - pos[1])*speedden/(90*speednom)
963                                 return time
964                 return False
965                 
966         def __evEOF(self):
967                 if self.eofState == 0 and self.seekstate != self.SEEK_STATE_EOF:
968                         self.eofState = 1
969                         time = self.calcRemainingTime()
970                         if not time:
971                                 time = 3000   # Failed to calc, use default
972                         elif time == 0:
973                                 time = 300    # Passed end, shortest wait
974                         elif time > 15000:
975                                 self.eofState = -2  # Too long, block eof
976                                 time = 15000
977                         else:
978                                 time += 1000  # Add margin
979                         self.eofTimer.start(time, True)
980
981         def inhibitEof(self):
982                 if self.eofState >= 1:
983                         self.eofState = -self.eofState
984                         self.eofTimer.stop()
985                         self.doEof()
986
987         def doEof(self):
988                 if self.seekstate == self.SEEK_STATE_EOF:
989                         return
990                 if self.eofState == -2 or self.isStateBackward(self.seekstate):
991                         self.eofState = 0
992                         return
993
994                 # if we are seeking, we try to end up ~1s before the end, and pause there.
995                 eofstate = self.eofState
996                 seekstate = self.seekstate
997                 self.eofState = 0
998                 if not self.seekstate == self.SEEK_STATE_PAUSE:
999                         self.setSeekState(self.SEEK_STATE_EOF)
1000                 if eofstate == -1 or not seekstate in (self.SEEK_STATE_PLAY, self.SEEK_STATE_PAUSE):
1001                         seekable = self.getSeek()
1002                         if seekable is not None:
1003                                 seekable.seekTo(-1)
1004                 if eofstate == 1 and seekstate == self.SEEK_STATE_PLAY:
1005                         self.doEofInternal(True)
1006                 else:
1007                         self.doEofInternal(False)
1008
1009         def doEofInternal(self, playing):
1010                 pass            # Defined in subclasses
1011
1012         def __evSOF(self):
1013                 self.setSeekState(self.SEEK_STATE_PLAY)
1014                 self.doSeek(0)
1015
1016 from Screens.PVRState import PVRState, TimeshiftState
1017
1018 class InfoBarPVRState:
1019         def __init__(self, screen=PVRState, force_show = False):
1020                 self.onPlayStateChanged.append(self.__playStateChanged)
1021                 self.pvrStateDialog = self.session.instantiateDialog(screen)
1022                 self.onShow.append(self._mayShow)
1023                 self.onHide.append(self.pvrStateDialog.hide)
1024                 self.force_show = force_show
1025
1026         def _mayShow(self):
1027                 if self.execing and self.seekstate != self.SEEK_STATE_PLAY:
1028                         self.pvrStateDialog.show()
1029
1030         def __playStateChanged(self, state):
1031                 playstateString = state[3]
1032                 self.pvrStateDialog["state"].setText(playstateString)
1033                 
1034                 # if we return into "PLAY" state, ensure that the dialog gets hidden if there will be no infobar displayed
1035                 if not config.usage.show_infobar_on_skip.value and self.seekstate == self.SEEK_STATE_PLAY and not self.force_show:
1036                         self.pvrStateDialog.hide()
1037                 else:
1038                         self._mayShow()
1039                         
1040
1041 class InfoBarTimeshiftState(InfoBarPVRState):
1042         def __init__(self):
1043                 InfoBarPVRState.__init__(self, screen=TimeshiftState, force_show = True)
1044
1045         def _mayShow(self):
1046                 if self.execing and self.timeshift_enabled and self.seekstate != self.SEEK_STATE_PLAY:
1047                         self.pvrStateDialog.show()
1048
1049 class InfoBarShowMovies:
1050
1051         # i don't really like this class.
1052         # it calls a not further specified "movie list" on up/down/movieList,
1053         # so this is not more than an action map
1054         def __init__(self):
1055                 self["MovieListActions"] = HelpableActionMap(self, "InfobarMovieListActions",
1056                         {
1057                                 "movieList": (self.showMovies, _("movie list")),
1058                                 "up": (self.showMovies, _("movie list")),
1059                                 "down": (self.showMovies, _("movie list"))
1060                         })
1061
1062 # InfoBarTimeshift requires InfoBarSeek, instantiated BEFORE!
1063
1064 # Hrmf.
1065 #
1066 # Timeshift works the following way:
1067 #                                         demux0   demux1                    "TimeshiftActions" "TimeshiftActivateActions" "SeekActions"
1068 # - normal playback                       TUNER    unused      PLAY               enable                disable              disable
1069 # - user presses "yellow" button.         FILE     record      PAUSE              enable                disable              enable
1070 # - user presess pause again              FILE     record      PLAY               enable                disable              enable
1071 # - user fast forwards                    FILE     record      FF                 enable                disable              enable
1072 # - end of timeshift buffer reached       TUNER    record      PLAY               enable                enable               disable
1073 # - user backwards                        FILE     record      BACK  # !!         enable                disable              enable
1074 #
1075
1076 # in other words:
1077 # - when a service is playing, pressing the "timeshiftStart" button ("yellow") enables recording ("enables timeshift"),
1078 # freezes the picture (to indicate timeshift), sets timeshiftMode ("activates timeshift")
1079 # now, the service becomes seekable, so "SeekActions" are enabled, "TimeshiftEnableActions" are disabled.
1080 # - the user can now PVR around
1081 # - if it hits the end, the service goes into live mode ("deactivates timeshift", it's of course still "enabled")
1082 # the service looses it's "seekable" state. It can still be paused, but just to activate timeshift right
1083 # after!
1084 # the seek actions will be disabled, but the timeshiftActivateActions will be enabled
1085 # - if the user rewinds, or press pause, timeshift will be activated again
1086
1087 # note that a timeshift can be enabled ("recording") and
1088 # activated (currently time-shifting).
1089
1090 class InfoBarTimeshift:
1091         def __init__(self):
1092                 self["TimeshiftActions"] = HelpableActionMap(self, "InfobarTimeshiftActions",
1093                         {
1094                                 "timeshiftStart": (self.startTimeshift, _("start timeshift")),  # the "yellow key"
1095                                 "timeshiftStop": (self.stopTimeshift, _("stop timeshift"))      # currently undefined :), probably 'TV'
1096                         }, prio=1)
1097                 self["TimeshiftActivateActions"] = ActionMap(["InfobarTimeshiftActivateActions"],
1098                         {
1099                                 "timeshiftActivateEnd": self.activateTimeshiftEnd, # something like "rewind key"
1100                                 "timeshiftActivateEndAndPause": self.activateTimeshiftEndAndPause  # something like "pause key"
1101                         }, prio=-1) # priority over record
1102
1103                 self.timeshift_enabled = 0
1104                 self.timeshift_state = 0
1105                 self.ts_rewind_timer = eTimer()
1106                 self.ts_rewind_timer.callback.append(self.rewindService)
1107
1108                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1109                         {
1110                                 iPlayableService.evStart: self.__serviceStarted,
1111                                 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged
1112                         })
1113
1114         def getTimeshift(self):
1115                 service = self.session.nav.getCurrentService()
1116                 return service and service.timeshift()
1117
1118         def startTimeshift(self):
1119                 print "enable timeshift"
1120                 ts = self.getTimeshift()
1121                 if ts is None:
1122                         self.session.open(MessageBox, _("Timeshift not possible!"), MessageBox.TYPE_ERROR)
1123                         print "no ts interface"
1124                         return 0
1125
1126                 if self.timeshift_enabled:
1127                         print "hu, timeshift already enabled?"
1128                 else:
1129                         if not ts.startTimeshift():
1130                                 self.timeshift_enabled = 1
1131
1132                                 # we remove the "relative time" for now.
1133                                 #self.pvrStateDialog["timeshift"].setRelative(time.time())
1134
1135                                 # PAUSE.
1136                                 #self.setSeekState(self.SEEK_STATE_PAUSE)
1137                                 self.activateTimeshiftEnd(False)
1138
1139                                 # enable the "TimeshiftEnableActions", which will override
1140                                 # the startTimeshift actions
1141                                 self.__seekableStatusChanged()
1142                         else:
1143                                 print "timeshift failed"
1144
1145         def stopTimeshift(self):
1146                 if not self.timeshift_enabled:
1147                         return 0
1148                 print "disable timeshift"
1149                 ts = self.getTimeshift()
1150                 if ts is None:
1151                         return 0
1152                 self.session.openWithCallback(self.stopTimeshiftConfirmed, MessageBox, _("Stop Timeshift?"), MessageBox.TYPE_YESNO)
1153
1154         def stopTimeshiftConfirmed(self, confirmed):
1155                 if not confirmed:
1156                         return
1157
1158                 ts = self.getTimeshift()
1159                 if ts is None:
1160                         return
1161
1162                 ts.stopTimeshift()
1163                 self.timeshift_enabled = 0
1164
1165                 # disable actions
1166                 self.__seekableStatusChanged()
1167
1168         # activates timeshift, and seeks to (almost) the end
1169         def activateTimeshiftEnd(self, back = True):
1170                 ts = self.getTimeshift()
1171                 print "activateTimeshiftEnd"
1172
1173                 if ts is None:
1174                         return
1175
1176                 if ts.isTimeshiftActive():
1177                         print "!! activate timeshift called - but shouldn't this be a normal pause?"
1178                         self.pauseService()
1179                 else:
1180                         print "play, ..."
1181                         ts.activateTimeshift() # activate timeshift will automatically pause
1182                         self.setSeekState(self.SEEK_STATE_PAUSE)
1183
1184                 if back:
1185                         self.doSeek(-5) # seek some gops before end
1186                         self.ts_rewind_timer.start(200, 1)
1187                 else:
1188                         self.doSeek(-1) # seek 1 gop before end
1189
1190         def rewindService(self):
1191                 self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
1192
1193         # same as activateTimeshiftEnd, but pauses afterwards.
1194         def activateTimeshiftEndAndPause(self):
1195                 print "activateTimeshiftEndAndPause"
1196                 #state = self.seekstate
1197                 self.activateTimeshiftEnd(False)
1198
1199         def __seekableStatusChanged(self):
1200                 enabled = False
1201
1202 #               print "self.isSeekable", self.isSeekable()
1203 #               print "self.timeshift_enabled", self.timeshift_enabled
1204
1205                 # when this service is not seekable, but timeshift
1206                 # is enabled, this means we can activate
1207                 # the timeshift
1208                 if not self.isSeekable() and self.timeshift_enabled:
1209                         enabled = True
1210
1211 #               print "timeshift activate:", enabled
1212                 self["TimeshiftActivateActions"].setEnabled(enabled)
1213
1214         def __serviceStarted(self):
1215                 self.timeshift_enabled = False
1216                 self.__seekableStatusChanged()
1217
1218 from Screens.PiPSetup import PiPSetup
1219
1220 class InfoBarExtensions:
1221         EXTENSION_SINGLE = 0
1222         EXTENSION_LIST = 1
1223
1224         def __init__(self):
1225                 self.list = []
1226
1227                 self["InstantExtensionsActions"] = HelpableActionMap(self, "InfobarExtensions",
1228                         {
1229                                 "extensions": (self.showExtensionSelection, _("view extensions...")),
1230                         }, 1) # lower priority
1231
1232         def addExtension(self, extension, key = None, type = EXTENSION_SINGLE):
1233                 self.list.append((type, extension, key))
1234
1235         def updateExtension(self, extension, key = None):
1236                 self.extensionsList.append(extension)
1237                 if key is not None:
1238                         if self.extensionKeys.has_key(key):
1239                                 key = None
1240
1241                 if key is None:
1242                         for x in self.availableKeys:
1243                                 if not self.extensionKeys.has_key(x):
1244                                         key = x
1245                                         break
1246
1247                 if key is not None:
1248                         self.extensionKeys[key] = len(self.extensionsList) - 1
1249
1250         def updateExtensions(self):
1251                 self.extensionsList = []
1252                 self.availableKeys = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "red", "green", "yellow", "blue" ]
1253                 self.extensionKeys = {}
1254                 for x in self.list:
1255                         if x[0] == self.EXTENSION_SINGLE:
1256                                 self.updateExtension(x[1], x[2])
1257                         else:
1258                                 for y in x[1]():
1259                                         self.updateExtension(y[0], y[1])
1260
1261
1262         def showExtensionSelection(self):
1263                 self.updateExtensions()
1264                 extensionsList = self.extensionsList[:]
1265                 keys = []
1266                 list = []
1267                 for x in self.availableKeys:
1268                         if self.extensionKeys.has_key(x):
1269                                 entry = self.extensionKeys[x]
1270                                 extension = self.extensionsList[entry]
1271                                 if extension[2]():
1272                                         name = str(extension[0]())
1273                                         list.append((extension[0](), extension))
1274                                         keys.append(x)
1275                                         extensionsList.remove(extension)
1276                                 else:
1277                                         extensionsList.remove(extension)
1278                 list.extend([(x[0](), x) for x in extensionsList])
1279
1280                 keys += [""] * len(extensionsList)
1281                 self.session.openWithCallback(self.extensionCallback, ChoiceBox, title=_("Please choose an extension..."), list = list, keys = keys)
1282
1283         def extensionCallback(self, answer):
1284                 if answer is not None:
1285                         answer[1][1]()
1286
1287 from Tools.BoundFunction import boundFunction
1288
1289 # depends on InfoBarExtensions
1290
1291 class InfoBarPlugins:
1292         def __init__(self):
1293                 self.addExtension(extension = self.getPluginList, type = InfoBarExtensions.EXTENSION_LIST)
1294
1295         def getPluginName(self, name):
1296                 return name
1297
1298         def getPluginList(self):
1299                 return [((boundFunction(self.getPluginName, p.name), boundFunction(self.runPlugin, p), lambda: True), None) for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EXTENSIONSMENU)]
1300
1301         def runPlugin(self, plugin):
1302                 if isinstance(self, InfoBarChannelSelection):
1303                         plugin(session = self.session, servicelist = self.servicelist)
1304                 else:
1305                         plugin(session = self.session)
1306
1307 from Components.Task import job_manager
1308 class InfoBarJobman:
1309         def __init__(self):
1310                 self.addExtension(extension = self.getJobList, type = InfoBarExtensions.EXTENSION_LIST)
1311
1312         def getJobList(self):
1313                 return [((boundFunction(self.getJobName, job), boundFunction(self.showJobView, job), lambda: True), None) for job in job_manager.getPendingJobs()]
1314
1315         def getJobName(self, job):
1316                 return "%s: %s (%d%%)" % (job.getStatustext(), job.name, int(100*job.progress/float(job.end)))
1317
1318         def showJobView(self, job):
1319                 from Screens.TaskView import JobView
1320                 job_manager.in_background = False
1321                 self.session.openWithCallback(self.JobViewCB, JobView, job)
1322         
1323         def JobViewCB(self, in_background):
1324                 job_manager.in_background = in_background
1325
1326 # depends on InfoBarExtensions
1327 class InfoBarSleepTimer:
1328         def __init__(self):
1329                 self.addExtension((self.getSleepTimerName, self.showSleepTimerSetup, lambda: True), "1")
1330
1331         def getSleepTimerName(self):
1332                 return _("Sleep Timer")
1333
1334         def showSleepTimerSetup(self):
1335                 self.session.open(SleepTimerEdit)
1336
1337 # depends on InfoBarExtensions
1338 class InfoBarPiP:
1339         def __init__(self):
1340                 self.session.pipshown = False
1341                 if SystemInfo.get("NumVideoDecoders", 1) > 1:
1342                         self.addExtension((self.getShowHideName, self.showPiP, lambda: True), "blue")
1343                         self.addExtension((self.getMoveName, self.movePiP, self.pipShown), "green")
1344                         self.addExtension((self.getSwapName, self.swapPiP, self.pipShown), "yellow")
1345
1346         def pipShown(self):
1347                 return self.session.pipshown
1348
1349         def pipHandles0Action(self):
1350                 return self.pipShown() and config.usage.pip_zero_button.value != "standard"
1351
1352         def getShowHideName(self):
1353                 if self.session.pipshown:
1354                         return _("Disable Picture in Picture")
1355                 else:
1356                         return _("Activate Picture in Picture")
1357
1358         def getSwapName(self):
1359                 return _("Swap Services")
1360
1361         def getMoveName(self):
1362                 return _("Move Picture in Picture")
1363
1364         def showPiP(self):
1365                 if self.session.pipshown:
1366                         del self.session.pip
1367                         self.session.pipshown = False
1368                 else:
1369                         self.session.pip = self.session.instantiateDialog(PictureInPicture)
1370                         self.session.pip.show()
1371                         newservice = self.session.nav.getCurrentlyPlayingServiceReference()
1372                         if self.session.pip.playService(newservice):
1373                                 self.session.pipshown = True
1374                                 self.session.pip.servicePath = self.servicelist.getCurrentServicePath()
1375                         else:
1376                                 self.session.pipshown = False
1377                                 del self.session.pip
1378                         self.session.nav.playService(newservice)
1379
1380         def swapPiP(self):
1381                 swapservice = self.session.nav.getCurrentlyPlayingServiceReference()
1382                 if self.session.pip.servicePath:
1383                         servicepath = self.servicelist.getCurrentServicePath()
1384                         ref=servicepath[len(servicepath)-1]
1385                         pipref=self.session.pip.getCurrentService()
1386                         self.session.pip.playService(swapservice)
1387                         self.servicelist.setCurrentServicePath(self.session.pip.servicePath)
1388                         if pipref.toString() != ref.toString(): # is a subservice ?
1389                                 self.session.nav.stopService() # stop portal
1390                                 self.session.nav.playService(pipref) # start subservice
1391                         self.session.pip.servicePath=servicepath
1392
1393         def movePiP(self):
1394                 self.session.open(PiPSetup, pip = self.session.pip)
1395
1396         def pipDoHandle0Action(self):
1397                 use = config.usage.pip_zero_button.value
1398                 if "swap" == use:
1399                         self.swapPiP()
1400                 elif "swapstop" == use:
1401                         self.swapPiP()
1402                         self.showPiP()
1403                 elif "stop" == use:
1404                         self.showPiP()
1405
1406 from RecordTimer import parseEvent, RecordTimerEntry
1407
1408 class InfoBarInstantRecord:
1409         """Instant Record - handles the instantRecord action in order to
1410         start/stop instant records"""
1411         def __init__(self):
1412                 self["InstantRecordActions"] = HelpableActionMap(self, "InfobarInstantRecord",
1413                         {
1414                                 "instantRecord": (self.instantRecord, _("Instant Record...")),
1415                         })
1416                 self.recording = []
1417
1418         def stopCurrentRecording(self, entry = -1):
1419                 if entry is not None and entry != -1:
1420                         self.session.nav.RecordTimer.removeEntry(self.recording[entry])
1421                         self.recording.remove(self.recording[entry])
1422
1423         def startInstantRecording(self, limitEvent = False):
1424                 serviceref = self.session.nav.getCurrentlyPlayingServiceReference()
1425
1426                 # try to get event info
1427                 event = None
1428                 try:
1429                         service = self.session.nav.getCurrentService()
1430                         epg = eEPGCache.getInstance()
1431                         event = epg.lookupEventTime(serviceref, -1, 0)
1432                         if event is None:
1433                                 info = service.info()
1434                                 ev = info.getEvent(0)
1435                                 event = ev
1436                 except:
1437                         pass
1438
1439                 begin = int(time())
1440                 end = begin + 3600      # dummy
1441                 name = "instant record"
1442                 description = ""
1443                 eventid = None
1444
1445                 if event is not None:
1446                         curEvent = parseEvent(event)
1447                         name = curEvent[2]
1448                         description = curEvent[3]
1449                         eventid = curEvent[4]
1450                         if limitEvent:
1451                                 end = curEvent[1]
1452                 else:
1453                         if limitEvent:
1454                                 self.session.open(MessageBox, _("No event info found, recording indefinitely."), MessageBox.TYPE_INFO)
1455
1456                 if isinstance(serviceref, eServiceReference):
1457                         serviceref = ServiceReference(serviceref)
1458
1459                 recording = RecordTimerEntry(serviceref, begin, end, name, description, eventid, dirname = config.movielist.last_videodir.value)
1460                 recording.dontSave = True
1461                 
1462                 if event is None or limitEvent == False:
1463                         recording.autoincrease = True
1464                         if recording.setAutoincreaseEnd():
1465                                 self.session.nav.RecordTimer.record(recording)
1466                                 self.recording.append(recording)
1467                 else:
1468                                 simulTimerList = self.session.nav.RecordTimer.record(recording)
1469                                 if simulTimerList is not None:  # conflict with other recording
1470                                         name = simulTimerList[1].name
1471                                         name_date = ' '.join((name, strftime('%c', localtime(simulTimerList[1].begin))))
1472                                         print "[TIMER] conflicts with", name_date
1473                                         recording.autoincrease = True   # start with max available length, then increment
1474                                         if recording.setAutoincreaseEnd():
1475                                                 self.session.nav.RecordTimer.record(recording)
1476                                                 self.recording.append(recording)
1477                                                 self.session.open(MessageBox, _("Record time limited due to conflicting timer %s") % name_date, MessageBox.TYPE_INFO)
1478                                         else:
1479                                                 self.session.open(MessageBox, _("Couldn't record due to conflicting timer %s") % name, MessageBox.TYPE_INFO)
1480                                         recording.autoincrease = False
1481                                 else:
1482                                         self.recording.append(recording)
1483
1484         def isInstantRecordRunning(self):
1485                 print "self.recording:", self.recording
1486                 if self.recording:
1487                         for x in self.recording:
1488                                 if x.isRunning():
1489                                         return True
1490                 return False
1491
1492         def recordQuestionCallback(self, answer):
1493                 print "pre:\n", self.recording
1494
1495                 if answer is None or answer[1] == "no":
1496                         return
1497                 list = []
1498                 recording = self.recording[:]
1499                 for x in recording:
1500                         if not x in self.session.nav.RecordTimer.timer_list:
1501                                 self.recording.remove(x)
1502                         elif x.dontSave and x.isRunning():
1503                                 list.append((x, False))
1504
1505                 if answer[1] == "changeduration":
1506                         if len(self.recording) == 1:
1507                                 self.changeDuration(0)
1508                         else:
1509                                 self.session.openWithCallback(self.changeDuration, TimerSelection, list)
1510                 elif answer[1] == "changeendtime":
1511                         if len(self.recording) == 1:
1512                                 self.setEndtime(0)
1513                         else:
1514                                 self.session.openWithCallback(self.setEndtime, TimerSelection, list)
1515                 elif answer[1] == "stop":
1516                         if len(self.recording) == 1:
1517                                 self.stopCurrentRecording(0)
1518                         else:
1519                                 self.session.openWithCallback(self.stopCurrentRecording, TimerSelection, list)
1520                 elif answer[1] in ( "indefinitely" , "manualduration", "manualendtime", "event"):
1521                         self.startInstantRecording(limitEvent = answer[1] in ("event", "manualendtime") or False)
1522                         if answer[1] == "manualduration":
1523                                 self.changeDuration(len(self.recording)-1)
1524                         elif answer[1] == "manualendtime":
1525                                 self.setEndtime(len(self.recording)-1)
1526                 print "after:\n", self.recording
1527
1528         def setEndtime(self, entry):
1529                 if entry is not None:
1530                         self.selectedEntry = entry
1531                         self.endtime=ConfigClock(default = self.recording[self.selectedEntry].end)
1532                         dlg = self.session.openWithCallback(self.TimeDateInputClosed, TimeDateInput, self.endtime)
1533                         dlg.setTitle(_("Please change recording endtime"))
1534
1535         def TimeDateInputClosed(self, ret):
1536                 if len(ret) > 1:
1537                         if ret[0]:
1538                                 localendtime = localtime(ret[1])
1539                                 print "stopping recording at", strftime("%c", localendtime)
1540                                 if self.recording[self.selectedEntry].end != ret[1]:
1541                                         self.recording[self.selectedEntry].autoincrease = False
1542                                 self.recording[self.selectedEntry].end = ret[1]
1543                                 self.session.nav.RecordTimer.timeChanged(self.recording[self.selectedEntry])
1544
1545         def changeDuration(self, entry):
1546                 if entry is not None:
1547                         self.selectedEntry = entry
1548                         self.session.openWithCallback(self.inputCallback, InputBox, title=_("How many minutes do you want to record?"), text="5", maxSize=False, type=Input.NUMBER)
1549
1550         def inputCallback(self, value):
1551                 if value is not None:
1552                         print "stopping recording after", int(value), "minutes."
1553                         entry = self.recording[self.selectedEntry]
1554                         if int(value) != 0:
1555                                 entry.autoincrease = False
1556                         entry.end = int(time()) + 60 * int(value)
1557                         self.session.nav.RecordTimer.timeChanged(entry)
1558
1559         def instantRecord(self):
1560                 dir = config.movielist.last_videodir.value
1561                 if not fileExists(dir, 'w'):
1562                         dir = resolveFilename(SCOPE_HDD)
1563                 try:
1564                         stat = os_stat(dir)
1565                 except:
1566                         # XXX: this message is a little odd as we might be recording to a remote device
1567                         self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
1568                         return
1569
1570                 if self.isInstantRecordRunning():
1571                         self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1572                                 title=_("A recording is currently running.\nWhat do you want to do?"), \
1573                                 list=((_("add recording (stop after current event)"), "event"), \
1574                                 (_("add recording (enter recording duration)"), "manualduration"), \
1575                                 (_("add recording (enter recording endtime)"), "manualendtime"), \
1576                                 (_("add recording (indefinitely)"), "indefinitely"), \
1577                                 (_("change recording (duration)"), "changeduration"), \
1578                                 (_("change recording (endtime)"), "changeendtime"), \
1579                                 (_("stop recording"), "stop"), \
1580                                 (_("do nothing"), "no")))
1581                 else:
1582                         self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1583                                 title=_("Start recording?"), \
1584                                 list=((_("add recording (stop after current event)"), "event"), \
1585                                 (_("add recording (enter recording duration)"), "manualduration"), \
1586                                 (_("add recording (enter recording endtime)"), "manualendtime"), \
1587                                 (_("add recording (indefinitely)"), "indefinitely"), \
1588                                 (_("don't record"), "no")))
1589
1590 from Tools.ISO639 import LanguageCodes
1591
1592 class InfoBarAudioSelection:
1593         def __init__(self):
1594                 self["AudioSelectionAction"] = HelpableActionMap(self, "InfobarAudioSelectionActions",
1595                         {
1596                                 "audioSelection": (self.audioSelection, _("Audio Options...")),
1597                         })
1598
1599         def audioSelection(self):
1600                 service = self.session.nav.getCurrentService()
1601                 self.audioTracks = audio = service and service.audioTracks()
1602                 n = audio and audio.getNumberOfTracks() or 0
1603                 tlist = []
1604                 if n > 0:
1605                         self.audioChannel = service.audioChannel()
1606
1607                         idx = 0
1608                         while idx < n:
1609                                 i = audio.getTrackInfo(idx)
1610                                 language = i.getLanguage()
1611                                 description = i.getDescription()
1612
1613                                 if LanguageCodes.has_key(language):
1614                                         language = LanguageCodes[language][0]
1615
1616                                 if len(description):
1617                                         description += " (" + language + ")"
1618                                 else:
1619                                         description = language
1620
1621                                 tlist.append((description, idx))
1622                                 idx += 1
1623
1624                         tlist.sort(key=lambda x: x[0])
1625
1626                         selectedAudio = self.audioTracks.getCurrentTrack()
1627
1628                         selection = 0
1629
1630                         for x in tlist:
1631                                 if x[1] != selectedAudio:
1632                                         selection += 1
1633                                 else:
1634                                         break
1635
1636                         if SystemInfo["CanDownmixAC3"]:
1637                                 tlist = [(_("AC3 downmix") + " - " +(_("Off"), _("On"))[config.av.downmix_ac3.value and 1 or 0], "CALLFUNC", self.changeAC3Downmix),
1638                                         ((_("Left"), _("Stereo"), _("Right"))[self.audioChannel.getCurrentChannel()], "mode"),
1639                                         ("--", "")] + tlist
1640                                 keys = [ "red", "green", "", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] + [""]*n
1641                                 selection += 3
1642                         else:
1643                                 tlist = [((_("Left"), _("Stereo"), _("Right"))[self.audioChannel.getCurrentChannel()], "mode"), ("--", "")] + tlist
1644                                 keys = [ "red", "", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] + [""]*n
1645                                 selection += 2
1646                         self.session.openWithCallback(self.audioSelected, ChoiceBox, title=_("Select audio track"), list = tlist, selection = selection, keys = keys)
1647                 else:
1648                         del self.audioTracks
1649
1650         def changeAC3Downmix(self, arg):
1651                 choicelist = self.session.current_dialog["list"]
1652                 list = choicelist.list
1653                 t = list[0][1]
1654                 list[0][1]=(t[0], t[1], t[2], t[3], t[4], t[5], t[6],
1655                         _("AC3 downmix") + " - " + (_("On"), _("Off"))[config.av.downmix_ac3.value and 1 or 0])
1656                 choicelist.setList(list)
1657                 if config.av.downmix_ac3.value:
1658                         config.av.downmix_ac3.value = False
1659                 else:
1660                         config.av.downmix_ac3.value = True
1661                 config.av.downmix_ac3.save()
1662
1663         def audioSelected(self, audio):
1664                 if audio is not None:
1665                         if isinstance(audio[1], str):
1666                                 if audio[1] == "mode":
1667                                         keys = ["red", "green", "yellow"]
1668                                         selection = self.audioChannel.getCurrentChannel()
1669                                         tlist = ((_("left"), 0), (_("stereo"), 1), (_("right"), 2))
1670                                         self.session.openWithCallback(self.modeSelected, ChoiceBox, title=_("Select audio mode"), list = tlist, selection = selection, keys = keys)
1671                         else:
1672                                 del self.audioChannel
1673                                 if self.session.nav.getCurrentService().audioTracks().getNumberOfTracks() > audio[1]:
1674                                         self.audioTracks.selectTrack(audio[1])
1675                 else:
1676                         del self.audioChannel
1677                 del self.audioTracks
1678
1679         def modeSelected(self, mode):
1680                 if mode is not None:
1681                         self.audioChannel.selectChannel(mode[1])
1682                 del self.audioChannel
1683
1684 class InfoBarSubserviceSelection:
1685         def __init__(self):
1686                 self["SubserviceSelectionAction"] = HelpableActionMap(self, "InfobarSubserviceSelectionActions",
1687                         {
1688                                 "subserviceSelection": (self.subserviceSelection, _("Subservice list...")),
1689                         })
1690
1691                 self["SubserviceQuickzapAction"] = HelpableActionMap(self, "InfobarSubserviceQuickzapActions",
1692                         {
1693                                 "nextSubservice": (self.nextSubservice, _("Switch to next subservice")),
1694                                 "prevSubservice": (self.prevSubservice, _("Switch to previous subservice"))
1695                         }, -1)
1696                 self["SubserviceQuickzapAction"].setEnabled(False)
1697
1698                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1699                         {
1700                                 iPlayableService.evUpdatedEventInfo: self.checkSubservicesAvail
1701                         })
1702
1703                 self.bsel = None
1704
1705         def checkSubservicesAvail(self):
1706                 service = self.session.nav.getCurrentService()
1707                 subservices = service and service.subServices()
1708                 if not subservices or subservices.getNumberOfSubservices() == 0:
1709                         self["SubserviceQuickzapAction"].setEnabled(False)
1710
1711         def nextSubservice(self):
1712                 self.changeSubservice(+1)
1713
1714         def prevSubservice(self):
1715                 self.changeSubservice(-1)
1716
1717         def changeSubservice(self, direction):
1718                 service = self.session.nav.getCurrentService()
1719                 subservices = service and service.subServices()
1720                 n = subservices and subservices.getNumberOfSubservices()
1721                 if n and n > 0:
1722                         selection = -1
1723                         ref = self.session.nav.getCurrentlyPlayingServiceReference()
1724                         idx = 0
1725                         while idx < n:
1726                                 if subservices.getSubservice(idx).toString() == ref.toString():
1727                                         selection = idx
1728                                         break
1729                                 idx += 1
1730                         if selection != -1:
1731                                 selection += direction
1732                                 if selection >= n:
1733                                         selection=0
1734                                 elif selection < 0:
1735                                         selection=n-1
1736                                 newservice = subservices.getSubservice(selection)
1737                                 if newservice.valid():
1738                                         del subservices
1739                                         del service
1740                                         self.session.nav.playService(newservice, False)
1741
1742         def subserviceSelection(self):
1743                 service = self.session.nav.getCurrentService()
1744                 subservices = service and service.subServices()
1745                 self.bouquets = self.servicelist.getBouquetList()
1746                 n = subservices and subservices.getNumberOfSubservices()
1747                 selection = 0
1748                 if n and n > 0:
1749                         ref = self.session.nav.getCurrentlyPlayingServiceReference()
1750                         tlist = []
1751                         idx = 0
1752                         while idx < n:
1753                                 i = subservices.getSubservice(idx)
1754                                 if i.toString() == ref.toString():
1755                                         selection = idx
1756                                 tlist.append((i.getName(), i))
1757                                 idx += 1
1758
1759                         if self.bouquets and len(self.bouquets):
1760                                 keys = ["red", "blue", "",  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1761                                 if config.usage.multibouquet.value:
1762                                         tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to bouquet"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1763                                 else:
1764                                         tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to favourites"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1765                                 selection += 3
1766                         else:
1767                                 tlist = [(_("Quickzap"), "quickzap", service.subServices()), ("--", "")] + tlist
1768                                 keys = ["red", "",  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1769                                 selection += 2
1770
1771                         self.session.openWithCallback(self.subserviceSelected, ChoiceBox, title=_("Please select a subservice..."), list = tlist, selection = selection, keys = keys)
1772
1773         def subserviceSelected(self, service):
1774                 del self.bouquets
1775                 if not service is None:
1776                         if isinstance(service[1], str):
1777                                 if service[1] == "quickzap":
1778                                         from Screens.SubservicesQuickzap import SubservicesQuickzap
1779                                         self.session.open(SubservicesQuickzap, service[2])
1780                         else:
1781                                 self["SubserviceQuickzapAction"].setEnabled(True)
1782                                 self.session.nav.playService(service[1], False)
1783
1784         def addSubserviceToBouquetCallback(self, service):
1785                 if len(service) > 1 and isinstance(service[1], eServiceReference):
1786                         self.selectedSubservice = service
1787                         if self.bouquets is None:
1788                                 cnt = 0
1789                         else:
1790                                 cnt = len(self.bouquets)
1791                         if cnt > 1: # show bouquet list
1792                                 self.bsel = self.session.openWithCallback(self.bouquetSelClosed, BouquetSelector, self.bouquets, self.addSubserviceToBouquet)
1793                         elif cnt == 1: # add to only one existing bouquet
1794                                 self.addSubserviceToBouquet(self.bouquets[0][1])
1795                                 self.session.open(MessageBox, _("Service has been added to the favourites."), MessageBox.TYPE_INFO)
1796
1797         def bouquetSelClosed(self, confirmed):
1798                 self.bsel = None
1799                 del self.selectedSubservice
1800                 if confirmed:
1801                         self.session.open(MessageBox, _("Service has been added to the selected bouquet."), MessageBox.TYPE_INFO)
1802
1803         def addSubserviceToBouquet(self, dest):
1804                 self.servicelist.addServiceToBouquet(dest, self.selectedSubservice[1])
1805                 if self.bsel:
1806                         self.bsel.close(True)
1807                 else:
1808                         del self.selectedSubservice
1809
1810 class InfoBarAdditionalInfo:
1811         def __init__(self):
1812
1813                 self["RecordingPossible"] = Boolean(fixed=harddiskmanager.HDDCount() > 0 and config.misc.rcused.value == 1)
1814                 self["TimeshiftPossible"] = self["RecordingPossible"]
1815                 self["ShowTimeshiftOnYellow"] = Boolean(fixed=(not config.misc.rcused.value == 0))
1816                 self["ShowAudioOnYellow"] = Boolean(fixed=config.misc.rcused.value == 0)
1817                 self["ShowRecordOnRed"] = Boolean(fixed=config.misc.rcused.value == 1)
1818                 self["ExtensionsAvailable"] = Boolean(fixed=1)
1819
1820 class InfoBarNotifications:
1821         def __init__(self):
1822                 self.onExecBegin.append(self.checkNotifications)
1823                 Notifications.notificationAdded.append(self.checkNotificationsIfExecing)
1824                 self.onClose.append(self.__removeNotification)
1825
1826         def __removeNotification(self):
1827                 Notifications.notificationAdded.remove(self.checkNotificationsIfExecing)
1828
1829         def checkNotificationsIfExecing(self):
1830                 if self.execing:
1831                         self.checkNotifications()
1832
1833         def checkNotifications(self):
1834                 notifications = Notifications.notifications
1835                 if notifications:
1836                         n = notifications[0]
1837
1838                         del notifications[0]
1839                         cb = n[0]
1840
1841                         if n[3].has_key("onSessionOpenCallback"):
1842                                 n[3]["onSessionOpenCallback"]()
1843                                 del n[3]["onSessionOpenCallback"]
1844
1845                         if cb is not None:
1846                                 dlg = self.session.openWithCallback(cb, n[1], *n[2], **n[3])
1847                         else:
1848                                 dlg = self.session.open(n[1], *n[2], **n[3])
1849
1850                         # remember that this notification is currently active
1851                         d = (n[4], dlg)
1852                         Notifications.current_notifications.append(d)
1853                         dlg.onClose.append(boundFunction(self.__notificationClosed, d))
1854
1855         def __notificationClosed(self, d):
1856                 Notifications.current_notifications.remove(d)
1857
1858 class InfoBarServiceNotifications:
1859         def __init__(self):
1860                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1861                         {
1862                                 iPlayableService.evEnd: self.serviceHasEnded
1863                         })
1864
1865         def serviceHasEnded(self):
1866                 print "service end!"
1867
1868                 try:
1869                         self.setSeekState(self.SEEK_STATE_PLAY)
1870                 except:
1871                         pass
1872
1873 class InfoBarCueSheetSupport:
1874         CUT_TYPE_IN = 0
1875         CUT_TYPE_OUT = 1
1876         CUT_TYPE_MARK = 2
1877         CUT_TYPE_LAST = 3
1878
1879         ENABLE_RESUME_SUPPORT = False
1880
1881         def __init__(self, actionmap = "InfobarCueSheetActions"):
1882                 self["CueSheetActions"] = HelpableActionMap(self, actionmap,
1883                         {
1884                                 "jumpPreviousMark": (self.jumpPreviousMark, _("jump to previous marked position")),
1885                                 "jumpNextMark": (self.jumpNextMark, _("jump to next marked position")),
1886                                 "toggleMark": (self.toggleMark, _("toggle a cut mark at the current position"))
1887                         }, prio=1)
1888
1889                 self.cut_list = [ ]
1890                 self.is_closing = False
1891                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1892                         {
1893                                 iPlayableService.evStart: self.__serviceStarted,
1894                         })
1895
1896         def __serviceStarted(self):
1897                 if self.is_closing:
1898                         return
1899                 print "new service started! trying to download cuts!"
1900                 self.downloadCuesheet()
1901
1902                 if self.ENABLE_RESUME_SUPPORT:
1903                         last = None
1904
1905                         for (pts, what) in self.cut_list:
1906                                 if what == self.CUT_TYPE_LAST:
1907                                         last = pts
1908
1909                         if last is not None:
1910                                 self.resume_point = last
1911                                 if config.usage.on_movie_start.value == "ask":
1912                                         Notifications.AddNotificationWithCallback(self.playLastCB, MessageBox, _("Do you want to resume this playback?"), timeout=10)
1913                                 elif config.usage.on_movie_start.value == "resume":
1914 # TRANSLATORS: The string "Resuming playback" flashes for a moment
1915 # TRANSLATORS: at the start of a movie, when the user has selected
1916 # TRANSLATORS: "Resume from last position" as start behavior.
1917 # TRANSLATORS: The purpose is to notify the user that the movie starts
1918 # TRANSLATORS: in the middle somewhere and not from the beginning.
1919 # TRANSLATORS: (Some translators seem to have interpreted it as a
1920 # TRANSLATORS: question or a choice, but it is a statement.)
1921                                         Notifications.AddNotificationWithCallback(self.playLastCB, MessageBox, _("Resuming playback"), timeout=2, type=MessageBox.TYPE_INFO)
1922
1923         def playLastCB(self, answer):
1924                 if answer == True:
1925                         self.doSeek(self.resume_point)
1926                 self.hideAfterResume()
1927
1928         def hideAfterResume(self):
1929                 if isinstance(self, InfoBarShowHide):
1930                         self.hide()
1931
1932         def __getSeekable(self):
1933                 service = self.session.nav.getCurrentService()
1934                 if service is None:
1935                         return None
1936                 return service.seek()
1937
1938         def cueGetCurrentPosition(self):
1939                 seek = self.__getSeekable()
1940                 if seek is None:
1941                         return None
1942                 r = seek.getPlayPosition()
1943                 if r[0]:
1944                         return None
1945                 return long(r[1])
1946
1947         def cueGetEndCutPosition(self):
1948                 ret = False
1949                 isin = True
1950                 for cp in self.cut_list:
1951                         if cp[1] == self.CUT_TYPE_OUT:
1952                                 if isin:
1953                                         isin = False
1954                                         ret = cp[0]
1955                         elif cp[1] == self.CUT_TYPE_IN:
1956                                 isin = True
1957                 return ret
1958                 
1959         def jumpPreviousNextMark(self, cmp, start=False):
1960                 current_pos = self.cueGetCurrentPosition()
1961                 if current_pos is None:
1962                         return False
1963                 mark = self.getNearestCutPoint(current_pos, cmp=cmp, start=start)
1964                 if mark is not None:
1965                         pts = mark[0]
1966                 else:
1967                         return False
1968
1969                 self.doSeek(pts)
1970                 return True
1971
1972         def jumpPreviousMark(self):
1973                 # we add 2 seconds, so if the play position is <2s after
1974                 # the mark, the mark before will be used
1975                 self.jumpPreviousNextMark(lambda x: -x-5*90000, start=True)
1976
1977         def jumpNextMark(self):
1978                 if not self.jumpPreviousNextMark(lambda x: x):
1979                         self.doSeek(-1)
1980
1981         def getNearestCutPoint(self, pts, cmp=abs, start=False):
1982                 # can be optimized
1983                 beforecut = False
1984                 nearest = None
1985                 if start:
1986                         beforecut = True
1987                         bestdiff = cmp(0 - pts)
1988                         if bestdiff >= 0:
1989                                 nearest = [0, False]
1990                 for cp in self.cut_list:
1991                         if beforecut and cp[1] in (self.CUT_TYPE_IN, self.CUT_TYPE_OUT):
1992                                 beforecut = False
1993                                 if cp[1] == self.CUT_TYPE_IN:  # Start is here, disregard previous marks
1994                                         diff = cmp(cp[0] - pts)
1995                                         if diff >= 0:
1996                                                 nearest = cp
1997                                                 bestdiff = diff
1998                                         else:
1999                                                 nearest = None
2000                         if cp[1] in (self.CUT_TYPE_MARK, self.CUT_TYPE_LAST):
2001                                 diff = cmp(cp[0] - pts)
2002                                 if diff >= 0 and (nearest is None or bestdiff > diff):
2003                                         nearest = cp
2004                                         bestdiff = diff
2005                 return nearest
2006
2007         def toggleMark(self, onlyremove=False, onlyadd=False, tolerance=5*90000, onlyreturn=False):
2008                 current_pos = self.cueGetCurrentPosition()
2009                 if current_pos is None:
2010                         print "not seekable"
2011                         return
2012
2013                 nearest_cutpoint = self.getNearestCutPoint(current_pos)
2014
2015                 if nearest_cutpoint is not None and abs(nearest_cutpoint[0] - current_pos) < tolerance:
2016                         if onlyreturn:
2017                                 return nearest_cutpoint
2018                         if not onlyadd:
2019                                 self.removeMark(nearest_cutpoint)
2020                 elif not onlyremove and not onlyreturn:
2021                         self.addMark((current_pos, self.CUT_TYPE_MARK))
2022
2023                 if onlyreturn:
2024                         return None
2025
2026         def addMark(self, point):
2027                 insort(self.cut_list, point)
2028                 self.uploadCuesheet()
2029                 self.showAfterCuesheetOperation()
2030
2031         def removeMark(self, point):
2032                 self.cut_list.remove(point)
2033                 self.uploadCuesheet()
2034                 self.showAfterCuesheetOperation()
2035
2036         def showAfterCuesheetOperation(self):
2037                 if isinstance(self, InfoBarShowHide):
2038                         self.doShow()
2039
2040         def __getCuesheet(self):
2041                 service = self.session.nav.getCurrentService()
2042                 if service is None:
2043                         return None
2044                 return service.cueSheet()
2045
2046         def uploadCuesheet(self):
2047                 cue = self.__getCuesheet()
2048
2049                 if cue is None:
2050                         print "upload failed, no cuesheet interface"
2051                         return
2052                 cue.setCutList(self.cut_list)
2053
2054         def downloadCuesheet(self):
2055                 cue = self.__getCuesheet()
2056
2057                 if cue is None:
2058                         print "download failed, no cuesheet interface"
2059                         self.cut_list = [ ]
2060                 else:
2061                         self.cut_list = cue.getCutList()
2062
2063 class InfoBarSummary(Screen):
2064         skin = """
2065         <screen position="0,0" size="132,64">
2066                 <widget source="global.CurrentTime" render="Label" position="62,46" size="82,18" font="Regular;16" >
2067                         <convert type="ClockToText">WithSeconds</convert>
2068                 </widget>
2069                 <widget source="session.RecordState" render="FixedLabel" text=" " position="62,46" size="82,18" zPosition="1" >
2070                         <convert type="ConfigEntryTest">config.usage.blinking_display_clock_during_recording,True,CheckSourceBoolean</convert>
2071                         <convert type="ConditionalShowHide">Blink</convert>
2072                 </widget>
2073                 <widget source="session.CurrentService" render="Label" position="6,4" size="120,42" font="Regular;18" >
2074                         <convert type="ServiceName">Name</convert>
2075                 </widget>
2076                 <widget source="session.Event_Now" render="Progress" position="6,46" size="46,18" borderWidth="1" >
2077                         <convert type="EventTime">Progress</convert>
2078                 </widget>
2079         </screen>"""
2080
2081 # for picon:  (path="piconlcd" will use LCD picons)
2082 #               <widget source="session.CurrentService" render="Picon" position="6,0" size="120,64" path="piconlcd" >
2083 #                       <convert type="ServiceName">Reference</convert>
2084 #               </widget>
2085
2086 class InfoBarSummarySupport:
2087         def __init__(self):
2088                 pass
2089
2090         def createSummary(self):
2091                 return InfoBarSummary
2092
2093 class InfoBarMoviePlayerSummary(Screen):
2094         skin = """
2095         <screen position="0,0" size="132,64">
2096                 <widget source="global.CurrentTime" render="Label" position="62,46" size="64,18" font="Regular;16" halign="right" >
2097                         <convert type="ClockToText">WithSeconds</convert>
2098                 </widget>
2099                 <widget source="session.RecordState" render="FixedLabel" text=" " position="62,46" size="64,18" zPosition="1" >
2100                         <convert type="ConfigEntryTest">config.usage.blinking_display_clock_during_recording,True,CheckSourceBoolean</convert>
2101                         <convert type="ConditionalShowHide">Blink</convert>
2102                 </widget>
2103                 <widget source="session.CurrentService" render="Label" position="6,4" size="120,42" font="Regular;18" >
2104                         <convert type="ServiceName">Name</convert>
2105                 </widget>
2106                 <widget source="session.CurrentService" render="Progress" position="6,46" size="56,18" borderWidth="1" >
2107                         <convert type="ServicePosition">Position</convert>
2108                 </widget>
2109         </screen>"""
2110
2111 class InfoBarMoviePlayerSummarySupport:
2112         def __init__(self):
2113                 pass
2114
2115         def createSummary(self):
2116                 return InfoBarMoviePlayerSummary
2117
2118 class InfoBarTeletextPlugin:
2119         def __init__(self):
2120                 self.teletext_plugin = None
2121
2122                 for p in plugins.getPlugins(PluginDescriptor.WHERE_TELETEXT):
2123                         self.teletext_plugin = p
2124
2125                 if self.teletext_plugin is not None:
2126                         self["TeletextActions"] = HelpableActionMap(self, "InfobarTeletextActions",
2127                                 {
2128                                         "startTeletext": (self.startTeletext, _("View teletext..."))
2129                                 })
2130                 else:
2131                         print "no teletext plugin found!"
2132
2133         def startTeletext(self):
2134                 self.teletext_plugin(session=self.session, service=self.session.nav.getCurrentService())
2135
2136 class InfoBarSubtitleSupport(object):
2137         def __init__(self):
2138                 object.__init__(self)
2139                 self.subtitle_window = self.session.instantiateDialog(SubtitleDisplay)
2140                 self.__subtitles_enabled = False
2141
2142                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
2143                         {
2144                                 iPlayableService.evEnd: self.__serviceStopped,
2145                                 iPlayableService.evUpdatedInfo: self.__updatedInfo
2146                         })
2147                 self.cached_subtitle_checked = False
2148                 self.__selected_subtitle = None
2149
2150         def __serviceStopped(self):
2151                 self.subtitle_window.hide()
2152                 self.__subtitles_enabled = False
2153                 self.cached_subtitle_checked = False
2154
2155         def __updatedInfo(self):
2156                 if not self.cached_subtitle_checked:
2157                         subtitle = self.getCurrentServiceSubtitle()
2158                         self.cached_subtitle_checked = True
2159                         self.__selected_subtitle = subtitle and subtitle.getCachedSubtitle()
2160                         if self.__selected_subtitle:
2161                                 subtitle.enableSubtitles(self.subtitle_window.instance, self.selected_subtitle)
2162                                 self.subtitle_window.show()
2163                                 self.__subtitles_enabled = True
2164
2165         def getCurrentServiceSubtitle(self):
2166                 service = self.session.nav.getCurrentService()
2167                 return service and service.subtitle()
2168
2169         def setSubtitlesEnable(self, enable=True):
2170                 subtitle = self.getCurrentServiceSubtitle()
2171                 if enable and self.__selected_subtitle is not None:
2172                         if subtitle and not self.__subtitles_enabled:
2173                                 subtitle.enableSubtitles(self.subtitle_window.instance, self.selected_subtitle)
2174                                 self.subtitle_window.show()
2175                                 self.__subtitles_enabled = True
2176                 else:
2177                         if subtitle:
2178                                 subtitle.disableSubtitles(self.subtitle_window.instance)
2179                         self.__subtitles_enabled = False
2180                         self.subtitle_window.hide()
2181
2182         def setSelectedSubtitle(self, subtitle):
2183                 self.__selected_subtitle = subtitle
2184
2185         subtitles_enabled = property(lambda self: self.__subtitles_enabled, setSubtitlesEnable)
2186         selected_subtitle = property(lambda self: self.__selected_subtitle, setSelectedSubtitle)
2187
2188 class InfoBarServiceErrorPopupSupport:
2189         def __init__(self):
2190                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
2191                         {
2192                                 iPlayableService.evTuneFailed: self.__tuneFailed,
2193                                 iPlayableService.evStart: self.__serviceStarted
2194                         })
2195                 self.__serviceStarted()
2196
2197         def __serviceStarted(self):
2198                 self.last_error = None
2199                 Notifications.RemovePopup(id = "ZapError")
2200
2201         def __tuneFailed(self):
2202                 service = self.session.nav.getCurrentService()
2203                 info = service and service.info()
2204                 error = info and info.getInfo(iServiceInformation.sDVBState)
2205
2206                 if error == self.last_error:
2207                         error = None
2208                 else:
2209                         self.last_error = error
2210
2211                 error = {
2212                         eDVBServicePMTHandler.eventNoResources: _("No free tuner!"),
2213                         eDVBServicePMTHandler.eventTuneFailed: _("Tune failed!"),
2214                         eDVBServicePMTHandler.eventNoPAT: _("No data on transponder!\n(Timeout reading PAT)"),
2215                         eDVBServicePMTHandler.eventNoPATEntry: _("Service not found!\n(SID not found in PAT)"),
2216                         eDVBServicePMTHandler.eventNoPMT: _("Service invalid!\n(Timeout reading PMT)"),
2217                         eDVBServicePMTHandler.eventNewProgramInfo: None,
2218                         eDVBServicePMTHandler.eventTuned: None,
2219                         eDVBServicePMTHandler.eventSOF: None,
2220                         eDVBServicePMTHandler.eventEOF: None,
2221                         eDVBServicePMTHandler.eventMisconfiguration: _("Service unavailable!\nCheck tuner configuration!"),
2222                 }.get(error) #this returns None when the key not exist in the dict
2223
2224                 if error is not None:
2225                         Notifications.AddPopup(text = error, type = MessageBox.TYPE_ERROR, timeout = 5, id = "ZapError")
2226                 else:
2227                         Notifications.RemovePopup(id = "ZapError")