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