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