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