8221fca7310fc47ae56651cde5a447c0747bee3a
[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                 oldstate = self.seekstate
839                 self.seekstate = state
840
841                 for i in (0, 1, 2):
842                         if oldstate[i] != self.seekstate[i]:
843                                 (self.session.nav.pause, pauseable.setFastForward, pauseable.setSlowMotion)[i](self.seekstate[i])
844
845                 for c in self.onPlayStateChanged:
846                         c(self.seekstate)
847
848                 self.checkSkipShowHideLock()
849
850                 return True
851
852         def playpauseService(self):
853                 if self.seekstate != self.SEEK_STATE_PLAY:
854                         self.unPauseService()
855                 else:
856                         self.pauseService()
857
858         def pauseService(self):
859                 if self.seekstate == self.SEEK_STATE_PAUSE:
860                         if config.seek.on_pause.value == "play":
861                                 self.unPauseService()
862                         elif config.seek.on_pause.value == "step":
863                                 self.doSeekRelative(0)
864                         elif config.seek.on_pause.value == "last":
865                                 self.setSeekState(self.lastseekstate)
866                                 self.lastseekstate = self.SEEK_STATE_PLAY
867                 else:
868                         if self.seekstate != self.SEEK_STATE_EOF:
869                                 self.lastseekstate = self.seekstate
870                         self.setSeekState(self.SEEK_STATE_PAUSE);
871
872         def unPauseService(self):
873                 print "unpause"
874                 if self.seekstate == self.SEEK_STATE_PLAY:
875                         return 0
876                 self.setSeekState(self.SEEK_STATE_PLAY)
877
878         def doSeek(self, pts):
879                 seekable = self.getSeek()
880                 if seekable is None:
881                         return
882                 seekable.seekTo(pts)
883
884         def doSeekRelative(self, pts):
885                 seekable = self.getSeek()
886                 if seekable is None:
887                         return
888                 prevstate = self.seekstate
889
890                 if self.seekstate == self.SEEK_STATE_EOF:
891                         if prevstate == self.SEEK_STATE_PAUSE:
892                                 self.setSeekState(self.SEEK_STATE_PAUSE)
893                         else:
894                                 self.setSeekState(self.SEEK_STATE_PLAY)
895                 seekable.seekRelative(pts<0 and -1 or 1, abs(pts))
896                 if abs(pts) > 100 and config.usage.show_infobar_on_skip.value:
897                         self.showAfterSeek()
898
899         def seekFwd(self):
900                 if self.seekstate == self.SEEK_STATE_PLAY:
901                         self.setSeekState(self.makeStateForward(int(config.seek.enter_forward.value)))
902                 elif self.seekstate == self.SEEK_STATE_PAUSE:
903                         if len(config.seek.speeds_slowmotion.value):
904                                 self.setSeekState(self.makeStateSlowMotion(config.seek.speeds_slowmotion.value[-1]))
905                         else:
906                                 self.setSeekState(self.makeStateForward(int(config.seek.enter_forward.value)))
907                 elif self.seekstate == self.SEEK_STATE_EOF:
908                         pass
909                 elif self.isStateForward(self.seekstate):
910                         speed = self.seekstate[1]
911                         if self.seekstate[2]:
912                                 speed /= self.seekstate[2]
913                         speed = self.getHigher(speed, config.seek.speeds_forward.value) or config.seek.speeds_forward.value[-1]
914                         self.setSeekState(self.makeStateForward(speed))
915                 elif self.isStateBackward(self.seekstate):
916                         speed = -self.seekstate[1]
917                         if self.seekstate[2]:
918                                 speed /= self.seekstate[2]
919                         speed = self.getLower(speed, config.seek.speeds_backward.value)
920                         if speed:
921                                 self.setSeekState(self.makeStateBackward(speed))
922                         else:
923                                 self.setSeekState(self.SEEK_STATE_PLAY)
924                 elif self.isStateSlowMotion(self.seekstate):
925                         speed = self.getLower(self.seekstate[2], config.seek.speeds_slowmotion.value) or config.seek.speeds_slowmotion.value[0]
926                         self.setSeekState(self.makeStateSlowMotion(speed))
927
928         def seekBack(self):
929                 seekstate = self.seekstate
930                 if seekstate == self.SEEK_STATE_PLAY:
931                         self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
932                 elif seekstate == self.SEEK_STATE_EOF:
933                         self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
934                         self.doSeekRelative(-6)
935                 elif seekstate == self.SEEK_STATE_PAUSE:
936                         self.doSeekRelative(-3)
937                 elif self.isStateForward(seekstate):
938                         speed = seekstate[1]
939                         if seekstate[2]:
940                                 speed /= seekstate[2]
941                         speed = self.getLower(speed, config.seek.speeds_forward.value)
942                         if speed:
943                                 self.setSeekState(self.makeStateForward(speed))
944                         else:
945                                 self.setSeekState(self.SEEK_STATE_PLAY)
946                 elif self.isStateBackward(seekstate):
947                         speed = -seekstate[1]
948                         if seekstate[2]:
949                                 speed /= seekstate[2]
950                         speed = self.getHigher(speed, config.seek.speeds_backward.value) or config.seek.speeds_backward.value[-1]
951                         self.setSeekState(self.makeStateBackward(speed))
952                 elif self.isStateSlowMotion(seekstate):
953                         speed = self.getHigher(seekstate[2], config.seek.speeds_slowmotion.value)
954                         if speed:
955                                 self.setSeekState(self.makeStateSlowMotion(speed))
956                         else:
957                                 self.setSeekState(self.SEEK_STATE_PAUSE)
958
959         def seekFwdManual(self):
960                 self.session.openWithCallback(self.fwdSeekTo, MinuteInput)
961
962         def fwdSeekTo(self, minutes):
963                 print "Seek", minutes, "minutes forward"
964                 self.doSeekRelative(minutes * 60 * 90000)
965
966         def seekBackManual(self):
967                 self.session.openWithCallback(self.rwdSeekTo, MinuteInput)
968
969         def rwdSeekTo(self, minutes):
970                 print "rwdSeekTo"
971                 self.doSeekRelative(-minutes * 60 * 90000)
972
973         def checkSkipShowHideLock(self):
974                 wantlock = self.seekstate != self.SEEK_STATE_PLAY
975
976                 if config.usage.show_infobar_on_skip.value:
977                         if self.lockedBecauseOfSkipping and not wantlock:
978                                 self.unlockShow()
979                                 self.lockedBecauseOfSkipping = False
980
981                         if wantlock and not self.lockedBecauseOfSkipping:
982                                 self.lockShow()
983                                 self.lockedBecauseOfSkipping = True
984
985         def calcRemainingTime(self):
986                 seekable = self.getSeek()
987                 if seekable is not None:
988                         len = seekable.getLength()
989                         try:
990                                 tmp = self.cueGetEndCutPosition()
991                                 if tmp:
992                                         len = [False, tmp]
993                         except:
994                                 pass
995                         pos = seekable.getPlayPosition()
996                         speednom = self.seekstate[1] or 1
997                         speedden = self.seekstate[2] or 1
998                         if not len[0] and not pos[0]:
999                                 if len[1] <= pos[1]:
1000                                         return 0
1001                                 time = (len[1] - pos[1])*speedden/(90*speednom)
1002                                 return time
1003                 return False
1004                 
1005         def __evEOF(self):
1006                 if self.seekstate == self.SEEK_STATE_EOF:
1007                         return
1008
1009                 # if we are seeking forward, we try to end up ~1s before the end, and pause there.
1010                 seekstate = self.seekstate
1011                 if self.seekstate != self.SEEK_STATE_PAUSE:
1012                         self.setSeekState(self.SEEK_STATE_EOF)
1013
1014                 if seekstate not in (self.SEEK_STATE_PLAY, self.SEEK_STATE_PAUSE): # if we are seeking
1015                         seekable = self.getSeek()
1016                         if seekable is not None:
1017                                 seekable.seekTo(-1)
1018                 if seekstate == self.SEEK_STATE_PLAY: # regular EOF
1019                         self.doEofInternal(True)
1020                 else:
1021                         self.doEofInternal(False)
1022
1023         def doEofInternal(self, playing):
1024                 pass            # Defined in subclasses
1025
1026         def __evSOF(self):
1027                 self.setSeekState(self.SEEK_STATE_PLAY)
1028                 self.doSeek(0)
1029
1030 from Screens.PVRState import PVRState, TimeshiftState
1031
1032 class InfoBarPVRState:
1033         def __init__(self, screen=PVRState, force_show = False):
1034                 self.onPlayStateChanged.append(self.__playStateChanged)
1035                 self.pvrStateDialog = self.session.instantiateDialog(screen)
1036                 self.onShow.append(self._mayShow)
1037                 self.onHide.append(self.pvrStateDialog.hide)
1038                 self.force_show = force_show
1039
1040         def _mayShow(self):
1041                 if self.execing and self.seekstate != self.SEEK_STATE_PLAY:
1042                         self.pvrStateDialog.show()
1043
1044         def __playStateChanged(self, state):
1045                 playstateString = state[3]
1046                 self.pvrStateDialog["state"].setText(playstateString)
1047                 
1048                 # if we return into "PLAY" state, ensure that the dialog gets hidden if there will be no infobar displayed
1049                 if not config.usage.show_infobar_on_skip.value and self.seekstate == self.SEEK_STATE_PLAY and not self.force_show:
1050                         self.pvrStateDialog.hide()
1051                 else:
1052                         self._mayShow()
1053                         
1054
1055 class InfoBarTimeshiftState(InfoBarPVRState):
1056         def __init__(self):
1057                 InfoBarPVRState.__init__(self, screen=TimeshiftState, force_show = True)
1058
1059         def _mayShow(self):
1060                 if self.execing and self.timeshift_enabled and self.seekstate != self.SEEK_STATE_PLAY:
1061                         self.pvrStateDialog.show()
1062
1063 class InfoBarShowMovies:
1064
1065         # i don't really like this class.
1066         # it calls a not further specified "movie list" on up/down/movieList,
1067         # so this is not more than an action map
1068         def __init__(self):
1069                 self["MovieListActions"] = HelpableActionMap(self, "InfobarMovieListActions",
1070                         {
1071                                 "movieList": (self.showMovies, _("movie list")),
1072                                 "up": (self.showMovies, _("movie list")),
1073                                 "down": (self.showMovies, _("movie list"))
1074                         })
1075
1076 # InfoBarTimeshift requires InfoBarSeek, instantiated BEFORE!
1077
1078 # Hrmf.
1079 #
1080 # Timeshift works the following way:
1081 #                                         demux0   demux1                    "TimeshiftActions" "TimeshiftActivateActions" "SeekActions"
1082 # - normal playback                       TUNER    unused      PLAY               enable                disable              disable
1083 # - user presses "yellow" button.         FILE     record      PAUSE              enable                disable              enable
1084 # - user presess pause again              FILE     record      PLAY               enable                disable              enable
1085 # - user fast forwards                    FILE     record      FF                 enable                disable              enable
1086 # - end of timeshift buffer reached       TUNER    record      PLAY               enable                enable               disable
1087 # - user backwards                        FILE     record      BACK  # !!         enable                disable              enable
1088 #
1089
1090 # in other words:
1091 # - when a service is playing, pressing the "timeshiftStart" button ("yellow") enables recording ("enables timeshift"),
1092 # freezes the picture (to indicate timeshift), sets timeshiftMode ("activates timeshift")
1093 # now, the service becomes seekable, so "SeekActions" are enabled, "TimeshiftEnableActions" are disabled.
1094 # - the user can now PVR around
1095 # - if it hits the end, the service goes into live mode ("deactivates timeshift", it's of course still "enabled")
1096 # the service looses it's "seekable" state. It can still be paused, but just to activate timeshift right
1097 # after!
1098 # the seek actions will be disabled, but the timeshiftActivateActions will be enabled
1099 # - if the user rewinds, or press pause, timeshift will be activated again
1100
1101 # note that a timeshift can be enabled ("recording") and
1102 # activated (currently time-shifting).
1103
1104 class InfoBarTimeshift:
1105         def __init__(self):
1106                 self["TimeshiftActions"] = HelpableActionMap(self, "InfobarTimeshiftActions",
1107                         {
1108                                 "timeshiftStart": (self.startTimeshift, _("start timeshift")),  # the "yellow key"
1109                                 "timeshiftStop": (self.stopTimeshift, _("stop timeshift"))      # currently undefined :), probably 'TV'
1110                         }, prio=1)
1111                 self["TimeshiftActivateActions"] = ActionMap(["InfobarTimeshiftActivateActions"],
1112                         {
1113                                 "timeshiftActivateEnd": self.activateTimeshiftEnd, # something like "rewind key"
1114                                 "timeshiftActivateEndAndPause": self.activateTimeshiftEndAndPause  # something like "pause key"
1115                         }, prio=-1) # priority over record
1116
1117                 self.timeshift_enabled = 0
1118                 self.timeshift_state = 0
1119                 self.ts_rewind_timer = eTimer()
1120                 self.ts_rewind_timer.callback.append(self.rewindService)
1121
1122                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1123                         {
1124                                 iPlayableService.evStart: self.__serviceStarted,
1125                                 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged
1126                         })
1127
1128         def getTimeshift(self):
1129                 service = self.session.nav.getCurrentService()
1130                 return service and service.timeshift()
1131
1132         def startTimeshift(self):
1133                 print "enable timeshift"
1134                 ts = self.getTimeshift()
1135                 if ts is None:
1136                         self.session.open(MessageBox, _("Timeshift not possible!"), MessageBox.TYPE_ERROR)
1137                         print "no ts interface"
1138                         return 0
1139
1140                 if self.timeshift_enabled:
1141                         print "hu, timeshift already enabled?"
1142                 else:
1143                         if not ts.startTimeshift():
1144                                 self.timeshift_enabled = 1
1145
1146                                 # we remove the "relative time" for now.
1147                                 #self.pvrStateDialog["timeshift"].setRelative(time.time())
1148
1149                                 # PAUSE.
1150                                 #self.setSeekState(self.SEEK_STATE_PAUSE)
1151                                 self.activateTimeshiftEnd(False)
1152
1153                                 # enable the "TimeshiftEnableActions", which will override
1154                                 # the startTimeshift actions
1155                                 self.__seekableStatusChanged()
1156                         else:
1157                                 print "timeshift failed"
1158
1159         def stopTimeshift(self):
1160                 if not self.timeshift_enabled:
1161                         return 0
1162                 print "disable timeshift"
1163                 ts = self.getTimeshift()
1164                 if ts is None:
1165                         return 0
1166                 self.session.openWithCallback(self.stopTimeshiftConfirmed, MessageBox, _("Stop Timeshift?"), MessageBox.TYPE_YESNO)
1167
1168         def stopTimeshiftConfirmed(self, confirmed):
1169                 if not confirmed:
1170                         return
1171
1172                 ts = self.getTimeshift()
1173                 if ts is None:
1174                         return
1175
1176                 ts.stopTimeshift()
1177                 self.timeshift_enabled = 0
1178
1179                 # disable actions
1180                 self.__seekableStatusChanged()
1181
1182         # activates timeshift, and seeks to (almost) the end
1183         def activateTimeshiftEnd(self, back = True):
1184                 ts = self.getTimeshift()
1185                 print "activateTimeshiftEnd"
1186
1187                 if ts is None:
1188                         return
1189
1190                 if ts.isTimeshiftActive():
1191                         print "!! activate timeshift called - but shouldn't this be a normal pause?"
1192                         self.pauseService()
1193                 else:
1194                         print "play, ..."
1195                         ts.activateTimeshift() # activate timeshift will automatically pause
1196                         self.setSeekState(self.SEEK_STATE_PAUSE)
1197
1198                 if back:
1199                         self.doSeek(-5) # seek some gops before end
1200                         self.ts_rewind_timer.start(200, 1)
1201                 else:
1202                         self.doSeek(-1) # seek 1 gop before end
1203
1204         def rewindService(self):
1205                 self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
1206
1207         # same as activateTimeshiftEnd, but pauses afterwards.
1208         def activateTimeshiftEndAndPause(self):
1209                 print "activateTimeshiftEndAndPause"
1210                 #state = self.seekstate
1211                 self.activateTimeshiftEnd(False)
1212
1213         def __seekableStatusChanged(self):
1214                 enabled = False
1215
1216 #               print "self.isSeekable", self.isSeekable()
1217 #               print "self.timeshift_enabled", self.timeshift_enabled
1218
1219                 # when this service is not seekable, but timeshift
1220                 # is enabled, this means we can activate
1221                 # the timeshift
1222                 if not self.isSeekable() and self.timeshift_enabled:
1223                         enabled = True
1224
1225 #               print "timeshift activate:", enabled
1226                 self["TimeshiftActivateActions"].setEnabled(enabled)
1227
1228         def __serviceStarted(self):
1229                 self.timeshift_enabled = False
1230                 self.__seekableStatusChanged()
1231
1232 from Screens.PiPSetup import PiPSetup
1233
1234 class InfoBarExtensions:
1235         EXTENSION_SINGLE = 0
1236         EXTENSION_LIST = 1
1237
1238         def __init__(self):
1239                 self.list = []
1240
1241                 self["InstantExtensionsActions"] = HelpableActionMap(self, "InfobarExtensions",
1242                         {
1243                                 "extensions": (self.showExtensionSelection, _("view extensions...")),
1244                         }, 1) # lower priority
1245
1246         def addExtension(self, extension, key = None, type = EXTENSION_SINGLE):
1247                 self.list.append((type, extension, key))
1248
1249         def updateExtension(self, extension, key = None):
1250                 self.extensionsList.append(extension)
1251                 if key is not None:
1252                         if self.extensionKeys.has_key(key):
1253                                 key = None
1254
1255                 if key is None:
1256                         for x in self.availableKeys:
1257                                 if not self.extensionKeys.has_key(x):
1258                                         key = x
1259                                         break
1260
1261                 if key is not None:
1262                         self.extensionKeys[key] = len(self.extensionsList) - 1
1263
1264         def updateExtensions(self):
1265                 self.extensionsList = []
1266                 self.availableKeys = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "red", "green", "yellow", "blue" ]
1267                 self.extensionKeys = {}
1268                 for x in self.list:
1269                         if x[0] == self.EXTENSION_SINGLE:
1270                                 self.updateExtension(x[1], x[2])
1271                         else:
1272                                 for y in x[1]():
1273                                         self.updateExtension(y[0], y[1])
1274
1275
1276         def showExtensionSelection(self):
1277                 self.updateExtensions()
1278                 extensionsList = self.extensionsList[:]
1279                 keys = []
1280                 list = []
1281                 for x in self.availableKeys:
1282                         if self.extensionKeys.has_key(x):
1283                                 entry = self.extensionKeys[x]
1284                                 extension = self.extensionsList[entry]
1285                                 if extension[2]():
1286                                         name = str(extension[0]())
1287                                         list.append((extension[0](), extension))
1288                                         keys.append(x)
1289                                         extensionsList.remove(extension)
1290                                 else:
1291                                         extensionsList.remove(extension)
1292                 list.extend([(x[0](), x) for x in extensionsList])
1293
1294                 keys += [""] * len(extensionsList)
1295                 self.session.openWithCallback(self.extensionCallback, ChoiceBox, title=_("Please choose an extension..."), list = list, keys = keys, skin_name = "ExtensionsList")
1296
1297         def extensionCallback(self, answer):
1298                 if answer is not None:
1299                         answer[1][1]()
1300
1301 from Tools.BoundFunction import boundFunction
1302
1303 # depends on InfoBarExtensions
1304
1305 class InfoBarPlugins:
1306         def __init__(self):
1307                 self.addExtension(extension = self.getPluginList, type = InfoBarExtensions.EXTENSION_LIST)
1308
1309         def getPluginName(self, name):
1310                 return name
1311
1312         def getPluginList(self):
1313                 list = [((boundFunction(self.getPluginName, p.name), boundFunction(self.runPlugin, p), lambda: True), None, p.name) for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EXTENSIONSMENU)]
1314                 list.sort(key = lambda e: e[2]) # sort by name
1315                 return list
1316
1317         def runPlugin(self, plugin):
1318                 if isinstance(self, InfoBarChannelSelection):
1319                         plugin(session = self.session, servicelist = self.servicelist)
1320                 else:
1321                         plugin(session = self.session)
1322
1323 from Components.Task import job_manager
1324 class InfoBarJobman:
1325         def __init__(self):
1326                 self.addExtension(extension = self.getJobList, type = InfoBarExtensions.EXTENSION_LIST)
1327
1328         def getJobList(self):
1329                 return [((boundFunction(self.getJobName, job), boundFunction(self.showJobView, job), lambda: True), None) for job in job_manager.getPendingJobs()]
1330
1331         def getJobName(self, job):
1332                 return "%s: %s (%d%%)" % (job.getStatustext(), job.name, int(100*job.progress/float(job.end)))
1333
1334         def showJobView(self, job):
1335                 from Screens.TaskView import JobView
1336                 job_manager.in_background = False
1337                 self.session.openWithCallback(self.JobViewCB, JobView, job)
1338         
1339         def JobViewCB(self, in_background):
1340                 job_manager.in_background = in_background
1341
1342 # depends on InfoBarExtensions
1343 class InfoBarSleepTimer:
1344         def __init__(self):
1345                 self.addExtension((self.getSleepTimerName, self.showSleepTimerSetup, lambda: True), "1")
1346
1347         def getSleepTimerName(self):
1348                 return _("Sleep Timer")
1349
1350         def showSleepTimerSetup(self):
1351                 self.session.open(SleepTimerEdit)
1352
1353 # depends on InfoBarExtensions
1354 class InfoBarPiP:
1355         def __init__(self):
1356                 self.session.pipshown = False
1357                 if SystemInfo.get("NumVideoDecoders", 1) > 1:
1358                         self.addExtension((self.getShowHideName, self.showPiP, lambda: True), "blue")
1359                         self.addExtension((self.getMoveName, self.movePiP, self.pipShown), "green")
1360                         self.addExtension((self.getSwapName, self.swapPiP, self.pipShown), "yellow")
1361
1362         def pipShown(self):
1363                 return self.session.pipshown
1364
1365         def pipHandles0Action(self):
1366                 return self.pipShown() and config.usage.pip_zero_button.value != "standard"
1367
1368         def getShowHideName(self):
1369                 if self.session.pipshown:
1370                         return _("Disable Picture in Picture")
1371                 else:
1372                         return _("Activate Picture in Picture")
1373
1374         def getSwapName(self):
1375                 return _("Swap Services")
1376
1377         def getMoveName(self):
1378                 return _("Move Picture in Picture")
1379
1380         def showPiP(self):
1381                 if self.session.pipshown:
1382                         del self.session.pip
1383                         self.session.pipshown = False
1384                 else:
1385                         self.session.pip = self.session.instantiateDialog(PictureInPicture)
1386                         self.session.pip.show()
1387                         newservice = self.session.nav.getCurrentlyPlayingServiceReference()
1388                         if self.session.pip.playService(newservice):
1389                                 self.session.pipshown = True
1390                                 self.session.pip.servicePath = self.servicelist.getCurrentServicePath()
1391                         else:
1392                                 self.session.pipshown = False
1393                                 del self.session.pip
1394                         self.session.nav.playService(newservice)
1395
1396         def swapPiP(self):
1397                 swapservice = self.session.nav.getCurrentlyPlayingServiceReference()
1398                 if self.session.pip.servicePath:
1399                         servicepath = self.servicelist.getCurrentServicePath()
1400                         ref=servicepath[len(servicepath)-1]
1401                         pipref=self.session.pip.getCurrentService()
1402                         self.session.pip.playService(swapservice)
1403                         self.servicelist.setCurrentServicePath(self.session.pip.servicePath)
1404                         if pipref.toString() != ref.toString(): # is a subservice ?
1405                                 self.session.nav.stopService() # stop portal
1406                                 self.session.nav.playService(pipref) # start subservice
1407                         self.session.pip.servicePath=servicepath
1408
1409         def movePiP(self):
1410                 self.session.open(PiPSetup, pip = self.session.pip)
1411
1412         def pipDoHandle0Action(self):
1413                 use = config.usage.pip_zero_button.value
1414                 if "swap" == use:
1415                         self.swapPiP()
1416                 elif "swapstop" == use:
1417                         self.swapPiP()
1418                         self.showPiP()
1419                 elif "stop" == use:
1420                         self.showPiP()
1421
1422 from RecordTimer import parseEvent, RecordTimerEntry
1423
1424 class InfoBarInstantRecord:
1425         """Instant Record - handles the instantRecord action in order to
1426         start/stop instant records"""
1427         def __init__(self):
1428                 self["InstantRecordActions"] = HelpableActionMap(self, "InfobarInstantRecord",
1429                         {
1430                                 "instantRecord": (self.instantRecord, _("Instant Record...")),
1431                         })
1432                 self.recording = []
1433
1434         def stopCurrentRecording(self, entry = -1):
1435                 if entry is not None and entry != -1:
1436                         self.session.nav.RecordTimer.removeEntry(self.recording[entry])
1437                         self.recording.remove(self.recording[entry])
1438
1439         def startInstantRecording(self, limitEvent = False):
1440                 serviceref = self.session.nav.getCurrentlyPlayingServiceReference()
1441
1442                 # try to get event info
1443                 event = None
1444                 try:
1445                         service = self.session.nav.getCurrentService()
1446                         epg = eEPGCache.getInstance()
1447                         event = epg.lookupEventTime(serviceref, -1, 0)
1448                         if event is None:
1449                                 info = service.info()
1450                                 ev = info.getEvent(0)
1451                                 event = ev
1452                 except:
1453                         pass
1454
1455                 begin = int(time())
1456                 end = begin + 3600      # dummy
1457                 name = "instant record"
1458                 description = ""
1459                 eventid = None
1460
1461                 if event is not None:
1462                         curEvent = parseEvent(event)
1463                         name = curEvent[2]
1464                         description = curEvent[3]
1465                         eventid = curEvent[4]
1466                         if limitEvent:
1467                                 end = curEvent[1]
1468                 else:
1469                         if limitEvent:
1470                                 self.session.open(MessageBox, _("No event info found, recording indefinitely."), MessageBox.TYPE_INFO)
1471
1472                 if isinstance(serviceref, eServiceReference):
1473                         serviceref = ServiceReference(serviceref)
1474
1475                 recording = RecordTimerEntry(serviceref, begin, end, name, description, eventid, dirname = config.movielist.last_videodir.value)
1476                 recording.dontSave = True
1477                 
1478                 if event is None or limitEvent == False:
1479                         recording.autoincrease = True
1480                         if recording.setAutoincreaseEnd():
1481                                 self.session.nav.RecordTimer.record(recording)
1482                                 self.recording.append(recording)
1483                 else:
1484                                 simulTimerList = self.session.nav.RecordTimer.record(recording)
1485                                 if simulTimerList is not None:  # conflict with other recording
1486                                         name = simulTimerList[1].name
1487                                         name_date = ' '.join((name, strftime('%c', localtime(simulTimerList[1].begin))))
1488                                         print "[TIMER] conflicts with", name_date
1489                                         recording.autoincrease = True   # start with max available length, then increment
1490                                         if recording.setAutoincreaseEnd():
1491                                                 self.session.nav.RecordTimer.record(recording)
1492                                                 self.recording.append(recording)
1493                                                 self.session.open(MessageBox, _("Record time limited due to conflicting timer %s") % name_date, MessageBox.TYPE_INFO)
1494                                         else:
1495                                                 self.session.open(MessageBox, _("Couldn't record due to conflicting timer %s") % name, MessageBox.TYPE_INFO)
1496                                         recording.autoincrease = False
1497                                 else:
1498                                         self.recording.append(recording)
1499
1500         def isInstantRecordRunning(self):
1501                 print "self.recording:", self.recording
1502                 if self.recording:
1503                         for x in self.recording:
1504                                 if x.isRunning():
1505                                         return True
1506                 return False
1507
1508         def recordQuestionCallback(self, answer):
1509                 print "pre:\n", self.recording
1510
1511                 if answer is None or answer[1] == "no":
1512                         return
1513                 list = []
1514                 recording = self.recording[:]
1515                 for x in recording:
1516                         if not x in self.session.nav.RecordTimer.timer_list:
1517                                 self.recording.remove(x)
1518                         elif x.dontSave and x.isRunning():
1519                                 list.append((x, False))
1520
1521                 if answer[1] == "changeduration":
1522                         if len(self.recording) == 1:
1523                                 self.changeDuration(0)
1524                         else:
1525                                 self.session.openWithCallback(self.changeDuration, TimerSelection, list)
1526                 elif answer[1] == "changeendtime":
1527                         if len(self.recording) == 1:
1528                                 self.setEndtime(0)
1529                         else:
1530                                 self.session.openWithCallback(self.setEndtime, TimerSelection, list)
1531                 elif answer[1] == "stop":
1532                         if len(self.recording) == 1:
1533                                 self.stopCurrentRecording(0)
1534                         else:
1535                                 self.session.openWithCallback(self.stopCurrentRecording, TimerSelection, list)
1536                 elif answer[1] in ( "indefinitely" , "manualduration", "manualendtime", "event"):
1537                         self.startInstantRecording(limitEvent = answer[1] in ("event", "manualendtime") or False)
1538                         if answer[1] == "manualduration":
1539                                 self.changeDuration(len(self.recording)-1)
1540                         elif answer[1] == "manualendtime":
1541                                 self.setEndtime(len(self.recording)-1)
1542                 print "after:\n", self.recording
1543
1544         def setEndtime(self, entry):
1545                 if entry is not None and entry >= 0:
1546                         self.selectedEntry = entry
1547                         self.endtime=ConfigClock(default = self.recording[self.selectedEntry].end)
1548                         dlg = self.session.openWithCallback(self.TimeDateInputClosed, TimeDateInput, self.endtime)
1549                         dlg.setTitle(_("Please change recording endtime"))
1550
1551         def TimeDateInputClosed(self, ret):
1552                 if len(ret) > 1:
1553                         if ret[0]:
1554                                 localendtime = localtime(ret[1])
1555                                 print "stopping recording at", strftime("%c", localendtime)
1556                                 if self.recording[self.selectedEntry].end != ret[1]:
1557                                         self.recording[self.selectedEntry].autoincrease = False
1558                                 self.recording[self.selectedEntry].end = ret[1]
1559                                 self.session.nav.RecordTimer.timeChanged(self.recording[self.selectedEntry])
1560
1561         def changeDuration(self, entry):
1562                 if entry is not None and entry >= 0:
1563                         self.selectedEntry = entry
1564                         self.session.openWithCallback(self.inputCallback, InputBox, title=_("How many minutes do you want to record?"), text="5", maxSize=False, type=Input.NUMBER)
1565
1566         def inputCallback(self, value):
1567                 if value is not None:
1568                         print "stopping recording after", int(value), "minutes."
1569                         entry = self.recording[self.selectedEntry]
1570                         if int(value) != 0:
1571                                 entry.autoincrease = False
1572                         entry.end = int(time()) + 60 * int(value)
1573                         self.session.nav.RecordTimer.timeChanged(entry)
1574
1575         def instantRecord(self):
1576                 dir = config.movielist.last_videodir.value
1577                 if not fileExists(dir, 'w'):
1578                         dir = resolveFilename(SCOPE_HDD)
1579                 try:
1580                         stat = os_stat(dir)
1581                 except:
1582                         # XXX: this message is a little odd as we might be recording to a remote device
1583                         self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
1584                         return
1585
1586                 if self.isInstantRecordRunning():
1587                         self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1588                                 title=_("A recording is currently running.\nWhat do you want to do?"), \
1589                                 list=((_("add recording (stop after current event)"), "event"), \
1590                                 (_("add recording (enter recording duration)"), "manualduration"), \
1591                                 (_("add recording (enter recording endtime)"), "manualendtime"), \
1592                                 (_("add recording (indefinitely)"), "indefinitely"), \
1593                                 (_("change recording (duration)"), "changeduration"), \
1594                                 (_("change recording (endtime)"), "changeendtime"), \
1595                                 (_("stop recording"), "stop"), \
1596                                 (_("do nothing"), "no")))
1597                 else:
1598                         self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1599                                 title=_("Start recording?"), \
1600                                 list=((_("add recording (stop after current event)"), "event"), \
1601                                 (_("add recording (enter recording duration)"), "manualduration"), \
1602                                 (_("add recording (enter recording endtime)"), "manualendtime"), \
1603                                 (_("add recording (indefinitely)"), "indefinitely"), \
1604                                 (_("don't record"), "no")))
1605
1606 from Tools.ISO639 import LanguageCodes
1607
1608 class InfoBarAudioSelection:
1609         def __init__(self):
1610                 self["AudioSelectionAction"] = HelpableActionMap(self, "InfobarAudioSelectionActions",
1611                         {
1612                                 "audioSelection": (self.audioSelection, _("Audio Options...")),
1613                         })
1614
1615         def audioSelection(self):
1616                 service = self.session.nav.getCurrentService()
1617                 self.audioTracks = audio = service and service.audioTracks()
1618                 n = audio and audio.getNumberOfTracks() or 0
1619                 tlist = []
1620                 if n > 0:
1621                         self.audioChannel = service.audioChannel()
1622
1623                         idx = 0
1624                         while idx < n:
1625                                 i = audio.getTrackInfo(idx)
1626                                 language = i.getLanguage()
1627                                 description = i.getDescription()
1628
1629                                 if LanguageCodes.has_key(language):
1630                                         language = LanguageCodes[language][0]
1631
1632                                 if len(description):
1633                                         description += " (" + language + ")"
1634                                 else:
1635                                         description = language
1636
1637                                 tlist.append((description, idx))
1638                                 idx += 1
1639
1640                         tlist.sort(key=lambda x: x[0])
1641
1642                         selectedAudio = self.audioTracks.getCurrentTrack()
1643
1644                         selection = 0
1645
1646                         for x in tlist:
1647                                 if x[1] != selectedAudio:
1648                                         selection += 1
1649                                 else:
1650                                         break
1651
1652                         if SystemInfo["CanDownmixAC3"]:
1653                                 tlist = [(_("AC3 downmix") + " - " +(_("Off"), _("On"))[config.av.downmix_ac3.value and 1 or 0], "CALLFUNC", self.changeAC3Downmix),
1654                                         ((_("Left"), _("Stereo"), _("Right"))[self.audioChannel.getCurrentChannel()], "mode"),
1655                                         ("--", "")] + tlist
1656                                 keys = [ "red", "green", "", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] + [""]*n
1657                                 selection += 3
1658                         else:
1659                                 tlist = [((_("Left"), _("Stereo"), _("Right"))[self.audioChannel.getCurrentChannel()], "mode"), ("--", "")] + tlist
1660                                 keys = [ "red", "", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] + [""]*n
1661                                 selection += 2
1662                         self.session.openWithCallback(self.audioSelected, ChoiceBox, title=_("Select audio track"), list = tlist, selection = selection, keys = keys, skin_name = "AudioTrackSelection")
1663                 else:
1664                         del self.audioTracks
1665
1666         def changeAC3Downmix(self, arg):
1667                 choicelist = self.session.current_dialog["list"]
1668                 list = choicelist.list
1669                 t = list[0][1]
1670                 list[0][1]=(t[0], t[1], t[2], t[3], t[4], t[5], t[6],
1671                         _("AC3 downmix") + " - " + (_("On"), _("Off"))[config.av.downmix_ac3.value and 1 or 0])
1672                 choicelist.setList(list)
1673                 if config.av.downmix_ac3.value:
1674                         config.av.downmix_ac3.value = False
1675                 else:
1676                         config.av.downmix_ac3.value = True
1677                 config.av.downmix_ac3.save()
1678
1679         def audioSelected(self, audio):
1680                 if audio is not None:
1681                         if isinstance(audio[1], str):
1682                                 if audio[1] == "mode":
1683                                         keys = ["red", "green", "yellow"]
1684                                         selection = self.audioChannel.getCurrentChannel()
1685                                         tlist = ((_("left"), 0), (_("stereo"), 1), (_("right"), 2))
1686                                         self.session.openWithCallback(self.modeSelected, ChoiceBox, title=_("Select audio mode"), list = tlist, selection = selection, keys = keys, skin_name ="AudioModeSelection")
1687                         else:
1688                                 del self.audioChannel
1689                                 if self.session.nav.getCurrentService().audioTracks().getNumberOfTracks() > audio[1]:
1690                                         self.audioTracks.selectTrack(audio[1])
1691                 else:
1692                         del self.audioChannel
1693                 del self.audioTracks
1694
1695         def modeSelected(self, mode):
1696                 if mode is not None:
1697                         self.audioChannel.selectChannel(mode[1])
1698                 del self.audioChannel
1699
1700 class InfoBarSubserviceSelection:
1701         def __init__(self):
1702                 self["SubserviceSelectionAction"] = HelpableActionMap(self, "InfobarSubserviceSelectionActions",
1703                         {
1704                                 "subserviceSelection": (self.subserviceSelection, _("Subservice list...")),
1705                         })
1706
1707                 self["SubserviceQuickzapAction"] = HelpableActionMap(self, "InfobarSubserviceQuickzapActions",
1708                         {
1709                                 "nextSubservice": (self.nextSubservice, _("Switch to next subservice")),
1710                                 "prevSubservice": (self.prevSubservice, _("Switch to previous subservice"))
1711                         }, -1)
1712                 self["SubserviceQuickzapAction"].setEnabled(False)
1713
1714                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1715                         {
1716                                 iPlayableService.evUpdatedEventInfo: self.checkSubservicesAvail
1717                         })
1718
1719                 self.bsel = None
1720
1721         def checkSubservicesAvail(self):
1722                 service = self.session.nav.getCurrentService()
1723                 subservices = service and service.subServices()
1724                 if not subservices or subservices.getNumberOfSubservices() == 0:
1725                         self["SubserviceQuickzapAction"].setEnabled(False)
1726
1727         def nextSubservice(self):
1728                 self.changeSubservice(+1)
1729
1730         def prevSubservice(self):
1731                 self.changeSubservice(-1)
1732
1733         def changeSubservice(self, direction):
1734                 service = self.session.nav.getCurrentService()
1735                 subservices = service and service.subServices()
1736                 n = subservices and subservices.getNumberOfSubservices()
1737                 if n and n > 0:
1738                         selection = -1
1739                         ref = self.session.nav.getCurrentlyPlayingServiceReference()
1740                         idx = 0
1741                         while idx < n:
1742                                 if subservices.getSubservice(idx).toString() == ref.toString():
1743                                         selection = idx
1744                                         break
1745                                 idx += 1
1746                         if selection != -1:
1747                                 selection += direction
1748                                 if selection >= n:
1749                                         selection=0
1750                                 elif selection < 0:
1751                                         selection=n-1
1752                                 newservice = subservices.getSubservice(selection)
1753                                 if newservice.valid():
1754                                         del subservices
1755                                         del service
1756                                         self.session.nav.playService(newservice, False)
1757
1758         def subserviceSelection(self):
1759                 service = self.session.nav.getCurrentService()
1760                 subservices = service and service.subServices()
1761                 self.bouquets = self.servicelist.getBouquetList()
1762                 n = subservices and subservices.getNumberOfSubservices()
1763                 selection = 0
1764                 if n and n > 0:
1765                         ref = self.session.nav.getCurrentlyPlayingServiceReference()
1766                         tlist = []
1767                         idx = 0
1768                         while idx < n:
1769                                 i = subservices.getSubservice(idx)
1770                                 if i.toString() == ref.toString():
1771                                         selection = idx
1772                                 tlist.append((i.getName(), i))
1773                                 idx += 1
1774
1775                         if self.bouquets and len(self.bouquets):
1776                                 keys = ["red", "blue", "",  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1777                                 if config.usage.multibouquet.value:
1778                                         tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to bouquet"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1779                                 else:
1780                                         tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to favourites"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1781                                 selection += 3
1782                         else:
1783                                 tlist = [(_("Quickzap"), "quickzap", service.subServices()), ("--", "")] + tlist
1784                                 keys = ["red", "",  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1785                                 selection += 2
1786
1787                         self.session.openWithCallback(self.subserviceSelected, ChoiceBox, title=_("Please select a subservice..."), list = tlist, selection = selection, keys = keys, skin_name = "SubserviceSelection")
1788
1789         def subserviceSelected(self, service):
1790                 del self.bouquets
1791                 if not service is None:
1792                         if isinstance(service[1], str):
1793                                 if service[1] == "quickzap":
1794                                         from Screens.SubservicesQuickzap import SubservicesQuickzap
1795                                         self.session.open(SubservicesQuickzap, service[2])
1796                         else:
1797                                 self["SubserviceQuickzapAction"].setEnabled(True)
1798                                 self.session.nav.playService(service[1], False)
1799
1800         def addSubserviceToBouquetCallback(self, service):
1801                 if len(service) > 1 and isinstance(service[1], eServiceReference):
1802                         self.selectedSubservice = service
1803                         if self.bouquets is None:
1804                                 cnt = 0
1805                         else:
1806                                 cnt = len(self.bouquets)
1807                         if cnt > 1: # show bouquet list
1808                                 self.bsel = self.session.openWithCallback(self.bouquetSelClosed, BouquetSelector, self.bouquets, self.addSubserviceToBouquet)
1809                         elif cnt == 1: # add to only one existing bouquet
1810                                 self.addSubserviceToBouquet(self.bouquets[0][1])
1811                                 self.session.open(MessageBox, _("Service has been added to the favourites."), MessageBox.TYPE_INFO)
1812
1813         def bouquetSelClosed(self, confirmed):
1814                 self.bsel = None
1815                 del self.selectedSubservice
1816                 if confirmed:
1817                         self.session.open(MessageBox, _("Service has been added to the selected bouquet."), MessageBox.TYPE_INFO)
1818
1819         def addSubserviceToBouquet(self, dest):
1820                 self.servicelist.addServiceToBouquet(dest, self.selectedSubservice[1])
1821                 if self.bsel:
1822                         self.bsel.close(True)
1823                 else:
1824                         del self.selectedSubservice
1825
1826 class InfoBarAdditionalInfo:
1827         def __init__(self):
1828
1829                 self["RecordingPossible"] = Boolean(fixed=harddiskmanager.HDDCount() > 0 and config.misc.rcused.value == 1)
1830                 self["TimeshiftPossible"] = self["RecordingPossible"]
1831                 self["ShowTimeshiftOnYellow"] = Boolean(fixed=(not config.misc.rcused.value == 0))
1832                 self["ShowAudioOnYellow"] = Boolean(fixed=config.misc.rcused.value == 0)
1833                 self["ShowRecordOnRed"] = Boolean(fixed=config.misc.rcused.value == 1)
1834                 self["ExtensionsAvailable"] = Boolean(fixed=1)
1835
1836 class InfoBarNotifications:
1837         def __init__(self):
1838                 self.onExecBegin.append(self.checkNotifications)
1839                 Notifications.notificationAdded.append(self.checkNotificationsIfExecing)
1840                 self.onClose.append(self.__removeNotification)
1841
1842         def __removeNotification(self):
1843                 Notifications.notificationAdded.remove(self.checkNotificationsIfExecing)
1844
1845         def checkNotificationsIfExecing(self):
1846                 if self.execing:
1847                         self.checkNotifications()
1848
1849         def checkNotifications(self):
1850                 notifications = Notifications.notifications
1851                 if notifications:
1852                         n = notifications[0]
1853
1854                         del notifications[0]
1855                         cb = n[0]
1856
1857                         if n[3].has_key("onSessionOpenCallback"):
1858                                 n[3]["onSessionOpenCallback"]()
1859                                 del n[3]["onSessionOpenCallback"]
1860
1861                         if cb is not None:
1862                                 dlg = self.session.openWithCallback(cb, n[1], *n[2], **n[3])
1863                         else:
1864                                 dlg = self.session.open(n[1], *n[2], **n[3])
1865
1866                         # remember that this notification is currently active
1867                         d = (n[4], dlg)
1868                         Notifications.current_notifications.append(d)
1869                         dlg.onClose.append(boundFunction(self.__notificationClosed, d))
1870
1871         def __notificationClosed(self, d):
1872                 Notifications.current_notifications.remove(d)
1873
1874 class InfoBarServiceNotifications:
1875         def __init__(self):
1876                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1877                         {
1878                                 iPlayableService.evEnd: self.serviceHasEnded
1879                         })
1880
1881         def serviceHasEnded(self):
1882                 print "service end!"
1883
1884                 try:
1885                         self.setSeekState(self.SEEK_STATE_PLAY)
1886                 except:
1887                         pass
1888
1889 class InfoBarCueSheetSupport:
1890         CUT_TYPE_IN = 0
1891         CUT_TYPE_OUT = 1
1892         CUT_TYPE_MARK = 2
1893         CUT_TYPE_LAST = 3
1894
1895         ENABLE_RESUME_SUPPORT = False
1896
1897         def __init__(self, actionmap = "InfobarCueSheetActions"):
1898                 self["CueSheetActions"] = HelpableActionMap(self, actionmap,
1899                         {
1900                                 "jumpPreviousMark": (self.jumpPreviousMark, _("jump to previous marked position")),
1901                                 "jumpNextMark": (self.jumpNextMark, _("jump to next marked position")),
1902                                 "toggleMark": (self.toggleMark, _("toggle a cut mark at the current position"))
1903                         }, prio=1)
1904
1905                 self.cut_list = [ ]
1906                 self.is_closing = False
1907                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1908                         {
1909                                 iPlayableService.evStart: self.__serviceStarted,
1910                         })
1911
1912         def __serviceStarted(self):
1913                 if self.is_closing:
1914                         return
1915                 print "new service started! trying to download cuts!"
1916                 self.downloadCuesheet()
1917
1918                 if self.ENABLE_RESUME_SUPPORT:
1919                         last = None
1920
1921                         for (pts, what) in self.cut_list:
1922                                 if what == self.CUT_TYPE_LAST:
1923                                         last = pts
1924
1925                         if last is not None:
1926                                 self.resume_point = last
1927                                 if config.usage.on_movie_start.value == "ask":
1928                                         Notifications.AddNotificationWithCallback(self.playLastCB, MessageBox, _("Do you want to resume this playback?"), timeout=10)
1929                                 elif config.usage.on_movie_start.value == "resume":
1930 # TRANSLATORS: The string "Resuming playback" flashes for a moment
1931 # TRANSLATORS: at the start of a movie, when the user has selected
1932 # TRANSLATORS: "Resume from last position" as start behavior.
1933 # TRANSLATORS: The purpose is to notify the user that the movie starts
1934 # TRANSLATORS: in the middle somewhere and not from the beginning.
1935 # TRANSLATORS: (Some translators seem to have interpreted it as a
1936 # TRANSLATORS: question or a choice, but it is a statement.)
1937                                         Notifications.AddNotificationWithCallback(self.playLastCB, MessageBox, _("Resuming playback"), timeout=2, type=MessageBox.TYPE_INFO)
1938
1939         def playLastCB(self, answer):
1940                 if answer == True:
1941                         self.doSeek(self.resume_point)
1942                 self.hideAfterResume()
1943
1944         def hideAfterResume(self):
1945                 if isinstance(self, InfoBarShowHide):
1946                         self.hide()
1947
1948         def __getSeekable(self):
1949                 service = self.session.nav.getCurrentService()
1950                 if service is None:
1951                         return None
1952                 return service.seek()
1953
1954         def cueGetCurrentPosition(self):
1955                 seek = self.__getSeekable()
1956                 if seek is None:
1957                         return None
1958                 r = seek.getPlayPosition()
1959                 if r[0]:
1960                         return None
1961                 return long(r[1])
1962
1963         def cueGetEndCutPosition(self):
1964                 ret = False
1965                 isin = True
1966                 for cp in self.cut_list:
1967                         if cp[1] == self.CUT_TYPE_OUT:
1968                                 if isin:
1969                                         isin = False
1970                                         ret = cp[0]
1971                         elif cp[1] == self.CUT_TYPE_IN:
1972                                 isin = True
1973                 return ret
1974                 
1975         def jumpPreviousNextMark(self, cmp, start=False):
1976                 current_pos = self.cueGetCurrentPosition()
1977                 if current_pos is None:
1978                         return False
1979                 mark = self.getNearestCutPoint(current_pos, cmp=cmp, start=start)
1980                 if mark is not None:
1981                         pts = mark[0]
1982                 else:
1983                         return False
1984
1985                 self.doSeek(pts)
1986                 return True
1987
1988         def jumpPreviousMark(self):
1989                 # we add 2 seconds, so if the play position is <2s after
1990                 # the mark, the mark before will be used
1991                 self.jumpPreviousNextMark(lambda x: -x-5*90000, start=True)
1992
1993         def jumpNextMark(self):
1994                 if not self.jumpPreviousNextMark(lambda x: x):
1995                         self.doSeek(-1)
1996
1997         def getNearestCutPoint(self, pts, cmp=abs, start=False):
1998                 # can be optimized
1999                 beforecut = False
2000                 nearest = None
2001                 if start:
2002                         beforecut = True
2003                         bestdiff = cmp(0 - pts)
2004                         if bestdiff >= 0:
2005                                 nearest = [0, False]
2006                 for cp in self.cut_list:
2007                         if beforecut and cp[1] in (self.CUT_TYPE_IN, self.CUT_TYPE_OUT):
2008                                 beforecut = False
2009                                 if cp[1] == self.CUT_TYPE_IN:  # Start is here, disregard previous marks
2010                                         diff = cmp(cp[0] - pts)
2011                                         if diff >= 0:
2012                                                 nearest = cp
2013                                                 bestdiff = diff
2014                                         else:
2015                                                 nearest = None
2016                         if cp[1] in (self.CUT_TYPE_MARK, self.CUT_TYPE_LAST):
2017                                 diff = cmp(cp[0] - pts)
2018                                 if diff >= 0 and (nearest is None or bestdiff > diff):
2019                                         nearest = cp
2020                                         bestdiff = diff
2021                 return nearest
2022
2023         def toggleMark(self, onlyremove=False, onlyadd=False, tolerance=5*90000, onlyreturn=False):
2024                 current_pos = self.cueGetCurrentPosition()
2025                 if current_pos is None:
2026                         print "not seekable"
2027                         return
2028
2029                 nearest_cutpoint = self.getNearestCutPoint(current_pos)
2030
2031                 if nearest_cutpoint is not None and abs(nearest_cutpoint[0] - current_pos) < tolerance:
2032                         if onlyreturn:
2033                                 return nearest_cutpoint
2034                         if not onlyadd:
2035                                 self.removeMark(nearest_cutpoint)
2036                 elif not onlyremove and not onlyreturn:
2037                         self.addMark((current_pos, self.CUT_TYPE_MARK))
2038
2039                 if onlyreturn:
2040                         return None
2041
2042         def addMark(self, point):
2043                 insort(self.cut_list, point)
2044                 self.uploadCuesheet()
2045                 self.showAfterCuesheetOperation()
2046
2047         def removeMark(self, point):
2048                 self.cut_list.remove(point)
2049                 self.uploadCuesheet()
2050                 self.showAfterCuesheetOperation()
2051
2052         def showAfterCuesheetOperation(self):
2053                 if isinstance(self, InfoBarShowHide):
2054                         self.doShow()
2055
2056         def __getCuesheet(self):
2057                 service = self.session.nav.getCurrentService()
2058                 if service is None:
2059                         return None
2060                 return service.cueSheet()
2061
2062         def uploadCuesheet(self):
2063                 cue = self.__getCuesheet()
2064
2065                 if cue is None:
2066                         print "upload failed, no cuesheet interface"
2067                         return
2068                 cue.setCutList(self.cut_list)
2069
2070         def downloadCuesheet(self):
2071                 cue = self.__getCuesheet()
2072
2073                 if cue is None:
2074                         print "download failed, no cuesheet interface"
2075                         self.cut_list = [ ]
2076                 else:
2077                         self.cut_list = cue.getCutList()
2078
2079 class InfoBarSummary(Screen):
2080         skin = """
2081         <screen position="0,0" size="132,64">
2082                 <widget source="global.CurrentTime" render="Label" position="62,46" size="82,18" font="Regular;16" >
2083                         <convert type="ClockToText">WithSeconds</convert>
2084                 </widget>
2085                 <widget source="session.RecordState" render="FixedLabel" text=" " position="62,46" size="82,18" zPosition="1" >
2086                         <convert type="ConfigEntryTest">config.usage.blinking_display_clock_during_recording,True,CheckSourceBoolean</convert>
2087                         <convert type="ConditionalShowHide">Blink</convert>
2088                 </widget>
2089                 <widget source="session.CurrentService" render="Label" position="6,4" size="120,42" font="Regular;18" >
2090                         <convert type="ServiceName">Name</convert>
2091                 </widget>
2092                 <widget source="session.Event_Now" render="Progress" position="6,46" size="46,18" borderWidth="1" >
2093                         <convert type="EventTime">Progress</convert>
2094                 </widget>
2095         </screen>"""
2096
2097 # for picon:  (path="piconlcd" will use LCD picons)
2098 #               <widget source="session.CurrentService" render="Picon" position="6,0" size="120,64" path="piconlcd" >
2099 #                       <convert type="ServiceName">Reference</convert>
2100 #               </widget>
2101
2102 class InfoBarSummarySupport:
2103         def __init__(self):
2104                 pass
2105
2106         def createSummary(self):
2107                 return InfoBarSummary
2108
2109 class InfoBarMoviePlayerSummary(Screen):
2110         skin = """
2111         <screen position="0,0" size="132,64">
2112                 <widget source="global.CurrentTime" render="Label" position="62,46" size="64,18" font="Regular;16" halign="right" >
2113                         <convert type="ClockToText">WithSeconds</convert>
2114                 </widget>
2115                 <widget source="session.RecordState" render="FixedLabel" text=" " position="62,46" size="64,18" zPosition="1" >
2116                         <convert type="ConfigEntryTest">config.usage.blinking_display_clock_during_recording,True,CheckSourceBoolean</convert>
2117                         <convert type="ConditionalShowHide">Blink</convert>
2118                 </widget>
2119                 <widget source="session.CurrentService" render="Label" position="6,4" size="120,42" font="Regular;18" >
2120                         <convert type="ServiceName">Name</convert>
2121                 </widget>
2122                 <widget source="session.CurrentService" render="Progress" position="6,46" size="56,18" borderWidth="1" >
2123                         <convert type="ServicePosition">Position</convert>
2124                 </widget>
2125         </screen>"""
2126
2127 class InfoBarMoviePlayerSummarySupport:
2128         def __init__(self):
2129                 pass
2130
2131         def createSummary(self):
2132                 return InfoBarMoviePlayerSummary
2133
2134 class InfoBarTeletextPlugin:
2135         def __init__(self):
2136                 self.teletext_plugin = None
2137
2138                 for p in plugins.getPlugins(PluginDescriptor.WHERE_TELETEXT):
2139                         self.teletext_plugin = p
2140
2141                 if self.teletext_plugin is not None:
2142                         self["TeletextActions"] = HelpableActionMap(self, "InfobarTeletextActions",
2143                                 {
2144                                         "startTeletext": (self.startTeletext, _("View teletext..."))
2145                                 })
2146                 else:
2147                         print "no teletext plugin found!"
2148
2149         def startTeletext(self):
2150                 self.teletext_plugin(session=self.session, service=self.session.nav.getCurrentService())
2151
2152 class InfoBarSubtitleSupport(object):
2153         def __init__(self):
2154                 object.__init__(self)
2155                 self.subtitle_window = self.session.instantiateDialog(SubtitleDisplay)
2156                 self.__subtitles_enabled = False
2157
2158                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
2159                         {
2160                                 iPlayableService.evEnd: self.__serviceStopped,
2161                                 iPlayableService.evUpdatedInfo: self.__updatedInfo
2162                         })
2163                 self.cached_subtitle_checked = False
2164                 self.__selected_subtitle = None
2165
2166         def __serviceStopped(self):
2167                 self.subtitle_window.hide()
2168                 self.__subtitles_enabled = False
2169                 self.cached_subtitle_checked = False
2170
2171         def __updatedInfo(self):
2172                 if not self.cached_subtitle_checked:
2173                         subtitle = self.getCurrentServiceSubtitle()
2174                         self.cached_subtitle_checked = True
2175                         self.__selected_subtitle = subtitle and subtitle.getCachedSubtitle()
2176                         if self.__selected_subtitle:
2177                                 subtitle.enableSubtitles(self.subtitle_window.instance, self.selected_subtitle)
2178                                 self.subtitle_window.show()
2179                                 self.__subtitles_enabled = True
2180
2181         def getCurrentServiceSubtitle(self):
2182                 service = self.session.nav.getCurrentService()
2183                 return service and service.subtitle()
2184
2185         def setSubtitlesEnable(self, enable=True):
2186                 subtitle = self.getCurrentServiceSubtitle()
2187                 if enable and self.__selected_subtitle is not None:
2188                         if subtitle and not self.__subtitles_enabled:
2189                                 subtitle.enableSubtitles(self.subtitle_window.instance, self.selected_subtitle)
2190                                 self.subtitle_window.show()
2191                                 self.__subtitles_enabled = True
2192                 else:
2193                         if subtitle:
2194                                 subtitle.disableSubtitles(self.subtitle_window.instance)
2195                         self.__subtitles_enabled = False
2196                         self.subtitle_window.hide()
2197
2198         def setSelectedSubtitle(self, subtitle):
2199                 self.__selected_subtitle = subtitle
2200
2201         subtitles_enabled = property(lambda self: self.__subtitles_enabled, setSubtitlesEnable)
2202         selected_subtitle = property(lambda self: self.__selected_subtitle, setSelectedSubtitle)
2203
2204 class InfoBarServiceErrorPopupSupport:
2205         def __init__(self):
2206                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
2207                         {
2208                                 iPlayableService.evTuneFailed: self.__tuneFailed,
2209                                 iPlayableService.evStart: self.__serviceStarted
2210                         })
2211                 self.__serviceStarted()
2212
2213         def __serviceStarted(self):
2214                 self.last_error = None
2215                 Notifications.RemovePopup(id = "ZapError")
2216
2217         def __tuneFailed(self):
2218                 service = self.session.nav.getCurrentService()
2219                 info = service and service.info()
2220                 error = info and info.getInfo(iServiceInformation.sDVBState)
2221
2222                 if error == self.last_error:
2223                         error = None
2224                 else:
2225                         self.last_error = error
2226
2227                 error = {
2228                         eDVBServicePMTHandler.eventNoResources: _("No free tuner!"),
2229                         eDVBServicePMTHandler.eventTuneFailed: _("Tune failed!"),
2230                         eDVBServicePMTHandler.eventNoPAT: _("No data on transponder!\n(Timeout reading PAT)"),
2231                         eDVBServicePMTHandler.eventNoPATEntry: _("Service not found!\n(SID not found in PAT)"),
2232                         eDVBServicePMTHandler.eventNoPMT: _("Service invalid!\n(Timeout reading PMT)"),
2233                         eDVBServicePMTHandler.eventNewProgramInfo: None,
2234                         eDVBServicePMTHandler.eventTuned: None,
2235                         eDVBServicePMTHandler.eventSOF: None,
2236                         eDVBServicePMTHandler.eventEOF: None,
2237                         eDVBServicePMTHandler.eventMisconfiguration: _("Service unavailable!\nCheck tuner configuration!"),
2238                 }.get(error) #this returns None when the key not exist in the dict
2239
2240                 if error is not None:
2241                         Notifications.AddPopup(text = error, type = MessageBox.TYPE_ERROR, timeout = 5, id = "ZapError")
2242                 else:
2243                         Notifications.RemovePopup(id = "ZapError")