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