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