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