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