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