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