6fbf9ded5074cec49a55e8c9441a3deba1c36dee
[enigma2.git] / lib / python / Screens / InfoBarGenerics.py
1 from Screen import Screen
2 from Components.ActionMap import ActionMap, HelpableActionMap
3 from Components.ActionMap import NumberActionMap
4 from Components.Label import *
5 from Components.ProgressBar import *
6 from Components.config import configfile, configsequencearg
7 from Components.config import config, configElement, ConfigSubsection, configSequence
8 from ChannelSelection import ChannelSelection, BouquetSelector
9
10 from Components.Pixmap import Pixmap, PixmapConditional
11 from Components.BlinkingPixmap import BlinkingPixmapConditional
12 from Components.ServiceName import ServiceName
13 from Components.EventInfo import EventInfo, EventInfoProgress
14
15 from ServiceReference import ServiceReference
16 from EpgSelection import EPGSelection
17
18 from Screens.MessageBox import MessageBox
19 from Screens.Dish import Dish
20 from Screens.Standby import Standby
21 from Screens.EventView import EventViewEPGSelect
22 from Screens.MinuteInput import MinuteInput
23 from Components.Harddisk import harddiskmanager
24
25 from Components.ServiceEventTracker import ServiceEventTracker
26
27 from Tools import Notifications
28 from Tools.Directories import *
29
30 #from enigma import eTimer, eDVBVolumecontrol, quitMainloop
31 from enigma import *
32
33 import time
34 import os
35 import bisect
36
37 from Components.config import config, currentConfigSelectionElement
38
39 # hack alert!
40 from Menu import MainMenu, mdom
41
42 class InfoBarDish:
43         def __init__(self):
44                 self.dishDialog = self.session.instantiateDialog(Dish)
45                 self.onLayoutFinish.append(self.dishDialog.show)
46
47 class InfoBarShowHide:
48         """ InfoBar show/hide control, accepts toggleShow and hide actions, might start
49         fancy animations. """
50         STATE_HIDDEN = 0
51         STATE_HIDING = 1
52         STATE_SHOWING = 2
53         STATE_SHOWN = 3
54         
55         def __init__(self):
56                 self["ShowHideActions"] = ActionMap( ["InfobarShowHideActions"] ,
57                         {
58                                 "toggleShow": self.toggleShow,
59                                 "hide": self.hide,
60                         })
61
62                 self.__state = self.STATE_SHOWN
63                 self.__locked = 0
64                 
65                 self.onExecBegin.append(self.show)
66                 
67                 self.hideTimer = eTimer()
68                 self.hideTimer.timeout.get().append(self.doTimerHide)
69                 self.hideTimer.start(5000, True)
70                 
71                 self.onShow.append(self.__onShow)
72                 self.onHide.append(self.__onHide)
73
74         def __onShow(self):
75                 self.__state = self.STATE_SHOWN
76                 self.startHideTimer()
77         
78         def startHideTimer(self):
79                 if self.__state == self.STATE_SHOWN and not self.__locked:
80                         self.hideTimer.start(5000, True)
81
82         def __onHide(self):
83                 self.__state = self.STATE_HIDDEN
84
85         def doShow(self):
86                 self.show()
87                 self.startHideTimer()
88
89         def doTimerHide(self):
90                 self.hideTimer.stop()
91                 if self.__state == self.STATE_SHOWN:
92                         self.hide()
93
94         def toggleShow(self):
95                 if self.__state == self.STATE_SHOWN:
96                         self.hide()
97                         self.hideTimer.stop()
98                 elif self.__state == self.STATE_HIDDEN:
99                         self.show()
100
101         def lockShow(self):
102                 self.__locked = self.__locked + 1
103                 if self.execing:
104                         self.show()
105                         self.hideTimer.stop()
106         
107         def unlockShow(self):
108                 self.__locked = self.__locked - 1
109                 if self.execing:
110                         self.startHideTimer()
111
112 #       def startShow(self):
113 #               self.instance.m_animation.startMoveAnimation(ePoint(0, 600), ePoint(0, 380), 100)
114 #               self.__state = self.STATE_SHOWN
115 #       
116 #       def startHide(self):
117 #               self.instance.m_animation.startMoveAnimation(ePoint(0, 380), ePoint(0, 600), 100)
118 #               self.__state = self.STATE_HIDDEN
119
120 class NumberZap(Screen):
121         def quit(self):
122                 self.Timer.stop()
123                 self.close(0)
124
125         def keyOK(self):
126                 self.Timer.stop()
127                 self.close(int(self["number"].getText()))
128
129         def keyNumberGlobal(self, number):
130                 self.Timer.start(3000, True)            #reset timer
131                 self.field = self.field + str(number)
132                 self["number"].setText(self.field)
133                 if len(self.field) >= 4:
134                         self.keyOK()
135
136         def __init__(self, session, number):
137                 Screen.__init__(self, session)
138                 self.field = str(number)
139
140                 self["channel"] = Label(_("Channel:"))
141
142                 self["number"] = Label(self.field)
143
144                 self["actions"] = NumberActionMap( [ "SetupActions" ], 
145                         {
146                                 "cancel": self.quit,
147                                 "ok": self.keyOK,
148                                 "1": self.keyNumberGlobal,
149                                 "2": self.keyNumberGlobal,
150                                 "3": self.keyNumberGlobal,
151                                 "4": self.keyNumberGlobal,
152                                 "5": self.keyNumberGlobal,
153                                 "6": self.keyNumberGlobal,
154                                 "7": self.keyNumberGlobal,
155                                 "8": self.keyNumberGlobal,
156                                 "9": self.keyNumberGlobal,
157                                 "0": self.keyNumberGlobal
158                         })
159
160                 self.Timer = eTimer()
161                 self.Timer.timeout.get().append(self.keyOK)
162                 self.Timer.start(3000, True)
163
164 class InfoBarPowerKey:
165         """ PowerKey stuff - handles the powerkey press and powerkey release actions"""
166         
167         def __init__(self):
168                 self.powerKeyTimer = eTimer()
169                 self.powerKeyTimer.timeout.get().append(self.powertimer)
170                 self["PowerKeyActions"] = HelpableActionMap(self, "PowerKeyActions",
171                         {
172                                 "powerdown": self.powerdown,
173                                 "powerup": self.powerup,
174                                 "discreteStandby": (self.standby, "Go standby"),
175                                 "discretePowerOff": (self.quit, "Go to deep standby"),
176                         })
177
178         def powertimer(self):   
179                 print "PowerOff - Now!"
180                 self.quit()
181         
182         def powerdown(self):
183                 self.standbyblocked = 0
184                 self.powerKeyTimer.start(3000, True)
185
186         def powerup(self):
187                 self.powerKeyTimer.stop()
188                 if self.standbyblocked == 0:
189                         self.standbyblocked = 1
190                         self.standby()
191
192         def standby(self):
193                 self.session.open(Standby, self)
194
195         def quit(self):
196                 # halt
197                 quitMainloop(1)
198
199 class InfoBarNumberZap:
200         """ Handles an initial number for NumberZapping """
201         def __init__(self):
202                 self["NumberActions"] = NumberActionMap( [ "NumberActions"],
203                         {
204                                 "1": self.keyNumberGlobal,
205                                 "2": self.keyNumberGlobal,
206                                 "3": self.keyNumberGlobal,
207                                 "4": self.keyNumberGlobal,
208                                 "5": self.keyNumberGlobal,
209                                 "6": self.keyNumberGlobal,
210                                 "7": self.keyNumberGlobal,
211                                 "8": self.keyNumberGlobal,
212                                 "9": self.keyNumberGlobal,
213                                 "0": self.keyNumberGlobal,
214                         })
215
216         def keyNumberGlobal(self, number):
217 #               print "You pressed number " + str(number)
218                 if number == 0:
219                         self.servicelist.recallPrevService()
220                         self.doShow()
221                 else:
222                         self.session.openWithCallback(self.numberEntered, NumberZap, number)
223
224         def numberEntered(self, retval):
225 #               print self.servicelist
226                 if retval > 0:
227                         self.zapToNumber(retval)
228
229         def searchNumberHelper(self, serviceHandler, num, bouquet):
230                 servicelist = serviceHandler.list(bouquet)
231                 if not servicelist is None:
232                         while num:
233                                 serviceIterator = servicelist.getNext()
234                                 if not serviceIterator.valid(): #check end of list
235                                         break
236                                 if serviceIterator.flags: #assume normal dvb service have no flags set
237                                         continue
238                                 num -= 1;
239                         if not num: #found service with searched number ?
240                                 return serviceIterator, 0
241                 return None, num
242
243         def zapToNumber(self, number):
244                 bouquet = self.servicelist.bouquet_root
245                 service = None
246                 serviceHandler = eServiceCenter.getInstance()
247                 if bouquet.toString().find('FROM BOUQUET "bouquets.') == -1: #FIXME HACK
248                         service, number = self.searchNumberHelper(serviceHandler, number, bouquet)
249                 else:
250                         bouquetlist = serviceHandler.list(bouquet)
251                         if not bouquetlist is None:
252                                 while number:
253                                         bouquet = self.servicelist.appendDVBTypes(bouquetlist.getNext())
254                                         if not bouquet.valid(): #check end of list
255                                                 break
256                                         if (bouquet.flags & eServiceReference.flagDirectory) != eServiceReference.flagDirectory:
257                                                 continue
258                                         service, number = self.searchNumberHelper(serviceHandler, number, bouquet)
259                 if not service is None:
260                         if self.servicelist.getRoot() != bouquet: #already in correct bouquet?
261                                 self.servicelist.clearPath()
262                                 if self.servicelist.bouquet_root != bouquet:
263                                         self.servicelist.enterPath(self.servicelist.bouquet_root)
264                                 self.servicelist.enterPath(bouquet)
265                         self.servicelist.setCurrentSelection(service) #select the service in servicelist
266                         self.servicelist.zap()
267
268 class InfoBarChannelSelection:
269         """ ChannelSelection - handles the channelSelection dialog and the initial 
270         channelChange actions which open the channelSelection dialog """
271         def __init__(self):
272                 #instantiate forever
273                 self.servicelist = self.session.instantiateDialog(ChannelSelection)
274
275                 self["ChannelSelectActions"] = HelpableActionMap(self, "InfobarChannelSelection",
276                         {
277                                 "switchChannelUp": self.switchChannelUp,
278                                 "switchChannelDown": self.switchChannelDown,
279                                 "zapUp": (self.zapUp, _("previous channel")),
280                                 "zapDown": (self.zapDown, _("next channel")),
281                                 "historyBack": (self.historyBack, _("previous channel in history")),
282                                 "historyNext": (self.historyNext, _("next channel in history"))
283                         })
284
285         def historyBack(self):
286                 self.servicelist.historyBack()
287
288         def historyNext(self):
289                 self.servicelist.historyNext()
290
291         def switchChannelUp(self):
292                 self.servicelist.moveUp()
293                 self.session.execDialog(self.servicelist)
294
295         def switchChannelDown(self):
296                 self.servicelist.moveDown()
297                 self.session.execDialog(self.servicelist)
298
299         def zapUp(self):
300                 if currentConfigSelectionElement(config.usage.quickzap_bouquet_change) == "yes":
301                         if self.servicelist.inBouquet() and self.servicelist.atBegin():
302                                 self.servicelist.prevBouquet()
303                 self.servicelist.moveUp()
304                 self.servicelist.zap()
305                 self.doShow()
306
307         def zapDown(self):
308                 if currentConfigSelectionElement(config.usage.quickzap_bouquet_change) == "yes" and self.servicelist.inBouquet() and self.servicelist.atEnd():
309                         self.servicelist.nextBouquet()
310                 else:
311                         self.servicelist.moveDown()
312                 self.servicelist.zap()
313                 self.doShow()
314
315 class InfoBarMenu:
316         """ Handles a menu action, to open the (main) menu """
317         def __init__(self):
318                 self["MenuActions"] = HelpableActionMap(self, "InfobarMenuActions", 
319                         {
320                                 "mainMenu": (self.mainMenu, "Enter main menu..."),
321                         })
322
323         def mainMenu(self):
324                 print "loading mainmenu XML..."
325                 menu = mdom.childNodes[0]
326                 assert menu.tagName == "menu", "root element in menu must be 'menu'!"
327                 self.session.open(MainMenu, menu, menu.childNodes)
328
329 class InfoBarEPG:
330         """ EPG - Opens an EPG list when the showEPGList action fires """
331         def __init__(self):
332                 self["EPGActions"] = HelpableActionMap(self, "InfobarEPGActions", 
333                         {
334                                 "showEventInfo": (self.openEventView, _("show EPG...")),
335                         })
336
337         def zapToService(self, service):
338                 if not service is None:
339                         if self.servicelist.getRoot() != self.epg_bouquet: #already in correct bouquet?
340                                 self.servicelist.clearPath()
341                                 if self.servicelist.bouquet_root != self.epg_bouquet:
342                                         self.servicelist.enterPath(self.servicelist.bouquet_root)
343                                 self.servicelist.enterPath(self.epg_bouquet)
344                         self.servicelist.setCurrentSelection(service) #select the service in servicelist
345                         self.servicelist.zap()
346
347         def openBouquetEPG(self, bouquet, withCallback=True):
348                 ptr=eEPGCache.getInstance()
349                 services = [ ]
350                 servicelist = eServiceCenter.getInstance().list(bouquet)
351                 if not servicelist is None:
352                         while True:
353                                 service = servicelist.getNext()
354                                 if not service.valid(): #check if end of list
355                                         break
356                                 if service.flags: #ignore non playable services
357                                         continue
358                                 services.append(ServiceReference(service))
359                 if len(services):
360                         self.epg_bouquet = bouquet
361                         if withCallback:
362                                 self.session.openWithCallback(self.closed, EPGSelection, services, self.zapToService)
363                         else:
364                                 self.session.open(EPGSelection, services, self.zapToService)
365
366         def closed(self, ret):
367                 if ret:
368                         self.close(ret)
369
370         def openMultiServiceEPG(self, withCallback=True):
371                 bouquets = self.servicelist.getBouquetList()
372                 if bouquets is None:
373                         cnt = 0
374                 else:
375                         cnt = len(bouquets)
376                 if cnt > 1: # show bouquet list
377                         if withCallback:
378                                 self.session.openWithCallback(self.closed, BouquetSelector, bouquets, self.openBouquetEPG)
379                         else:
380                                 self.session.open(BouquetSelector, bouquets, self.openBouquetEPG)
381                 elif cnt == 1: 
382                         self.openBouquetEPG(bouquets[0][1], withCallback)
383
384         def openSingleServiceEPG(self):
385                 ref=self.session.nav.getCurrentlyPlayingServiceReference()
386                 ptr=eEPGCache.getInstance()
387                 self.session.openWithCallback(self.closed, EPGSelection, ref)
388
389         def openEventView(self):
390                 self.epglist = [ ]
391                 service = self.session.nav.getCurrentService()
392                 ref = self.session.nav.getCurrentlyPlayingServiceReference()
393                 info = service.info()
394                 ptr=info.getEvent(0)
395                 if ptr:
396                         self.epglist.append(ptr)
397                 ptr=info.getEvent(1)
398                 if ptr:
399                         self.epglist.append(ptr)
400                 if len(self.epglist) == 0:
401                         epg = eEPGCache.getInstance()
402                         ptr = epg.lookupEventTime(ref, -1)
403                         if ptr:
404                                 self.epglist.append(ptr)
405                                 ptr = epg.lookupEventTime(ref, ptr.getBeginTime(), +1)
406                                 if ptr:
407                                         self.epglist.append(ptr)
408                 if len(self.epglist) > 0:
409                         self.session.open(EventViewEPGSelect, self.epglist[0], ServiceReference(ref), self.eventViewCallback, self.openSingleServiceEPG, self.openMultiServiceEPG)
410                 else:
411                         print "no epg for the service avail.. so we show multiepg instead of eventinfo"
412                         self.openMultiServiceEPG(False)
413
414         def eventViewCallback(self, setEvent, setService, val): #used for now/next displaying
415                 if len(self.epglist) > 1:
416                         tmp = self.epglist[0]
417                         self.epglist[0]=self.epglist[1]
418                         self.epglist[1]=tmp
419                         setEvent(self.epglist[0])
420
421 from math import log
422
423 class InfoBarTuner:
424         """provides a snr/agc/ber display"""
425         def __init__(self):
426                 self["snr"] = Label()
427                 self["agc"] = Label()
428                 self["ber"] = Label()
429                 self["snr_percent"] = Label()
430                 self["agc_percent"] = Label()
431                 self["ber_count"] = Label()
432                 self["snr_progress"] = ProgressBar()
433                 self["agc_progress"] = ProgressBar()
434                 self["ber_progress"] = ProgressBar()
435                 self.timer = eTimer()
436                 self.timer.timeout.get().append(self.updateTunerInfo)
437                 self.timer.start(1000)
438
439         def calc(self,val):
440                 if not val:
441                         return 0
442                 if val < 2500:
443                         return (long)(log(val)/log(2))
444                 return val*100/65535
445
446         def updateTunerInfo(self):
447                 if self.instance.isVisible():
448                         service = self.session.nav.getCurrentService()
449                         snr=0
450                         agc=0
451                         ber=0
452                         if service is not None:
453                                 feinfo = service.frontendStatusInfo()
454                                 if feinfo is not None:
455                                         ber=feinfo.getFrontendInfo(iFrontendStatusInformation.bitErrorRate)
456                                         snr=feinfo.getFrontendInfo(iFrontendStatusInformation.signalPower)*100/65536
457                                         agc=feinfo.getFrontendInfo(iFrontendStatusInformation.signalQuality)*100/65536
458                         self["snr_percent"].setText("%d%%"%(snr))
459                         self["agc_percent"].setText("%d%%"%(agc))
460                         self["ber_count"].setText("%d"%(ber))
461                         self["snr_progress"].setValue(snr)
462                         self["agc_progress"].setValue(agc)
463                         self["ber_progress"].setValue(self.calc(ber))
464
465 class InfoBarEvent:
466         """provides a current/next event info display"""
467         def __init__(self):
468                 self["Event_Now_StartTime"] = EventInfo(self.session.nav, EventInfo.Now_StartTime)
469                 self["Event_Next_StartTime"] = EventInfo(self.session.nav, EventInfo.Next_StartTime)
470                                 
471                 self["Event_Now"] = EventInfo(self.session.nav, EventInfo.Now)
472                 self["Event_Next"] = EventInfo(self.session.nav, EventInfo.Next)
473
474                 self["Event_Now_Duration"] = EventInfo(self.session.nav, EventInfo.Now_Remaining)
475                 self["Event_Next_Duration"] = EventInfo(self.session.nav, EventInfo.Next_Duration)
476
477                 self["Now_ProgressBar"] = EventInfoProgress(self.session.nav, EventInfo.Now)
478
479 class InfoBarServiceName:
480         def __init__(self):
481                 self["ServiceName"] = ServiceName(self.session.nav)
482
483 class InfoBarSeek:
484         """handles actions like seeking, pause"""
485         
486         # ispause, isff, issm
487         SEEK_STATE_PLAY = (0, 0, 0, ">")
488         SEEK_STATE_PAUSE = (1, 0, 0, "||")
489         SEEK_STATE_FF_2X = (0, 2, 0, ">> 2x")
490         SEEK_STATE_FF_4X = (0, 4, 0, ">> 4x")
491         SEEK_STATE_FF_8X = (0, 8, 0, ">> 8x")
492         SEEK_STATE_FF_32X = (0, 32, 0, ">> 32x")
493         SEEK_STATE_FF_64X = (0, 64, 0, ">> 64x")
494         SEEK_STATE_FF_128X = (0, 128, 0, ">> 128x")
495         
496         SEEK_STATE_BACK_16X = (0, -16, 0, "<< 16x")
497         SEEK_STATE_BACK_32X = (0, -32, 0, "<< 32x")
498         SEEK_STATE_BACK_64X = (0, -64, 0, "<< 64x")
499         SEEK_STATE_BACK_128X = (0, -128, 0, "<< 128x")
500         
501         SEEK_STATE_SM_HALF = (0, 0, 2, "/2")
502         SEEK_STATE_SM_QUARTER = (0, 0, 4, "/4")
503         SEEK_STATE_SM_EIGHTH = (0, 0, 8, "/8")
504         
505         def __init__(self):
506                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
507                         {
508                                 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged,
509                                 iPlayableService.evStart: self.__serviceStarted,
510                                 
511                                 iPlayableService.evEOF: self.__evEOF,
512                                 iPlayableService.evSOF: self.__evSOF,
513                         })
514                 self["SeekActions"] = HelpableActionMap(self, "InfobarSeekActions", 
515                         {
516                                 "pauseService": (self.pauseService, "pause"),
517                                 "unPauseService": (self.unPauseService, "continue"),
518                                 
519                                 "seekFwd": (self.seekFwd, "skip forward"),
520                                 "seekFwdDown": self.seekFwdDown,
521                                 "seekFwdUp": self.seekFwdUp,
522                                 "seekBack": (self.seekBack, "skip backward"),
523                                 "seekBackDown": self.seekBackDown,
524                                 "seekBackUp": self.seekBackUp,
525                         }, prio=-1)
526                         # give them a little more priority to win over color buttons
527
528                 self.seekstate = self.SEEK_STATE_PLAY
529                 self.onClose.append(self.delTimer)
530                 
531                 self.fwdtimer = False
532                 self.fwdKeyTimer = eTimer()
533                 self.fwdKeyTimer.timeout.get().append(self.fwdTimerFire)
534
535                 self.rwdtimer = False
536                 self.rwdKeyTimer = eTimer()
537                 self.rwdKeyTimer.timeout.get().append(self.rwdTimerFire)
538                 
539                 self.onPlayStateChanged = [ ]
540                 
541                 self.lockedBecauseOfSkipping = False
542         
543         def up(self):
544                 pass
545         
546         def down(self):
547                 pass
548         
549         def delTimer(self):
550                 del self.fwdKeyTimer
551                 del self.rwdKeyTimer
552         
553         def getSeek(self):
554                 service = self.session.nav.getCurrentService()
555                 if service is None:
556                         return False
557
558                 seek = service.seek()
559
560                 if seek is None or not seek.isCurrentlySeekable():
561                         return None
562                 
563                 return seek
564         
565         def isSeekable(self):
566                 if self.getSeek() is None:
567                         return False
568                 return True
569
570         def __seekableStatusChanged(self):
571                 print "seekable status changed!"
572                 if not self.isSeekable():
573                         self["SeekActions"].setEnabled(False)
574                         print "not seekable, return to play"
575                         self.setSeekState(self.SEEK_STATE_PLAY)
576                 else:
577                         self["SeekActions"].setEnabled(True)
578                         print "seekable"
579
580         def __serviceStarted(self):
581                 self.seekstate = self.SEEK_STATE_PLAY
582
583         def setSeekState(self, state):
584                 service = self.session.nav.getCurrentService()
585                 
586                 if service is None:
587                         return False
588                 
589                 if not self.isSeekable():
590                         if state not in [self.SEEK_STATE_PLAY, self.SEEK_STATE_PAUSE]:
591                                 state = self.SEEK_STATE_PLAY
592                 
593                 pauseable = service.pause()
594
595                 if pauseable is None:
596                         print "not pauseable."
597                         state = self.SEEK_STATE_PLAY
598                 
599                 oldstate = self.seekstate
600                 self.seekstate = state
601                 
602                 for i in range(3):
603                         if oldstate[i] != self.seekstate[i]:
604                                 (self.session.nav.pause, pauseable.setFastForward, pauseable.setSlowMotion)[i](self.seekstate[i])
605
606                 for c in self.onPlayStateChanged:
607                         c(self.seekstate)
608                 
609                 self.checkSkipShowHideLock()
610
611                 return True
612                 
613         def pauseService(self):
614                 if self.seekstate == self.SEEK_STATE_PAUSE:
615                         print "pause, but in fact unpause"
616                         self.unPauseService()
617                 else:
618                         if self.seekstate == self.SEEK_STATE_PLAY:
619                                 print "yes, playing."
620                         else:
621                                 print "no", self.seekstate
622                         print "pause"
623                         self.setSeekState(self.SEEK_STATE_PAUSE);
624                 
625         def unPauseService(self):
626                 print "unpause"
627                 self.setSeekState(self.SEEK_STATE_PLAY);
628         
629         def doSeek(self, seektime):
630                 print "doseek", seektime
631                 service = self.session.nav.getCurrentService()
632                 if service is None:
633                         return
634                 
635                 seekable = self.getSeek()
636                 if seekable is None:
637                         return
638                 
639                 seekable.seekTo(90 * seektime)
640
641         def seekFwdDown(self):
642                 print "start fwd timer"
643                 self.fwdtimer = True
644                 self.fwdKeyTimer.start(1000)
645
646         def seekBackDown(self):
647                 print "start rewind timer"
648                 self.rwdtimer = True
649                 self.rwdKeyTimer.start(1000)
650
651         def seekFwdUp(self):
652                 print "seekFwdUp"
653                 if self.fwdtimer:
654                         self.fwdKeyTimer.stop()
655                         self.fwdtimer = False
656                         self.seekFwd()
657
658         def seekFwd(self):
659                 lookup = {
660                                 self.SEEK_STATE_PLAY: self.SEEK_STATE_FF_2X,
661                                 self.SEEK_STATE_PAUSE: self.SEEK_STATE_SM_EIGHTH,
662                                 self.SEEK_STATE_FF_2X: self.SEEK_STATE_FF_4X,
663                                 self.SEEK_STATE_FF_4X: self.SEEK_STATE_FF_8X,
664                                 self.SEEK_STATE_FF_8X: self.SEEK_STATE_FF_32X,
665                                 self.SEEK_STATE_FF_32X: self.SEEK_STATE_FF_64X,
666                                 self.SEEK_STATE_FF_64X: self.SEEK_STATE_FF_128X,
667                                 self.SEEK_STATE_FF_128X: self.SEEK_STATE_FF_128X,
668                                 self.SEEK_STATE_BACK_16X: self.SEEK_STATE_PLAY,
669                                 self.SEEK_STATE_BACK_32X: self.SEEK_STATE_BACK_16X,
670                                 self.SEEK_STATE_BACK_64X: self.SEEK_STATE_BACK_32X,
671                                 self.SEEK_STATE_BACK_128X: self.SEEK_STATE_BACK_64X,
672                                 self.SEEK_STATE_SM_HALF: self.SEEK_STATE_SM_HALF,
673                                 self.SEEK_STATE_SM_QUARTER: self.SEEK_STATE_SM_HALF,
674                                 self.SEEK_STATE_SM_EIGHTH: self.SEEK_STATE_SM_QUARTER
675                         }
676                 self.setSeekState(lookup[self.seekstate])
677         
678         def seekBackUp(self):
679                 print "seekBackUp"
680                 if self.rwdtimer:
681                         self.rwdKeyTimer.stop()
682                         self.rwdtimer = False
683                         self.seekBack()
684                 
685         def seekBack(self):
686                 lookup = {
687                                 self.SEEK_STATE_PLAY: self.SEEK_STATE_BACK_16X,
688                                 self.SEEK_STATE_PAUSE: self.SEEK_STATE_PAUSE,
689                                 self.SEEK_STATE_FF_2X: self.SEEK_STATE_PLAY,
690                                 self.SEEK_STATE_FF_4X: self.SEEK_STATE_FF_2X,
691                                 self.SEEK_STATE_FF_8X: self.SEEK_STATE_FF_4X,
692                                 self.SEEK_STATE_FF_32X: self.SEEK_STATE_FF_8X,
693                                 self.SEEK_STATE_FF_64X: self.SEEK_STATE_FF_32X,
694                                 self.SEEK_STATE_FF_128X: self.SEEK_STATE_FF_64X,
695                                 self.SEEK_STATE_BACK_16X: self.SEEK_STATE_BACK_32X,
696                                 self.SEEK_STATE_BACK_32X: self.SEEK_STATE_BACK_64X,
697                                 self.SEEK_STATE_BACK_64X: self.SEEK_STATE_BACK_128X,
698                                 self.SEEK_STATE_BACK_128X: self.SEEK_STATE_BACK_128X,
699                                 self.SEEK_STATE_SM_HALF: self.SEEK_STATE_SM_QUARTER,
700                                 self.SEEK_STATE_SM_QUARTER: self.SEEK_STATE_SM_EIGHTH,
701                                 self.SEEK_STATE_SM_EIGHTH: self.SEEK_STATE_PAUSE
702                         }
703                 self.setSeekState(lookup[self.seekstate])
704
705         def fwdTimerFire(self):
706                 print "Display seek fwd"
707                 self.fwdKeyTimer.stop()
708                 self.fwdtimer = False
709                 self.session.openWithCallback(self.fwdSeekTo, MinuteInput)
710                 
711         def fwdSeekTo(self, minutes):
712                 print "Seek", minutes, "minutes forward"
713                 if minutes != 0:
714                         seekable = self.getSeek()
715                         if seekable is not None:
716                                 seekable.seekRelative(1, minutes * 60 * 90000)
717         
718         def rwdTimerFire(self):
719                 print "rwdTimerFire"
720                 self.rwdKeyTimer.stop()
721                 self.rwdtimer = False
722                 self.session.openWithCallback(self.rwdSeekTo, MinuteInput)
723         
724         def rwdSeekTo(self, minutes):
725                 print "rwdSeekTo"
726                 self.fwdSeekTo(0 - minutes)
727         
728         def checkSkipShowHideLock(self):
729                 wantlock = self.seekstate != self.SEEK_STATE_PLAY
730                 
731                 if self.lockedBecauseOfSkipping and not wantlock:
732                         self.unlockShow()
733                         self.lockedBecauseOfSkipping = False
734                 
735                 if wantlock and not self.lockedBecauseOfSkipping:
736                         self.lockShow()
737                         self.lockedBecauseOfSkipping = True
738
739         def __evEOF(self):
740                 if self.seekstate != self.SEEK_STATE_PLAY:
741                         self.setSeekState(self.SEEK_STATE_PAUSE)
742                         # HACK
743                         self.getSeek().seekRelative(1, -90000)
744                         self.setSeekState(self.SEEK_STATE_PLAY)
745                 else:
746                         self.setSeekState(self.SEEK_STATE_PAUSE)
747         
748         def __evSOF(self):
749                 self.setSeekState(self.SEEK_STATE_PLAY)
750                 self.doSeek(0)
751
752         def seekRelative(self, diff):
753                 seekable = self.getSeek()
754                 if seekable is not None:
755                         seekable.seekRelative(0, diff)
756
757 from Screens.PVRState import PVRState
758
759 class InfoBarPVRState:
760         def __init__(self):
761                 self.onPlayStateChanged.append(self.__playStateChanged)
762                 self.pvrStateDialog = self.session.instantiateDialog(PVRState)
763                 self.onShow.append(self.__mayShow)
764                 self.onHide.append(self.pvrStateDialog.hide)
765         
766         def __mayShow(self):
767                 if self.seekstate != self.SEEK_STATE_PLAY:
768                         self.pvrStateDialog.show()
769
770         def __playStateChanged(self, state):
771                 playstateString = state[3]
772                 self.pvrStateDialog["state"].setText(playstateString)
773                 self.__mayShow()
774
775 class InfoBarShowMovies:
776
777         # i don't really like this class. 
778         # it calls a not further specified "movie list" on up/down/movieList,
779         # so this is not more than an action map
780         def __init__(self):
781                 self["MovieListActions"] = HelpableActionMap(self, "InfobarMovieListActions", 
782                         {
783                                 "movieList": (self.showMovies, "movie list"),
784                                 "up": (self.showMovies, "movie list"),
785                                 "down": (self.showMovies, "movie list")
786                         })
787
788 # InfoBarTimeshift requires InfoBarSeek, instantiated BEFORE!
789
790 # Hrmf.
791 #
792 # Timeshift works the following way:
793 #                                         demux0   demux1                    "TimeshiftActions" "TimeshiftActivateActions" "SeekActions"
794 # - normal playback                       TUNER    unused      PLAY               enable                disable              disable
795 # - user presses "yellow" button.         TUNER    record      PAUSE              enable                disable              enable
796 # - user presess pause again              FILE     record      PLAY               enable                disable              enable
797 # - user fast forwards                    FILE     record      FF                 enable                disable              enable
798 # - end of timeshift buffer reached       TUNER    record      PLAY               enable                enable               disable
799 # - user backwards                        FILE     record      BACK  # !!         enable                disable              enable
800 #
801
802 # in other words:
803 # - when a service is playing, pressing the "timeshiftStart" button ("yellow") enables recording ("enables timeshift"),
804 # freezes the picture (to indicate timeshift), sets timeshiftMode ("activates timeshift")
805 # now, the service becomes seekable, so "SeekActions" are enabled, "TimeshiftEnableActions" are disabled.
806 # - the user can now PVR around
807 # - if it hits the end, the service goes into live mode ("deactivates timeshift", it's of course still "enabled")
808 # the service looses it's "seekable" state. It can still be paused, but just to activate timeshift right
809 # after!
810 # the seek actions will be disabled, but the timeshiftActivateActions will be enabled
811 # - if the user rewinds, or press pause, timeshift will be activated again
812
813 # note that a timeshift can be enabled ("recording") and
814 # activated (currently time-shifting).
815
816 class InfoBarTimeshift:
817         def __init__(self):
818                 self["TimeshiftActions"] = HelpableActionMap(self, "InfobarTimeshiftActions", 
819                         {
820                                 "timeshiftStart": (self.startTimeshift, "start timeshift"),  # the "yellow key"
821                                 "timeshiftStop": (self.stopTimeshift, "stop timeshift")      # currently undefined :), probably 'TV'
822                         }, prio=1)
823                 self["TimeshiftActivateActions"] = ActionMap(["InfobarTimeshiftActivateActions"],
824                         {
825                                 "timeshiftActivateEnd": self.activateTimeshiftEnd, # something like "pause key"
826                                 "timeshiftActivateEndAndPause": self.activateTimeshiftEndAndPause  # something like "backward key"
827                         }, prio=-1) # priority over record
828
829                 self.timeshift_enabled = 0
830                 self.timeshift_state = 0
831                 self.ts_pause_timer = eTimer()
832                 self.ts_pause_timer.timeout.get().append(self.pauseService)
833
834                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
835                         {
836                                 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged
837                         })
838         
839         def getTimeshift(self):
840                 service = self.session.nav.getCurrentService()
841                 return service.timeshift()
842
843         def startTimeshift(self):
844                 print "enable timeshift"
845                 ts = self.getTimeshift()
846                 if ts is None:
847                         self.session.open(MessageBox, _("Timeshift not possible!"), MessageBox.TYPE_ERROR)
848                         print "no ts interface"
849                         return
850                 
851                 if self.timeshift_enabled:
852                         print "hu, timeshift already enabled?"
853                 else:
854                         if not ts.startTimeshift():
855                                 self.timeshift_enabled = 1
856                                 
857                                 # PAUSE.
858                                 self.setSeekState(self.SEEK_STATE_PAUSE)
859                                 
860                                 # enable the "TimeshiftEnableActions", which will override
861                                 # the startTimeshift actions
862                                 self.__seekableStatusChanged()
863                         else:
864                                 print "timeshift failed"
865
866         def stopTimeshift(self):
867                 if not self.timeshift_enabled:
868                         return
869                 print "disable timeshift"
870                 ts = self.getTimeshift()
871                 if ts is None:
872                         return
873                 self.session.openWithCallback(self.stopTimeshiftConfirmed, MessageBox, _("Stop Timeshift?"), MessageBox.TYPE_YESNO)
874
875         def stopTimeshiftConfirmed(self, confirmed):
876                 if not confirmed:
877                         return
878
879                 ts = self.getTimeshift()
880                 if ts is None:
881                         return
882
883                 ts.stopTimeshift()
884                 self.timeshift_enabled = 0
885
886                 # disable actions
887                 self.__seekableStatusChanged()
888         
889         # activates timeshift, and seeks to (almost) the end
890         def activateTimeshiftEnd(self):
891                 ts = self.getTimeshift()
892                 
893                 if ts is None:
894                         return
895                 
896                 if ts.isTimeshiftActive():
897                         print "!! activate timeshift called - but shouldn't this be a normal pause?"
898                         self.pauseService()
899                 else:
900                         self.setSeekState(self.SEEK_STATE_PLAY)
901                         ts.activateTimeshift()
902                         self.seekRelative(0)
903         
904         # same as activateTimeshiftEnd, but pauses afterwards.
905         def activateTimeshiftEndAndPause(self):
906                 state = self.seekstate
907                 self.activateTimeshiftEnd()
908                 
909                 # well, this is "andPause", but it could be pressed from pause,
910                 # when pausing on the (fake-)"live" picture, so an un-pause
911                 # is perfectly ok.
912                 
913                 print "now, pauseService"
914                 if state == self.SEEK_STATE_PLAY:
915                         print "is PLAYING, start pause timer"
916                         self.ts_pause_timer.start(200, 1)
917                 else:
918                         print "unpause"
919                         self.unPauseService()
920         
921         def __seekableStatusChanged(self):
922                 enabled = False
923                 
924                 print "self.isSeekable", self.isSeekable()
925                 print "self.timeshift_enabled", self.timeshift_enabled
926                 
927                 # when this service is not seekable, but timeshift
928                 # is enabled, this means we can activate
929                 # the timeshift
930                 if not self.isSeekable() and self.timeshift_enabled:
931                         enabled = True
932
933                 print "timeshift activate:", enabled
934                 self["TimeshiftActivateActions"].setEnabled(enabled)
935
936 from RecordTimer import parseEvent
937
938 class InfoBarInstantRecord:
939         """Instant Record - handles the instantRecord action in order to 
940         start/stop instant records"""
941         def __init__(self):
942                 self["InstantRecordActions"] = HelpableActionMap(self, "InfobarInstantRecord",
943                         {
944                                 "instantRecord": (self.instantRecord, "Instant Record..."),
945                         })
946                 self.recording = None
947                 self["BlinkingPoint"] = BlinkingPixmapConditional()
948                 self["BlinkingPoint"].hide()
949                 self["BlinkingPoint"].setConnect(self.session.nav.RecordTimer.isRecording)
950
951         def stopCurrentRecording(self): 
952                 self.session.nav.RecordTimer.removeEntry(self.recording)
953                 self.recording = None
954
955         def startInstantRecording(self):
956                 serviceref = self.session.nav.getCurrentlyPlayingServiceReference()
957                 
958                 # try to get event info
959                 event = None
960                 try:
961                         service = self.session.nav.getCurrentService()
962                         info = service.info()
963                         ev = info.getEvent(0)
964                         event = ev
965                 except:
966                         pass
967                 
968                 if event is not None:
969                         data = parseEvent(event)
970                         begin = time.time()
971                         end = begin + 3600 * 10
972                         
973                         data = (begin, end, data[2], data[3], data[4])
974                 else:
975                         data = (time.time(), time.time() + 3600 * 10, "instant record", "", None)
976                 
977                 # fix me, description. 
978                 self.recording = self.session.nav.recordWithTimer(serviceref, *data)
979                 self.recording.dontSave = True
980                 
981                 #self["BlinkingPoint"].setConnect(lambda: self.recording.isRunning())
982                 
983         def isInstantRecordRunning(self):
984                 if self.recording != None:
985                         if self.recording.isRunning():
986                                 return True
987                 return False
988
989         def recordQuestionCallback(self, answer):
990                 if answer == False:
991                         return
992                 
993                 if self.isInstantRecordRunning():
994                         self.stopCurrentRecording()
995                 else:
996                         self.startInstantRecording()
997
998         def instantRecord(self):
999                 try:
1000                         stat = os.stat(resolveFilename(SCOPE_HDD))
1001                 except:
1002                         self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
1003                         return
1004         
1005                 if self.isInstantRecordRunning():
1006                         self.session.openWithCallback(self.recordQuestionCallback, MessageBox, _("Do you want to stop the current\n(instant) recording?"))
1007                 else:
1008                         self.session.openWithCallback(self.recordQuestionCallback, MessageBox, _("Start recording?"))
1009
1010 from Screens.AudioSelection import AudioSelection
1011
1012 class InfoBarAudioSelection:
1013         def __init__(self):
1014                 self["AudioSelectionAction"] = HelpableActionMap(self, "InfobarAudioSelectionActions", 
1015                         {
1016                                 "audioSelection": (self.audioSelection, "Audio Options..."),
1017                         })
1018
1019         def audioSelection(self):
1020                 service = self.session.nav.getCurrentService()
1021                 audio = service.audioTracks()
1022                 n = audio.getNumberOfTracks()
1023                 if n > 0:
1024                         self.session.open(AudioSelection, audio)
1025
1026 from Screens.SubserviceSelection import SubserviceSelection
1027
1028 class InfoBarSubserviceSelection:
1029         def __init__(self):
1030                 self["SubserviceSelectionAction"] = HelpableActionMap(self, "InfobarSubserviceSelectionActions",
1031                         {
1032                                 "subserviceSelection": (self.subserviceSelection, "Subservice list..."),
1033                         })
1034
1035         def subserviceSelection(self):
1036                 service = self.session.nav.getCurrentService()
1037                 subservices = service.subServices()
1038                 n = subservices.getNumberOfSubservices()
1039                 if n > 0:
1040                         self.session.openWithCallback(self.subserviceSelected, SubserviceSelection, subservices)
1041
1042         def subserviceSelected(self, service):
1043                 if not service is None:
1044                         self.session.nav.playService(service)
1045
1046 class InfoBarAdditionalInfo:
1047         def __init__(self):
1048                 self["DolbyActive"] = Pixmap()
1049                 self["CryptActive"] = Pixmap()
1050                 self["FormatActive"] = Pixmap()
1051                 
1052                 self["ButtonRed"] = PixmapConditional(withTimer = False)
1053                 self["ButtonRed"].setConnect(lambda: harddiskmanager.HDDCount() > 0)
1054                 self.onLayoutFinish.append(self["ButtonRed"].update)
1055                 self["ButtonRedText"] = LabelConditional(text = _("Record"), withTimer = False)
1056                 self["ButtonRedText"].setConnect(lambda: harddiskmanager.HDDCount() > 0)
1057                 self.onLayoutFinish.append(self["ButtonRedText"].update)
1058
1059                 self["ButtonGreen"] = Pixmap()
1060                 self["ButtonGreenText"] = Label(_("Subservices"))
1061
1062                 self["ButtonYellow"] = PixmapConditional(withTimer = False)
1063                 self["ButtonYellow"].setConnect(lambda: harddiskmanager.HDDCount() > 0)
1064                 self["ButtonYellowText"] = LabelConditional(text = _("Timeshifting"), withTimer = False)
1065                 self["ButtonYellowText"].setConnect(lambda: harddiskmanager.HDDCount() > 0)
1066                 self.onLayoutFinish.append(self["ButtonYellow"].update)
1067                 self.onLayoutFinish.append(self["ButtonYellowText"].update)
1068
1069                 self["ButtonBlue"] = PixmapConditional(withTimer = False)
1070                 self["ButtonBlue"].setConnect(lambda: False)
1071                 self["ButtonBlueText"] = LabelConditional(text = _("Extensions"), withTimer = False)
1072                 self["ButtonBlueText"].setConnect(lambda: False)
1073                 self.onLayoutFinish.append(self["ButtonBlue"].update)
1074                 self.onLayoutFinish.append(self["ButtonBlueText"].update)
1075
1076                 self.session.nav.event.append(self.gotServiceEvent) # we like to get service events
1077
1078         def hideSubServiceIndication(self):
1079                 self["ButtonGreen"].hide()
1080                 self["ButtonGreenText"].hide()
1081
1082         def showSubServiceIndication(self):
1083                 self["ButtonGreen"].show()
1084                 self["ButtonGreenText"].show()
1085
1086         def checkFormat(self, service):
1087                 info = service.info()
1088                 if info is not None:
1089                         aspect = info.getInfo(iServiceInformation.sAspect)
1090                         if aspect in [ 3, 4, 7, 8, 0xB, 0xC, 0xF, 0x10 ]:
1091                                 self["FormatActive"].show()
1092                         else:
1093                                 self["FormatActive"].hide()
1094
1095         def checkSubservices(self, service):
1096                 if service.subServices().getNumberOfSubservices() > 0:
1097                         self.showSubServiceIndication()
1098                 else:
1099                         self.hideSubServiceIndication()
1100
1101         def checkDolby(self, service):
1102                 # FIXME
1103                 dolby = False
1104                 audio = service.audioTracks()
1105                 if audio is not None:
1106                         n = audio.getNumberOfTracks()
1107                         for x in range(n):
1108                                 i = audio.getTrackInfo(x)
1109                                 description = i.getDescription();
1110                                 if description.find("AC3") != -1 or description.find("DTS") != -1:
1111                                         dolby = True
1112                                         break
1113                 if dolby:
1114                         self["DolbyActive"].show()
1115                 else:
1116                         self["DolbyActive"].hide()
1117
1118         def checkCrypted(self, service):
1119                 info = service.info()
1120                 if info is not None:
1121                         if info.getInfo(iServiceInformation.sIsCrypted) > 0:
1122                                 self["CryptActive"].show()
1123                         else:
1124                                 self["CryptActive"].hide()
1125
1126         def gotServiceEvent(self, ev):
1127                 service = self.session.nav.getCurrentService()
1128                 if ev == iPlayableService.evUpdatedEventInfo:
1129                         self.checkSubservices(service)
1130                         self.checkFormat(service)
1131                 elif ev == iPlayableService.evUpdatedInfo:
1132                         self.checkCrypted(service)
1133                         self.checkDolby(service)
1134                 elif ev == iPlayableService.evEnd:
1135                         self.hideSubServiceIndication()
1136                         self["CryptActive"].hide()
1137                         self["DolbyActive"].hide()
1138                         self["FormatActive"].hide()
1139
1140 class InfoBarNotifications:
1141         def __init__(self):
1142                 self.onExecBegin.append(self.checkNotifications)
1143                 Notifications.notificationAdded.append(self.checkNotificationsIfExecing)
1144         
1145         def checkNotificationsIfExecing(self):
1146                 if self.execing:
1147                         self.checkNotifications()
1148
1149         def checkNotifications(self):
1150                 if len(Notifications.notifications):
1151                         n = Notifications.notifications[0]
1152                         Notifications.notifications = Notifications.notifications[1:]
1153                         print "open",n
1154                         cb = n[0]
1155                         if cb is not None:
1156                                 self.session.openWithCallback(cb, *n[1:])
1157                         else:
1158                                 self.session.open(*n[1:])
1159
1160 class InfoBarServiceNotifications:
1161         def __init__(self):
1162                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1163                         {
1164                                 iPlayableService.evEnd: self.serviceHasEnded
1165                         })
1166
1167         def serviceHasEnded(self):
1168                 print "service end!"
1169
1170                 try:
1171                         self.setSeekState(self.SEEK_STATE_PLAY)
1172                 except:
1173                         pass
1174
1175 class InfoBarCueSheetSupport:
1176         CUT_TYPE_IN = 0
1177         CUT_TYPE_OUT = 1
1178         CUT_TYPE_MARK = 2
1179         
1180         def __init__(self):
1181                 self["CueSheetActions"] = HelpableActionMap(self, "InfobarCueSheetActions", 
1182                         {
1183                                 "jumpPreviousMark": (self.jumpPreviousMark, "jump to next marked position"),
1184                                 "jumpNextMark": (self.jumpNextMark, "jump to previous marked position"),
1185                                 "toggleMark": (self.toggleMark, "toggle a cut mark at the current position")
1186                         }, prio=1) 
1187                 
1188                 self.cut_list = [ ]
1189                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1190                         {
1191                                 iPlayableService.evStart: self.__serviceStarted,
1192                         })
1193
1194         def __serviceStarted(self):
1195                 print "new service started! trying to download cuts!"
1196                 self.downloadCuesheet()
1197
1198         def __getSeekable(self):
1199                 service = self.session.nav.getCurrentService()
1200                 if service is None:
1201                         return None
1202                 return service.seek()
1203
1204         def __getCurrentPosition(self):
1205                 seek = self.__getSeekable()
1206                 if seek is None:
1207                         return None
1208                 r = seek.getPlayPosition()
1209                 if r[0]:
1210                         return None
1211                 return long(r[1])
1212
1213         def jumpPreviousNextMark(self, cmp, alternative=None):
1214                 current_pos = self.__getCurrentPosition()
1215                 if current_pos is None:
1216                         return
1217                 mark = self.getNearestCutPoint(current_pos, cmp=cmp)
1218                 if mark is not None:
1219                         pts = mark[0]
1220                 elif alternative is not None:
1221                         pts = alternative
1222                 else:
1223                         return
1224
1225                 seekable = self.__getSeekable()
1226                 if seekable is not None:
1227                         seekable.seekTo(pts)
1228
1229         def jumpPreviousMark(self):
1230                 print "jumpPreviousMark"
1231                 # we add 2 seconds, so if the play position is <2s after
1232                 # the mark, the mark before will be used
1233                 self.jumpPreviousNextMark(lambda x: -x-5*90000, alternative=0)
1234
1235         def jumpNextMark(self):
1236                 print "jumpNextMark"
1237                 self.jumpPreviousNextMark(lambda x: x)
1238
1239         def getNearestCutPoint(self, pts, cmp=abs):
1240                 # can be optimized
1241                 nearest = None
1242                 for cp in self.cut_list:
1243                         diff = cmp(cp[0] - pts)
1244                         if diff >= 0 and (nearest is None or cmp(nearest[0] - pts) > diff):
1245                                 nearest = cp
1246                 return nearest
1247
1248         def toggleMark(self):
1249                 print "toggleMark"
1250                 current_pos = self.__getCurrentPosition()
1251                 if current_pos is None:
1252                         print "not seekable"
1253                         return
1254                 
1255                 print "current position: ", current_pos
1256
1257                 nearest_cutpoint = self.getNearestCutPoint(current_pos)
1258                 print "nearest_cutpoint: ", nearest_cutpoint
1259                 
1260                 if nearest_cutpoint is not None and abs(nearest_cutpoint[0] - current_pos) < 5*90000:
1261                         self.removeMark(self, *nearest_cutpoint)
1262                 else:
1263                         self.addMark(self, current_pos, self.CUT_TYPE_MARK)
1264
1265         def addMark(self, where, type):
1266                 bisect.insort(self.cut_list, (current_pos, self.CUT_TYPE_MARK))
1267                 self.uploadCuesheet()
1268
1269         def removeMark(self, where, type):
1270                 self.cut_list.remove(nearest_cutpoint)
1271                 self.uploadCuesheet()
1272
1273         def __getCuesheet(self):
1274                 service = self.session.nav.getCurrentService()
1275                 if service is None:
1276                         return None
1277                 return service.cueSheet()
1278
1279         def uploadCuesheet(self):
1280                 cue = self.__getCuesheet()
1281
1282                 if cue is None:
1283                         print "upload failed, no cuesheet interface"
1284                         return
1285                 cue.setCutList(self.cut_list)
1286
1287         def downloadCuesheet(self):
1288                 cue = self.__getCuesheet()
1289
1290                 if cue is None:
1291                         print "upload failed, no cuesheet interface"
1292                         return
1293                 self.cut_list = cue.getCutList()
1294
1295                 print "cuts:", self.cut_list