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