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