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