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