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