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