79747a613f05d148f81c6c07e7f34ad0d11fda79
[enigma2.git] / lib / python / Screens / InfoBarGenerics.py
1 from ChannelSelection import ChannelSelection, BouquetSelector
2
3 from Components.ActionMap import ActionMap, HelpableActionMap
4 from Components.ActionMap import NumberActionMap
5 from Components.Harddisk import harddiskmanager
6 from Components.Input import Input
7 from Components.Label import Label
8 from Components.PluginComponent import plugins
9 from Components.ServiceEventTracker import ServiceEventTracker
10 from Components.Sources.Boolean import Boolean
11 from Components.config import config, ConfigBoolean, ConfigClock
12 from Components.SystemInfo import SystemInfo
13 from EpgSelection import EPGSelection
14 from Plugins.Plugin import PluginDescriptor
15
16 from Screen import Screen
17 from Screens.ChoiceBox import ChoiceBox
18 from Screens.Dish import Dish
19 from Screens.EventView import EventViewEPGSelect, EventViewSimple
20 from Screens.InputBox import InputBox
21 from Screens.MessageBox import MessageBox
22 from Screens.MinuteInput import MinuteInput
23 from Screens.TimerSelection import TimerSelection
24 from Screens.PictureInPicture import PictureInPicture
25 from Screens.SubtitleDisplay import SubtitleDisplay
26 from Screens.RdsDisplay import RdsInfoDisplay, RassInteractive
27 from Screens.SleepTimerEdit import SleepTimerEdit
28 from Screens.TimeDateInput import TimeDateInput
29 from ServiceReference import ServiceReference
30
31 from Tools import Notifications
32 from Tools.Directories import SCOPE_HDD, resolveFilename, pathExists
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):
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
1020         def _mayShow(self):
1021                 if self.execing and self.seekstate != self.SEEK_STATE_PLAY:
1022                         self.pvrStateDialog.show()
1023
1024         def __playStateChanged(self, state):
1025                 playstateString = state[3]
1026                 self.pvrStateDialog["state"].setText(playstateString)
1027                 self._mayShow()
1028
1029 class InfoBarTimeshiftState(InfoBarPVRState):
1030         def __init__(self):
1031                 InfoBarPVRState.__init__(self, screen=TimeshiftState)
1032
1033         def _mayShow(self):
1034                 if self.execing and self.timeshift_enabled and self.seekstate != self.SEEK_STATE_PLAY:
1035                         self.pvrStateDialog.show()
1036
1037 class InfoBarShowMovies:
1038
1039         # i don't really like this class.
1040         # it calls a not further specified "movie list" on up/down/movieList,
1041         # so this is not more than an action map
1042         def __init__(self):
1043                 self["MovieListActions"] = HelpableActionMap(self, "InfobarMovieListActions",
1044                         {
1045                                 "movieList": (self.showMovies, _("movie list")),
1046                                 "up": (self.showMovies, _("movie list")),
1047                                 "down": (self.showMovies, _("movie list"))
1048                         })
1049
1050 # InfoBarTimeshift requires InfoBarSeek, instantiated BEFORE!
1051
1052 # Hrmf.
1053 #
1054 # Timeshift works the following way:
1055 #                                         demux0   demux1                    "TimeshiftActions" "TimeshiftActivateActions" "SeekActions"
1056 # - normal playback                       TUNER    unused      PLAY               enable                disable              disable
1057 # - user presses "yellow" button.         FILE     record      PAUSE              enable                disable              enable
1058 # - user presess pause again              FILE     record      PLAY               enable                disable              enable
1059 # - user fast forwards                    FILE     record      FF                 enable                disable              enable
1060 # - end of timeshift buffer reached       TUNER    record      PLAY               enable                enable               disable
1061 # - user backwards                        FILE     record      BACK  # !!         enable                disable              enable
1062 #
1063
1064 # in other words:
1065 # - when a service is playing, pressing the "timeshiftStart" button ("yellow") enables recording ("enables timeshift"),
1066 # freezes the picture (to indicate timeshift), sets timeshiftMode ("activates timeshift")
1067 # now, the service becomes seekable, so "SeekActions" are enabled, "TimeshiftEnableActions" are disabled.
1068 # - the user can now PVR around
1069 # - if it hits the end, the service goes into live mode ("deactivates timeshift", it's of course still "enabled")
1070 # the service looses it's "seekable" state. It can still be paused, but just to activate timeshift right
1071 # after!
1072 # the seek actions will be disabled, but the timeshiftActivateActions will be enabled
1073 # - if the user rewinds, or press pause, timeshift will be activated again
1074
1075 # note that a timeshift can be enabled ("recording") and
1076 # activated (currently time-shifting).
1077
1078 class InfoBarTimeshift:
1079         def __init__(self):
1080                 self["TimeshiftActions"] = HelpableActionMap(self, "InfobarTimeshiftActions",
1081                         {
1082                                 "timeshiftStart": (self.startTimeshift, _("start timeshift")),  # the "yellow key"
1083                                 "timeshiftStop": (self.stopTimeshift, _("stop timeshift"))      # currently undefined :), probably 'TV'
1084                         }, prio=1)
1085                 self["TimeshiftActivateActions"] = ActionMap(["InfobarTimeshiftActivateActions"],
1086                         {
1087                                 "timeshiftActivateEnd": self.activateTimeshiftEnd, # something like "rewind key"
1088                                 "timeshiftActivateEndAndPause": self.activateTimeshiftEndAndPause  # something like "pause key"
1089                         }, prio=-1) # priority over record
1090
1091                 self.timeshift_enabled = 0
1092                 self.timeshift_state = 0
1093                 self.ts_rewind_timer = eTimer()
1094                 self.ts_rewind_timer.callback.append(self.rewindService)
1095
1096                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1097                         {
1098                                 iPlayableService.evStart: self.__serviceStarted,
1099                                 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged
1100                         })
1101
1102         def getTimeshift(self):
1103                 service = self.session.nav.getCurrentService()
1104                 return service and service.timeshift()
1105
1106         def startTimeshift(self):
1107                 print "enable timeshift"
1108                 ts = self.getTimeshift()
1109                 if ts is None:
1110                         self.session.open(MessageBox, _("Timeshift not possible!"), MessageBox.TYPE_ERROR)
1111                         print "no ts interface"
1112                         return 0
1113
1114                 if self.timeshift_enabled:
1115                         print "hu, timeshift already enabled?"
1116                 else:
1117                         if not ts.startTimeshift():
1118                                 self.timeshift_enabled = 1
1119
1120                                 # we remove the "relative time" for now.
1121                                 #self.pvrStateDialog["timeshift"].setRelative(time.time())
1122
1123                                 # PAUSE.
1124                                 #self.setSeekState(self.SEEK_STATE_PAUSE)
1125                                 self.activateTimeshiftEnd(False)
1126
1127                                 # enable the "TimeshiftEnableActions", which will override
1128                                 # the startTimeshift actions
1129                                 self.__seekableStatusChanged()
1130                         else:
1131                                 print "timeshift failed"
1132
1133         def stopTimeshift(self):
1134                 if not self.timeshift_enabled:
1135                         return 0
1136                 print "disable timeshift"
1137                 ts = self.getTimeshift()
1138                 if ts is None:
1139                         return 0
1140                 self.session.openWithCallback(self.stopTimeshiftConfirmed, MessageBox, _("Stop Timeshift?"), MessageBox.TYPE_YESNO)
1141
1142         def stopTimeshiftConfirmed(self, confirmed):
1143                 if not confirmed:
1144                         return
1145
1146                 ts = self.getTimeshift()
1147                 if ts is None:
1148                         return
1149
1150                 ts.stopTimeshift()
1151                 self.timeshift_enabled = 0
1152
1153                 # disable actions
1154                 self.__seekableStatusChanged()
1155
1156         # activates timeshift, and seeks to (almost) the end
1157         def activateTimeshiftEnd(self, back = True):
1158                 ts = self.getTimeshift()
1159                 print "activateTimeshiftEnd"
1160
1161                 if ts is None:
1162                         return
1163
1164                 if ts.isTimeshiftActive():
1165                         print "!! activate timeshift called - but shouldn't this be a normal pause?"
1166                         self.pauseService()
1167                 else:
1168                         print "play, ..."
1169                         ts.activateTimeshift() # activate timeshift will automatically pause
1170                         self.setSeekState(self.SEEK_STATE_PAUSE)
1171
1172                 if back:
1173                         self.doSeek(-5) # seek some gops before end
1174                         self.ts_rewind_timer.start(200, 1)
1175                 else:
1176                         self.doSeek(-1) # seek 1 gop before end
1177
1178         def rewindService(self):
1179                 self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
1180
1181         # same as activateTimeshiftEnd, but pauses afterwards.
1182         def activateTimeshiftEndAndPause(self):
1183                 print "activateTimeshiftEndAndPause"
1184                 #state = self.seekstate
1185                 self.activateTimeshiftEnd(False)
1186
1187         def __seekableStatusChanged(self):
1188                 enabled = False
1189
1190 #               print "self.isSeekable", self.isSeekable()
1191 #               print "self.timeshift_enabled", self.timeshift_enabled
1192
1193                 # when this service is not seekable, but timeshift
1194                 # is enabled, this means we can activate
1195                 # the timeshift
1196                 if not self.isSeekable() and self.timeshift_enabled:
1197                         enabled = True
1198
1199 #               print "timeshift activate:", enabled
1200                 self["TimeshiftActivateActions"].setEnabled(enabled)
1201
1202         def __serviceStarted(self):
1203                 self.timeshift_enabled = False
1204                 self.__seekableStatusChanged()
1205
1206 from Screens.PiPSetup import PiPSetup
1207
1208 class InfoBarExtensions:
1209         EXTENSION_SINGLE = 0
1210         EXTENSION_LIST = 1
1211
1212         def __init__(self):
1213                 self.list = []
1214
1215                 self["InstantExtensionsActions"] = HelpableActionMap(self, "InfobarExtensions",
1216                         {
1217                                 "extensions": (self.showExtensionSelection, _("view extensions...")),
1218                         })
1219
1220         def addExtension(self, extension, key = None, type = EXTENSION_SINGLE):
1221                 self.list.append((type, extension, key))
1222
1223         def updateExtension(self, extension, key = None):
1224                 self.extensionsList.append(extension)
1225                 if key is not None:
1226                         if self.extensionKeys.has_key(key):
1227                                 key = None
1228
1229                 if key is None:
1230                         for x in self.availableKeys:
1231                                 if not self.extensionKeys.has_key(x):
1232                                         key = x
1233                                         break
1234
1235                 if key is not None:
1236                         self.extensionKeys[key] = len(self.extensionsList) - 1
1237
1238         def updateExtensions(self):
1239                 self.extensionsList = []
1240                 self.availableKeys = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "red", "green", "yellow", "blue" ]
1241                 self.extensionKeys = {}
1242                 for x in self.list:
1243                         if x[0] == self.EXTENSION_SINGLE:
1244                                 self.updateExtension(x[1], x[2])
1245                         else:
1246                                 for y in x[1]():
1247                                         self.updateExtension(y[0], y[1])
1248
1249
1250         def showExtensionSelection(self):
1251                 self.updateExtensions()
1252                 extensionsList = self.extensionsList[:]
1253                 keys = []
1254                 list = []
1255                 for x in self.availableKeys:
1256                         if self.extensionKeys.has_key(x):
1257                                 entry = self.extensionKeys[x]
1258                                 extension = self.extensionsList[entry]
1259                                 if extension[2]():
1260                                         name = str(extension[0]())
1261                                         list.append((extension[0](), extension))
1262                                         keys.append(x)
1263                                         extensionsList.remove(extension)
1264                                 else:
1265                                         extensionsList.remove(extension)
1266                 for x in extensionsList:
1267                         list.append((x[0](), x))
1268                 keys += [""] * len(extensionsList)
1269                 self.session.openWithCallback(self.extensionCallback, ChoiceBox, title=_("Please choose an extension..."), list = list, keys = keys)
1270
1271         def extensionCallback(self, answer):
1272                 if answer is not None:
1273                         answer[1][1]()
1274
1275 from Tools.BoundFunction import boundFunction
1276
1277 # depends on InfoBarExtensions
1278
1279 class InfoBarPlugins:
1280         def __init__(self):
1281                 self.addExtension(extension = self.getPluginList, type = InfoBarExtensions.EXTENSION_LIST)
1282
1283         def getPluginName(self, name):
1284                 return name
1285
1286         def getPluginList(self):
1287                 list = []
1288                 for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EXTENSIONSMENU):
1289                         list.append(((boundFunction(self.getPluginName, p.name), boundFunction(self.runPlugin, p), lambda: True), None))
1290                 return list
1291
1292         def runPlugin(self, plugin):
1293                 plugin(session = self.session, servicelist = self.servicelist)
1294
1295 from Components.Task import job_manager
1296 class InfoBarJobman:
1297         def __init__(self):
1298                 self.addExtension(extension = self.getJobList, type = InfoBarExtensions.EXTENSION_LIST)
1299
1300         def getJobList(self):
1301                 list = []
1302                 for job in job_manager.getPendingJobs():
1303                         list.append(((boundFunction(self.getJobName, job), boundFunction(self.showJobView, job), lambda: True), None))
1304                 return list
1305
1306         def getJobName(self, job):
1307                 return "%s: %s (%d%%)" % (job.getStatustext(), job.name, int(100*job.progress/float(job.end)))
1308
1309         def showJobView(self, job):
1310                 from Screens.TaskView import JobView
1311                 job_manager.in_background = False
1312                 self.session.openWithCallback(self.JobViewCB, JobView, job)
1313         
1314         def JobViewCB(self, in_background):
1315                 job_manager.in_background = in_background
1316
1317 # depends on InfoBarExtensions
1318 class InfoBarSleepTimer:
1319         def __init__(self):
1320                 self.addExtension((self.getSleepTimerName, self.showSleepTimerSetup, lambda: True), "1")
1321
1322         def getSleepTimerName(self):
1323                 return _("Sleep Timer")
1324
1325         def showSleepTimerSetup(self):
1326                 self.session.open(SleepTimerEdit)
1327
1328 # depends on InfoBarExtensions
1329 class InfoBarPiP:
1330         def __init__(self):
1331                 self.session.pipshown = False
1332                 if SystemInfo.get("NumVideoDecoders", 1) > 1:
1333                         self.addExtension((self.getShowHideName, self.showPiP, lambda: True), "blue")
1334                         self.addExtension((self.getMoveName, self.movePiP, self.pipShown), "green")
1335                         self.addExtension((self.getSwapName, self.swapPiP, self.pipShown), "yellow")
1336
1337         def pipShown(self):
1338                 return self.session.pipshown
1339
1340         def pipHandles0Action(self):
1341                 return self.pipShown() and config.usage.pip_zero_button.value != "standard"
1342
1343         def getShowHideName(self):
1344                 if self.session.pipshown:
1345                         return _("Disable Picture in Picture")
1346                 else:
1347                         return _("Activate Picture in Picture")
1348
1349         def getSwapName(self):
1350                 return _("Swap Services")
1351
1352         def getMoveName(self):
1353                 return _("Move Picture in Picture")
1354
1355         def showPiP(self):
1356                 if self.session.pipshown:
1357                         del self.session.pip
1358                         self.session.pipshown = False
1359                 else:
1360                         self.session.pip = self.session.instantiateDialog(PictureInPicture)
1361                         self.session.pip.show()
1362                         newservice = self.session.nav.getCurrentlyPlayingServiceReference()
1363                         if self.session.pip.playService(newservice):
1364                                 self.session.pipshown = True
1365                                 self.session.pip.servicePath = self.servicelist.getCurrentServicePath()
1366                         else:
1367                                 self.session.pipshown = False
1368                                 del self.session.pip
1369                         self.session.nav.playService(newservice)
1370
1371         def swapPiP(self):
1372                 swapservice = self.session.nav.getCurrentlyPlayingServiceReference()
1373                 if self.session.pip.servicePath:
1374                         servicepath = self.servicelist.getCurrentServicePath()
1375                         ref=servicepath[len(servicepath)-1]
1376                         pipref=self.session.pip.getCurrentService()
1377                         self.session.pip.playService(swapservice)
1378                         self.servicelist.setCurrentServicePath(self.session.pip.servicePath)
1379                         if pipref.toString() != ref.toString(): # is a subservice ?
1380                                 self.session.nav.stopService() # stop portal
1381                                 self.session.nav.playService(pipref) # start subservice
1382                         self.session.pip.servicePath=servicepath
1383
1384         def movePiP(self):
1385                 self.session.open(PiPSetup, pip = self.session.pip)
1386
1387         def pipDoHandle0Action(self):
1388                 use = config.usage.pip_zero_button.value
1389                 if "swap" == use:
1390                         self.swapPiP()
1391                 elif "swapstop" == use:
1392                         self.swapPiP()
1393                         self.showPiP()
1394                 elif "stop" == use:
1395                         self.showPiP()
1396
1397 from RecordTimer import parseEvent, RecordTimerEntry
1398
1399 class InfoBarInstantRecord:
1400         """Instant Record - handles the instantRecord action in order to
1401         start/stop instant records"""
1402         def __init__(self):
1403                 self["InstantRecordActions"] = HelpableActionMap(self, "InfobarInstantRecord",
1404                         {
1405                                 "instantRecord": (self.instantRecord, _("Instant Record...")),
1406                         })
1407                 self.recording = []
1408
1409         def stopCurrentRecording(self, entry = -1):
1410                 if entry is not None and entry != -1:
1411                         self.session.nav.RecordTimer.removeEntry(self.recording[entry])
1412                         self.recording.remove(self.recording[entry])
1413
1414         def startInstantRecording(self, limitEvent = False):
1415                 serviceref = self.session.nav.getCurrentlyPlayingServiceReference()
1416
1417                 # try to get event info
1418                 event = None
1419                 try:
1420                         service = self.session.nav.getCurrentService()
1421                         epg = eEPGCache.getInstance()
1422                         event = epg.lookupEventTime(serviceref, -1, 0)
1423                         if event is None:
1424                                 info = service.info()
1425                                 ev = info.getEvent(0)
1426                                 event = ev
1427                 except:
1428                         pass
1429
1430                 begin = int(time())
1431                 end = begin + 3600 * 24 * 365 * 1 # 1 year
1432                 name = "instant record"
1433                 description = ""
1434                 eventid = None
1435
1436                 if event is not None:
1437                         curEvent = parseEvent(event)
1438                         name = curEvent[2]
1439                         description = curEvent[3]
1440                         eventid = curEvent[4]
1441                         if limitEvent:
1442                                 end = curEvent[1]
1443                 else:
1444                         if limitEvent:
1445                                 self.session.open(MessageBox, _("No event info found, recording indefinitely."), MessageBox.TYPE_INFO)
1446
1447                 if isinstance(serviceref, eServiceReference):
1448                         serviceref = ServiceReference(serviceref)
1449
1450                 recording = RecordTimerEntry(serviceref, begin, end, name, description, eventid, dirname = config.movielist.last_videodir.value)
1451                 recording.dontSave = True
1452                 recording.autoincrease = True
1453
1454                 simulTimerList = self.session.nav.RecordTimer.record(recording)
1455                 if simulTimerList is not None:
1456                         print "timer conflict detected!"
1457                         if (len(simulTimerList) > 1):
1458                                 print "tsc_list > 1"
1459                                 recording.end = simulTimerList[1].begin - 30
1460                                 self.session.nav.RecordTimer.record(recording)
1461                                 print "new endtime applied"
1462                         else:
1463                                 print "conflict with only one timer? ! ?"
1464                 self.recording.append(recording)
1465
1466         def isInstantRecordRunning(self):
1467                 print "self.recording:", self.recording
1468                 if len(self.recording) > 0:
1469                         for x in self.recording:
1470                                 if x.isRunning():
1471                                         return True
1472                 return False
1473
1474         def recordQuestionCallback(self, answer):
1475                 print "pre:\n", self.recording
1476
1477                 if answer is None or answer[1] == "no":
1478                         return
1479                 list = []
1480                 recording = self.recording[:]
1481                 for x in recording:
1482                         if not x in self.session.nav.RecordTimer.timer_list:
1483                                 self.recording.remove(x)
1484                         elif x.dontSave and x.isRunning():
1485                                 list.append((x, False))
1486
1487                 if answer[1] == "changeduration":
1488                         if len(self.recording) == 1:
1489                                 self.changeDuration(0)
1490                         else:
1491                                 self.session.openWithCallback(self.changeDuration, TimerSelection, list)
1492                 elif answer[1] == "changeendtime":
1493                         if len(self.recording) == 1:
1494                                 self.setEndtime(0)
1495                         else:
1496                                 self.session.openWithCallback(self.setEndtime, TimerSelection, list)
1497                 elif answer[1] == "stop":
1498                         if len(self.recording) == 1:
1499                                 self.stopCurrentRecording(0)
1500                         else:
1501                                 self.session.openWithCallback(self.stopCurrentRecording, TimerSelection, list)
1502                 elif answer[1] in ( "indefinitely" , "manualduration", "manualendtime", "event"):
1503                         self.startInstantRecording(limitEvent = answer[1] in ("event", "manualendtime") or False)
1504                         if answer[1] == "manualduration":
1505                                 self.changeDuration(len(self.recording)-1)
1506                         elif answer[1] == "manualendtime":
1507                                 self.setEndtime(len(self.recording)-1)
1508                 print "after:\n", self.recording
1509
1510         def setEndtime(self, entry):
1511                 if entry is not None:
1512                         self.selectedEntry = entry
1513                         self.endtime=ConfigClock(default = self.recording[self.selectedEntry].end)
1514                         dlg = self.session.openWithCallback(self.TimeDateInputClosed, TimeDateInput, self.endtime)
1515                         dlg.setTitle(_("Please change recording endtime"))
1516
1517         def TimeDateInputClosed(self, ret):
1518                 if len(ret) > 1:
1519                         if ret[0]:
1520                                 localendtime = localtime(ret[1])
1521                                 print "stopping recording at", strftime("%c", localendtime)
1522                                 if self.recording[self.selectedEntry].end != ret[1]:
1523                                         self.recording[self.selectedEntry].autoincrease = False
1524                                 self.recording[self.selectedEntry].end = ret[1]
1525                                 self.session.nav.RecordTimer.timeChanged(self.recording[self.selectedEntry])
1526
1527         def changeDuration(self, entry):
1528                 if entry is not None:
1529                         self.selectedEntry = entry
1530                         self.session.openWithCallback(self.inputCallback, InputBox, title=_("How many minutes do you want to record?"), text="5", maxSize=False, type=Input.NUMBER)
1531
1532         def inputCallback(self, value):
1533                 if value is not None:
1534                         print "stopping recording after", int(value), "minutes."
1535                         if int(value) != 0:
1536                                 self.recording[self.selectedEntry].autoincrease = False
1537                         self.recording[self.selectedEntry].end = int(time()) + 60 * int(value)
1538                         self.session.nav.RecordTimer.timeChanged(self.recording[self.selectedEntry])
1539
1540         def instantRecord(self):
1541                 dir = config.movielist.last_videodir.value
1542                 if not pathExists(dir):
1543                         dir = resolveFilename(SCOPE_HDD)
1544                 try:
1545                         stat = os_stat(dir)
1546                 except:
1547                         # XXX: this message is a little odd as we might be recording to a remote device
1548                         self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
1549                         return
1550
1551                 if self.isInstantRecordRunning():
1552                         self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1553                                 title=_("A recording is currently running.\nWhat do you want to do?"), \
1554                                 list=[(_("stop recording"), "stop"), \
1555                                 (_("change recording (duration)"), "changeduration"), \
1556                                 (_("change recording (endtime)"), "changeendtime"), \
1557                                 (_("add recording (indefinitely)"), "indefinitely"), \
1558                                 (_("add recording (stop after current event)"), "event"), \
1559                                 (_("add recording (enter recording duration)"), "manualduration"), \
1560                                 (_("add recording (enter recording endtime)"), "manualendtime"), \
1561                                 (_("do nothing"), "no")])
1562                 else:
1563                         self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1564                                 title=_("Start recording?"), \
1565                                 list=[(_("add recording (indefinitely)"), "indefinitely"), \
1566                                 (_("add recording (stop after current event)"), "event"), \
1567                                 (_("add recording (enter recording duration)"), "manualduration"), \
1568                                 (_("add recording (enter recording endtime)"), "manualendtime"), \
1569                                 (_("don't record"), "no")])
1570
1571 from Tools.ISO639 import LanguageCodes
1572
1573 class InfoBarAudioSelection:
1574         def __init__(self):
1575                 self["AudioSelectionAction"] = HelpableActionMap(self, "InfobarAudioSelectionActions",
1576                         {
1577                                 "audioSelection": (self.audioSelection, _("Audio Options...")),
1578                         })
1579
1580         def audioSelection(self):
1581                 service = self.session.nav.getCurrentService()
1582                 self.audioTracks = audio = service and service.audioTracks()
1583                 n = audio and audio.getNumberOfTracks() or 0
1584                 tlist = []
1585                 if n > 0:
1586                         self.audioChannel = service.audioChannel()
1587
1588                         for x in range(n):
1589                                 i = audio.getTrackInfo(x)
1590                                 language = i.getLanguage()
1591                                 description = i.getDescription()
1592
1593                                 if LanguageCodes.has_key(language):
1594                                         language = LanguageCodes[language][0]
1595
1596                                 if len(description):
1597                                         description += " (" + language + ")"
1598                                 else:
1599                                         description = language
1600
1601                                 tlist.append((description, x))
1602
1603                         tlist.sort(key=lambda x: x[0])
1604
1605                         selectedAudio = self.audioTracks.getCurrentTrack()
1606
1607                         selection = 0
1608
1609                         for x in tlist:
1610                                 if x[1] != selectedAudio:
1611                                         selection += 1
1612                                 else:
1613                                         break
1614
1615                         if SystemInfo["CanDownmixAC3"]:
1616                                 tlist = [(_("AC3 downmix") + " - " +[_("Off"), _("On")][config.av.downmix_ac3.value and 1 or 0], "CALLFUNC", self.changeAC3Downmix),
1617                                         ([_("Left"), _("Stereo"), _("Right")][self.audioChannel.getCurrentChannel()], "mode"),
1618                                         ("--", "")] + tlist
1619                                 keys = [ "red", "green", "", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] + [""]*n
1620                                 selection += 3
1621                         else:
1622                                 tlist = [([_("Left"), _("Stereo"), _("Right")][self.audioChannel.getCurrentChannel()], "mode"), ("--", "")] + tlist
1623                                 keys = [ "red", "", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] + [""]*n
1624                                 selection += 2
1625                         self.session.openWithCallback(self.audioSelected, ChoiceBox, title=_("Select audio track"), list = tlist, selection = selection, keys = keys)
1626                 else:
1627                         del self.audioTracks
1628
1629         def changeAC3Downmix(self, arg):
1630                 choicelist = self.session.current_dialog["list"]
1631                 list = choicelist.list
1632                 t = list[0][1]
1633                 list[0][1]=(t[0], t[1], t[2], t[3], t[4], t[5], t[6],
1634                         _("AC3 downmix") + " - " +[_("On"), _("Off")][config.av.downmix_ac3.value and 1 or 0])
1635                 choicelist.setList(list)
1636                 if config.av.downmix_ac3.value:
1637                         config.av.downmix_ac3.value = False
1638                 else:
1639                         config.av.downmix_ac3.value = True
1640                 config.av.downmix_ac3.save()
1641
1642         def audioSelected(self, audio):
1643                 if audio is not None:
1644                         if isinstance(audio[1], str):
1645                                 if audio[1] == "mode":
1646                                         keys = ["red", "green", "yellow"]
1647                                         selection = self.audioChannel.getCurrentChannel()
1648                                         tlist = [(_("left"), 0), (_("stereo"), 1), (_("right"), 2)]
1649                                         self.session.openWithCallback(self.modeSelected, ChoiceBox, title=_("Select audio mode"), list = tlist, selection = selection, keys = keys)
1650                         else:
1651                                 del self.audioChannel
1652                                 if self.session.nav.getCurrentService().audioTracks().getNumberOfTracks() > audio[1]:
1653                                         self.audioTracks.selectTrack(audio[1])
1654                 else:
1655                         del self.audioChannel
1656                 del self.audioTracks
1657
1658         def modeSelected(self, mode):
1659                 if mode is not None:
1660                         self.audioChannel.selectChannel(mode[1])
1661                 del self.audioChannel
1662
1663 class InfoBarSubserviceSelection:
1664         def __init__(self):
1665                 self["SubserviceSelectionAction"] = HelpableActionMap(self, "InfobarSubserviceSelectionActions",
1666                         {
1667                                 "subserviceSelection": (self.subserviceSelection, _("Subservice list...")),
1668                         })
1669
1670                 self["SubserviceQuickzapAction"] = HelpableActionMap(self, "InfobarSubserviceQuickzapActions",
1671                         {
1672                                 "nextSubservice": (self.nextSubservice, _("Switch to next subservice")),
1673                                 "prevSubservice": (self.prevSubservice, _("Switch to previous subservice"))
1674                         }, -1)
1675                 self["SubserviceQuickzapAction"].setEnabled(False)
1676
1677                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1678                         {
1679                                 iPlayableService.evUpdatedEventInfo: self.checkSubservicesAvail
1680                         })
1681
1682                 self.bsel = None
1683
1684         def checkSubservicesAvail(self):
1685                 service = self.session.nav.getCurrentService()
1686                 subservices = service and service.subServices()
1687                 if not subservices or subservices.getNumberOfSubservices() == 0:
1688                         self["SubserviceQuickzapAction"].setEnabled(False)
1689
1690         def nextSubservice(self):
1691                 self.changeSubservice(+1)
1692
1693         def prevSubservice(self):
1694                 self.changeSubservice(-1)
1695
1696         def changeSubservice(self, direction):
1697                 service = self.session.nav.getCurrentService()
1698                 subservices = service and service.subServices()
1699                 n = subservices and subservices.getNumberOfSubservices()
1700                 if n and n > 0:
1701                         selection = -1
1702                         ref = self.session.nav.getCurrentlyPlayingServiceReference()
1703                         for x in range(n):
1704                                 if subservices.getSubservice(x).toString() == ref.toString():
1705                                         selection = x
1706                         if selection != -1:
1707                                 selection += direction
1708                                 if selection >= n:
1709                                         selection=0
1710                                 elif selection < 0:
1711                                         selection=n-1
1712                                 newservice = subservices.getSubservice(selection)
1713                                 if newservice.valid():
1714                                         del subservices
1715                                         del service
1716                                         self.session.nav.playService(newservice, False)
1717
1718         def subserviceSelection(self):
1719                 service = self.session.nav.getCurrentService()
1720                 subservices = service and service.subServices()
1721                 self.bouquets = self.servicelist.getBouquetList()
1722                 n = subservices and subservices.getNumberOfSubservices()
1723                 selection = 0
1724                 if n and n > 0:
1725                         ref = self.session.nav.getCurrentlyPlayingServiceReference()
1726                         tlist = []
1727                         for x in range(n):
1728                                 i = subservices.getSubservice(x)
1729                                 if i.toString() == ref.toString():
1730                                         selection = x
1731                                 tlist.append((i.getName(), i))
1732
1733                         if self.bouquets and len(self.bouquets):
1734                                 keys = ["red", "blue", "",  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1735                                 if config.usage.multibouquet.value:
1736                                         tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to bouquet"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1737                                 else:
1738                                         tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to favourites"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1739                                 selection += 3
1740                         else:
1741                                 tlist = [(_("Quickzap"), "quickzap", service.subServices()), ("--", "")] + tlist
1742                                 keys = ["red", "",  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1743                                 selection += 2
1744
1745                         self.session.openWithCallback(self.subserviceSelected, ChoiceBox, title=_("Please select a subservice..."), list = tlist, selection = selection, keys = keys)
1746
1747         def subserviceSelected(self, service):
1748                 del self.bouquets
1749                 if not service is None:
1750                         if isinstance(service[1], str):
1751                                 if service[1] == "quickzap":
1752                                         from Screens.SubservicesQuickzap import SubservicesQuickzap
1753                                         self.session.open(SubservicesQuickzap, service[2])
1754                         else:
1755                                 self["SubserviceQuickzapAction"].setEnabled(True)
1756                                 self.session.nav.playService(service[1], False)
1757
1758         def addSubserviceToBouquetCallback(self, service):
1759                 if len(service) > 1 and isinstance(service[1], eServiceReference):
1760                         self.selectedSubservice = service
1761                         if self.bouquets is None:
1762                                 cnt = 0
1763                         else:
1764                                 cnt = len(self.bouquets)
1765                         if cnt > 1: # show bouquet list
1766                                 self.bsel = self.session.openWithCallback(self.bouquetSelClosed, BouquetSelector, self.bouquets, self.addSubserviceToBouquet)
1767                         elif cnt == 1: # add to only one existing bouquet
1768                                 self.addSubserviceToBouquet(self.bouquets[0][1])
1769                                 self.session.open(MessageBox, _("Service has been added to the favourites."), MessageBox.TYPE_INFO)
1770
1771         def bouquetSelClosed(self, confirmed):
1772                 self.bsel = None
1773                 del self.selectedSubservice
1774                 if confirmed:
1775                         self.session.open(MessageBox, _("Service has been added to the selected bouquet."), MessageBox.TYPE_INFO)
1776
1777         def addSubserviceToBouquet(self, dest):
1778                 self.servicelist.addServiceToBouquet(dest, self.selectedSubservice[1])
1779                 if self.bsel:
1780                         self.bsel.close(True)
1781                 else:
1782                         del self.selectedSubservice
1783
1784 class InfoBarAdditionalInfo:
1785         def __init__(self):
1786
1787                 self["RecordingPossible"] = Boolean(fixed=harddiskmanager.HDDCount() > 0 and config.misc.rcused.value == 1)
1788                 self["TimeshiftPossible"] = self["RecordingPossible"]
1789                 self["ShowTimeshiftOnYellow"] = Boolean(fixed=(not config.misc.rcused.value == 0))
1790                 self["ShowAudioOnYellow"] = Boolean(fixed=config.misc.rcused.value == 0)
1791                 self["ShowRecordOnRed"] = Boolean(fixed=config.misc.rcused.value == 1)
1792                 self["ExtensionsAvailable"] = Boolean(fixed=1)
1793
1794 class InfoBarNotifications:
1795         def __init__(self):
1796                 self.onExecBegin.append(self.checkNotifications)
1797                 Notifications.notificationAdded.append(self.checkNotificationsIfExecing)
1798                 self.onClose.append(self.__removeNotification)
1799
1800         def __removeNotification(self):
1801                 Notifications.notificationAdded.remove(self.checkNotificationsIfExecing)
1802
1803         def checkNotificationsIfExecing(self):
1804                 if self.execing:
1805                         self.checkNotifications()
1806
1807         def checkNotifications(self):
1808                 if len(Notifications.notifications):
1809                         n = Notifications.notifications[0]
1810
1811                         Notifications.notifications = Notifications.notifications[1:]
1812                         cb = n[0]
1813
1814                         if n[3].has_key("onSessionOpenCallback"):
1815                                 n[3]["onSessionOpenCallback"]()
1816                                 del n[3]["onSessionOpenCallback"]
1817
1818                         if cb is not None:
1819                                 dlg = self.session.openWithCallback(cb, n[1], *n[2], **n[3])
1820                         else:
1821                                 dlg = self.session.open(n[1], *n[2], **n[3])
1822
1823                         # remember that this notification is currently active
1824                         d = (n[4], dlg)
1825                         Notifications.current_notifications.append(d)
1826                         dlg.onClose.append(boundFunction(self.__notificationClosed, d))
1827
1828         def __notificationClosed(self, d):
1829                 Notifications.current_notifications.remove(d)
1830
1831 class InfoBarServiceNotifications:
1832         def __init__(self):
1833                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1834                         {
1835                                 iPlayableService.evEnd: self.serviceHasEnded
1836                         })
1837
1838         def serviceHasEnded(self):
1839                 print "service end!"
1840
1841                 try:
1842                         self.setSeekState(self.SEEK_STATE_PLAY)
1843                 except:
1844                         pass
1845
1846 class InfoBarCueSheetSupport:
1847         CUT_TYPE_IN = 0
1848         CUT_TYPE_OUT = 1
1849         CUT_TYPE_MARK = 2
1850         CUT_TYPE_LAST = 3
1851
1852         ENABLE_RESUME_SUPPORT = False
1853
1854         def __init__(self, actionmap = "InfobarCueSheetActions"):
1855                 self["CueSheetActions"] = HelpableActionMap(self, actionmap,
1856                         {
1857                                 "jumpPreviousMark": (self.jumpPreviousMark, _("jump to previous marked position")),
1858                                 "jumpNextMark": (self.jumpNextMark, _("jump to next marked position")),
1859                                 "toggleMark": (self.toggleMark, _("toggle a cut mark at the current position"))
1860                         }, prio=1)
1861
1862                 self.cut_list = [ ]
1863                 self.is_closing = False
1864                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1865                         {
1866                                 iPlayableService.evStart: self.__serviceStarted,
1867                         })
1868
1869         def __serviceStarted(self):
1870                 if self.is_closing:
1871                         return
1872                 print "new service started! trying to download cuts!"
1873                 self.downloadCuesheet()
1874
1875                 if self.ENABLE_RESUME_SUPPORT:
1876                         last = None
1877
1878                         for (pts, what) in self.cut_list:
1879                                 if what == self.CUT_TYPE_LAST:
1880                                         last = pts
1881
1882                         if last is not None:
1883                                 self.resume_point = last
1884                                 if config.usage.on_movie_start.value == "ask":
1885                                         Notifications.AddNotificationWithCallback(self.playLastCB, MessageBox, _("Do you want to resume this playback?"), timeout=10)
1886                                 elif config.usage.on_movie_start.value == "resume":
1887 # TRANSLATORS: The string "Resuming playback" flashes for a moment
1888 # TRANSLATORS: at the start of a movie, when the user has selected
1889 # TRANSLATORS: "Resume from last position" as start behavior.
1890 # TRANSLATORS: The purpose is to notify the user that the movie starts
1891 # TRANSLATORS: in the middle somewhere and not from the beginning.
1892 # TRANSLATORS: (Some translators seem to have interpreted it as a
1893 # TRANSLATORS: question or a choice, but it is a statement.)
1894                                         Notifications.AddNotificationWithCallback(self.playLastCB, MessageBox, _("Resuming playback"), timeout=2, type=MessageBox.TYPE_INFO)
1895
1896         def playLastCB(self, answer):
1897                 if answer == True:
1898                         self.doSeek(self.resume_point)
1899                 self.hideAfterResume()
1900
1901         def hideAfterResume(self):
1902                 if isinstance(self, InfoBarShowHide):
1903                         self.hide()
1904
1905         def __getSeekable(self):
1906                 service = self.session.nav.getCurrentService()
1907                 if service is None:
1908                         return None
1909                 return service.seek()
1910
1911         def cueGetCurrentPosition(self):
1912                 seek = self.__getSeekable()
1913                 if seek is None:
1914                         return None
1915                 r = seek.getPlayPosition()
1916                 if r[0]:
1917                         return None
1918                 return long(r[1])
1919
1920         def cueGetEndCutPosition(self):
1921                 ret = False
1922                 isin = True
1923                 for cp in self.cut_list:
1924                         if cp[1] == self.CUT_TYPE_OUT:
1925                                 if isin:
1926                                         isin = False
1927                                         ret = cp[0]
1928                         elif cp[1] == self.CUT_TYPE_IN:
1929                                 isin = True
1930                 return ret
1931                 
1932         def jumpPreviousNextMark(self, cmp, start=False):
1933                 current_pos = self.cueGetCurrentPosition()
1934                 if current_pos is None:
1935                         return False
1936                 mark = self.getNearestCutPoint(current_pos, cmp=cmp, start=start)
1937                 if mark is not None:
1938                         pts = mark[0]
1939                 else:
1940                         return False
1941
1942                 self.doSeek(pts)
1943                 return True
1944
1945         def jumpPreviousMark(self):
1946                 # we add 2 seconds, so if the play position is <2s after
1947                 # the mark, the mark before will be used
1948                 self.jumpPreviousNextMark(lambda x: -x-5*90000, start=True)
1949
1950         def jumpNextMark(self):
1951                 if not self.jumpPreviousNextMark(lambda x: x):
1952                         self.doSeek(-1)
1953
1954         def getNearestCutPoint(self, pts, cmp=abs, start=False):
1955                 # can be optimized
1956                 beforecut = False
1957                 nearest = None
1958                 if start:
1959                         beforecut = True
1960                         bestdiff = cmp(0 - pts)
1961                         if bestdiff >= 0:
1962                                 nearest = [0, False]
1963                 for cp in self.cut_list:
1964                         if beforecut and cp[1] in [self.CUT_TYPE_IN, self.CUT_TYPE_OUT]:
1965                                 beforecut = False
1966                                 if cp[1] == self.CUT_TYPE_IN:  # Start is here, disregard previous marks
1967                                         diff = cmp(cp[0] - pts)
1968                                         if diff >= 0:
1969                                                 nearest = cp
1970                                                 bestdiff = diff
1971                                         else:
1972                                                 nearest = None
1973                         if cp[1] in [self.CUT_TYPE_MARK, self.CUT_TYPE_LAST]:
1974                                 diff = cmp(cp[0] - pts)
1975                                 if diff >= 0 and (nearest is None or bestdiff > diff):
1976                                         nearest = cp
1977                                         bestdiff = diff
1978                 return nearest
1979
1980         def toggleMark(self, onlyremove=False, onlyadd=False, tolerance=5*90000, onlyreturn=False):
1981                 current_pos = self.cueGetCurrentPosition()
1982                 if current_pos is None:
1983                         print "not seekable"
1984                         return
1985
1986                 nearest_cutpoint = self.getNearestCutPoint(current_pos)
1987
1988                 if nearest_cutpoint is not None and abs(nearest_cutpoint[0] - current_pos) < tolerance:
1989                         if onlyreturn:
1990                                 return nearest_cutpoint
1991                         if not onlyadd:
1992                                 self.removeMark(nearest_cutpoint)
1993                 elif not onlyremove and not onlyreturn:
1994                         self.addMark((current_pos, self.CUT_TYPE_MARK))
1995
1996                 if onlyreturn:
1997                         return None
1998
1999         def addMark(self, point):
2000                 insort(self.cut_list, point)
2001                 self.uploadCuesheet()
2002                 self.showAfterCuesheetOperation()
2003
2004         def removeMark(self, point):
2005                 self.cut_list.remove(point)
2006                 self.uploadCuesheet()
2007                 self.showAfterCuesheetOperation()
2008
2009         def showAfterCuesheetOperation(self):
2010                 if isinstance(self, InfoBarShowHide):
2011                         self.doShow()
2012
2013         def __getCuesheet(self):
2014                 service = self.session.nav.getCurrentService()
2015                 if service is None:
2016                         return None
2017                 return service.cueSheet()
2018
2019         def uploadCuesheet(self):
2020                 cue = self.__getCuesheet()
2021
2022                 if cue is None:
2023                         print "upload failed, no cuesheet interface"
2024                         return
2025                 cue.setCutList(self.cut_list)
2026
2027         def downloadCuesheet(self):
2028                 cue = self.__getCuesheet()
2029
2030                 if cue is None:
2031                         print "download failed, no cuesheet interface"
2032                         self.cut_list = [ ]
2033                 else:
2034                         self.cut_list = cue.getCutList()
2035
2036 class InfoBarSummary(Screen):
2037         skin = """
2038         <screen position="0,0" size="132,64">
2039                 <widget source="global.CurrentTime" render="Label" position="62,46" size="82,18" font="Regular;16" >
2040                         <convert type="ClockToText">WithSeconds</convert>
2041                 </widget>
2042                 <widget source="session.RecordState" render="FixedLabel" text=" " position="62,46" size="82,18" zPosition="1" >
2043                         <convert type="ConfigEntryTest">config.usage.blinking_display_clock_during_recording,True,CheckSourceBoolean</convert>
2044                         <convert type="ConditionalShowHide">Blink</convert>
2045                 </widget>
2046                 <widget source="session.CurrentService" render="Label" position="6,4" size="120,42" font="Regular;18" >
2047                         <convert type="ServiceName">Name</convert>
2048                 </widget>
2049                 <widget source="session.Event_Now" render="Progress" position="6,46" size="46,18" borderWidth="1" >
2050                         <convert type="EventTime">Progress</convert>
2051                 </widget>
2052         </screen>"""
2053
2054 # for picon:  (path="piconlcd" will use LCD picons)
2055 #               <widget source="session.CurrentService" render="Picon" position="6,0" size="120,64" path="piconlcd" >
2056 #                       <convert type="ServiceName">Reference</convert>
2057 #               </widget>
2058
2059         def __init__(self, session, parent):
2060                 Screen.__init__(self, session, parent = parent)
2061
2062 class InfoBarSummarySupport:
2063         def __init__(self):
2064                 pass
2065
2066         def createSummary(self):
2067                 return InfoBarSummary
2068
2069 class InfoBarMoviePlayerSummary(Screen):
2070         skin = """
2071         <screen position="0,0" size="132,64">
2072                 <widget source="global.CurrentTime" render="Label" position="62,46" size="64,18" font="Regular;16" halign="right" >
2073                         <convert type="ClockToText">WithSeconds</convert>
2074                 </widget>
2075                 <widget source="session.RecordState" render="FixedLabel" text=" " position="62,46" size="64,18" zPosition="1" >
2076                         <convert type="ConfigEntryTest">config.usage.blinking_display_clock_during_recording,True,CheckSourceBoolean</convert>
2077                         <convert type="ConditionalShowHide">Blink</convert>
2078                 </widget>
2079                 <widget source="session.CurrentService" render="Label" position="6,4" size="120,42" font="Regular;18" >
2080                         <convert type="ServiceName">Name</convert>
2081                 </widget>
2082                 <widget source="session.CurrentService" render="Progress" position="6,46" size="56,18" borderWidth="1" >
2083                         <convert type="ServicePosition">Position</convert>
2084                 </widget>
2085         </screen>"""
2086
2087         def __init__(self, session, parent):
2088                 Screen.__init__(self, session)
2089
2090 class InfoBarMoviePlayerSummarySupport:
2091         def __init__(self):
2092                 pass
2093
2094         def createSummary(self):
2095                 return InfoBarMoviePlayerSummary
2096
2097 class InfoBarTeletextPlugin:
2098         def __init__(self):
2099                 self.teletext_plugin = None
2100
2101                 for p in plugins.getPlugins(PluginDescriptor.WHERE_TELETEXT):
2102                         self.teletext_plugin = p
2103
2104                 if self.teletext_plugin is not None:
2105                         self["TeletextActions"] = HelpableActionMap(self, "InfobarTeletextActions",
2106                                 {
2107                                         "startTeletext": (self.startTeletext, _("View teletext..."))
2108                                 })
2109                 else:
2110                         print "no teletext plugin found!"
2111
2112         def startTeletext(self):
2113                 self.teletext_plugin(session=self.session, service=self.session.nav.getCurrentService())
2114
2115 class InfoBarSubtitleSupport(object):
2116         def __init__(self):
2117                 object.__init__(self)
2118                 self.subtitle_window = self.session.instantiateDialog(SubtitleDisplay)
2119                 self.__subtitles_enabled = False
2120
2121                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
2122                         {
2123                                 iPlayableService.evEnd: self.__serviceStopped,
2124                                 iPlayableService.evUpdatedInfo: self.__updatedInfo
2125                         })
2126                 self.cached_subtitle_checked = False
2127                 self.__selected_subtitle = None
2128
2129         def __serviceStopped(self):
2130                 self.subtitle_window.hide()
2131                 self.__subtitles_enabled = False
2132                 self.cached_subtitle_checked = False
2133
2134         def __updatedInfo(self):
2135                 if not self.cached_subtitle_checked:
2136                         subtitle = self.getCurrentServiceSubtitle()
2137                         self.cached_subtitle_checked = True
2138                         self.__selected_subtitle = subtitle and subtitle.getCachedSubtitle()
2139                         if self.__selected_subtitle:
2140                                 subtitle.enableSubtitles(self.subtitle_window.instance, self.selected_subtitle)
2141                                 self.subtitle_window.show()
2142                                 self.__subtitles_enabled = True
2143
2144         def getCurrentServiceSubtitle(self):
2145                 service = self.session.nav.getCurrentService()
2146                 return service and service.subtitle()
2147
2148         def setSubtitlesEnable(self, enable=True):
2149                 subtitle = self.getCurrentServiceSubtitle()
2150                 if enable and self.__selected_subtitle is not None:
2151                         if subtitle and not self.__subtitles_enabled:
2152                                 subtitle.enableSubtitles(self.subtitle_window.instance, self.selected_subtitle)
2153                                 self.subtitle_window.show()
2154                                 self.__subtitles_enabled = True
2155                 else:
2156                         if subtitle:
2157                                 subtitle.disableSubtitles(self.subtitle_window.instance)
2158                         self.__subtitles_enabled = False
2159                         self.subtitle_window.hide()
2160
2161         def setSelectedSubtitle(self, subtitle):
2162                 self.__selected_subtitle = subtitle
2163
2164         subtitles_enabled = property(lambda self: self.__subtitles_enabled, setSubtitlesEnable)
2165         selected_subtitle = property(lambda self: self.__selected_subtitle, setSelectedSubtitle)
2166
2167 class InfoBarServiceErrorPopupSupport:
2168         def __init__(self):
2169                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
2170                         {
2171                                 iPlayableService.evTuneFailed: self.__tuneFailed,
2172                                 iPlayableService.evStart: self.__serviceStarted
2173                         })
2174                 self.__serviceStarted()
2175
2176         def __serviceStarted(self):
2177                 self.last_error = None
2178                 Notifications.RemovePopup(id = "ZapError")
2179
2180         def __tuneFailed(self):
2181                 service = self.session.nav.getCurrentService()
2182                 info = service and service.info()
2183                 error = info and info.getInfo(iServiceInformation.sDVBState)
2184
2185                 if error == self.last_error:
2186                         error = None
2187                 else:
2188                         self.last_error = error
2189
2190                 errors = {
2191                         eDVBServicePMTHandler.eventNoResources: _("No free tuner!"),
2192                         eDVBServicePMTHandler.eventTuneFailed: _("Tune failed!"),
2193                         eDVBServicePMTHandler.eventNoPAT: _("No data on transponder!\n(Timeout reading PAT)"),
2194                         eDVBServicePMTHandler.eventNoPATEntry: _("Service not found!\n(SID not found in PAT)"),
2195                         eDVBServicePMTHandler.eventNoPMT: _("Service invalid!\n(Timeout reading PMT)"),
2196                         eDVBServicePMTHandler.eventNewProgramInfo: None,
2197                         eDVBServicePMTHandler.eventTuned: None,
2198                         eDVBServicePMTHandler.eventSOF: None,
2199                         eDVBServicePMTHandler.eventEOF: None,
2200                         eDVBServicePMTHandler.eventMisconfiguration: _("Service unavailable!\nCheck tuner configuration!"),
2201                 }
2202
2203                 error = errors.get(error) #this returns None when the key not exist in the dict
2204
2205                 if error is not None:
2206                         Notifications.AddPopup(text = error, type = MessageBox.TYPE_ERROR, timeout = 5, id = "ZapError")
2207                 else:
2208                         Notifications.RemovePopup(id = "ZapError")