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