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