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