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