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