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