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