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