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