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