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