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