minimum backward seek is 16X
[openblackhole/openblackhole-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, _("next channel")),
280                                 "zapDown": (self.zapDown, _("previous 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                                 "seekFwdUp": (self.seekFwdUp, "skip forward"),
521                                 "seekBack": (self.seekBack, "skip backward"),
522                                 "seekBackUp": (self.seekBackUp, "skip backward"),
523                         }, prio=-1)
524                         # give them a little more priority to win over color buttons
525
526                 self.seekstate = self.SEEK_STATE_PLAY
527                 self.onClose.append(self.delTimer)
528                 
529                 self.fwdtimer = False
530                 self.fwdKeyTimer = eTimer()
531                 self.fwdKeyTimer.timeout.get().append(self.fwdTimerFire)
532
533                 self.rwdtimer = False
534                 self.rwdKeyTimer = eTimer()
535                 self.rwdKeyTimer.timeout.get().append(self.rwdTimerFire)
536                 
537                 self.onPlayStateChanged = [ ]
538                 
539                 self.lockedBecauseOfSkipping = False
540         
541         def up(self):
542                 pass
543         
544         def down(self):
545                 pass
546         
547         def delTimer(self):
548                 del self.fwdKeyTimer
549                 del self.rwdKeyTimer
550         
551         def getSeek(self):
552                 service = self.session.nav.getCurrentService()
553                 if service is None:
554                         return False
555
556                 seek = service.seek()
557
558                 if seek is None or not seek.isCurrentlySeekable():
559                         return None
560                 
561                 return seek
562         
563         def isSeekable(self):
564                 if self.getSeek() is None:
565                         return False
566                 return True
567
568         def __seekableStatusChanged(self):
569                 print "seekable status changed!"
570                 if not self.isSeekable():
571                         self["SeekActions"].setEnabled(False)
572                         print "not seekable, return to play"
573                         self.setSeekState(self.SEEK_STATE_PLAY)
574                 else:
575                         self["SeekActions"].setEnabled(True)
576                         print "seekable"
577
578         def __serviceStarted(self):
579                 self.seekstate = self.SEEK_STATE_PLAY
580
581         def setSeekState(self, state):
582                 service = self.session.nav.getCurrentService()
583                 
584                 if service is None:
585                         return False
586                 
587                 if not self.isSeekable():
588                         if state not in [self.SEEK_STATE_PLAY, self.SEEK_STATE_PAUSE]:
589                                 state = self.SEEK_STATE_PLAY
590                 
591                 pauseable = service.pause()
592
593                 if pauseable is None:
594                         print "not pauseable."
595                         state = self.SEEK_STATE_PLAY
596                 
597                 oldstate = self.seekstate
598                 self.seekstate = state
599                 
600                 for i in range(3):
601                         if oldstate[i] != self.seekstate[i]:
602                                 (self.session.nav.pause, pauseable.setFastForward, pauseable.setSlowMotion)[i](self.seekstate[i])
603
604                 for c in self.onPlayStateChanged:
605                         c(self.seekstate)
606                 
607                 self.checkSkipShowHideLock()
608
609                 return True
610                 
611         def pauseService(self):
612                 if self.seekstate == self.SEEK_STATE_PAUSE:
613                         print "pause, but in fact unpause"
614                         self.unPauseService()
615                 else:
616                         if self.seekstate == self.SEEK_STATE_PLAY:
617                                 print "yes, playing."
618                         else:
619                                 print "no", self.seekstate
620                         print "pause"
621                         self.setSeekState(self.SEEK_STATE_PAUSE);
622                 
623         def unPauseService(self):
624                 print "unpause"
625                 self.setSeekState(self.SEEK_STATE_PLAY);
626         
627         def doSeek(self, seektime):
628                 print "doseek", seektime
629                 service = self.session.nav.getCurrentService()
630                 if service is None:
631                         return
632                 
633                 seekable = self.getSeek()
634                 if seekable is None:
635                         return
636                 
637                 seekable.seekTo(90 * seektime)
638
639         def seekFwd(self):
640                 print "start fwd timer"
641                 self.fwdtimer = True
642                 self.fwdKeyTimer.start(1000)
643
644         def seekBack(self):
645                 print "start rewind timer"
646                 self.rwdtimer = True
647                 self.rwdKeyTimer.start(1000)
648
649         def seekFwdUp(self):
650                 print "seekFwdUp"
651                 if self.fwdtimer:
652                         self.fwdKeyTimer.stop()
653                         self.fwdtimer = False
654                         lookup = {
655                                         self.SEEK_STATE_PLAY: self.SEEK_STATE_FF_2X,
656                                         self.SEEK_STATE_PAUSE: self.SEEK_STATE_SM_EIGHTH,
657                                         self.SEEK_STATE_FF_2X: self.SEEK_STATE_FF_4X,
658                                         self.SEEK_STATE_FF_4X: self.SEEK_STATE_FF_8X,
659                                         self.SEEK_STATE_FF_8X: self.SEEK_STATE_FF_32X,
660                                         self.SEEK_STATE_FF_32X: self.SEEK_STATE_FF_64X,
661                                         self.SEEK_STATE_FF_64X: self.SEEK_STATE_FF_128X,
662                                         self.SEEK_STATE_FF_128X: self.SEEK_STATE_FF_128X,
663                                         self.SEEK_STATE_BACK_16X: self.SEEK_STATE_PLAY,
664                                         self.SEEK_STATE_BACK_32X: self.SEEK_STATE_BACK_16X,
665                                         self.SEEK_STATE_BACK_64X: self.SEEK_STATE_BACK_32X,
666                                         self.SEEK_STATE_BACK_128X: self.SEEK_STATE_BACK_64X,
667                                         self.SEEK_STATE_SM_HALF: self.SEEK_STATE_SM_HALF,
668                                         self.SEEK_STATE_SM_QUARTER: self.SEEK_STATE_SM_HALF,
669                                         self.SEEK_STATE_SM_EIGHTH: self.SEEK_STATE_SM_QUARTER
670                                 }
671                         self.setSeekState(lookup[self.seekstate]);
672         
673         def seekBackUp(self):
674                 print "seekBackUp"
675                 if self.rwdtimer:
676                         self.rwdKeyTimer.stop()
677                         self.rwdtimer = False
678                 
679                         lookup = {
680                                         self.SEEK_STATE_PLAY: self.SEEK_STATE_BACK_16X,
681                                         self.SEEK_STATE_PAUSE: self.SEEK_STATE_PAUSE,
682                                         self.SEEK_STATE_FF_2X: self.SEEK_STATE_PLAY,
683                                         self.SEEK_STATE_FF_4X: self.SEEK_STATE_FF_2X,
684                                         self.SEEK_STATE_FF_8X: self.SEEK_STATE_FF_4X,
685                                         self.SEEK_STATE_FF_32X: self.SEEK_STATE_FF_8X,
686                                         self.SEEK_STATE_FF_64X: self.SEEK_STATE_FF_32X,
687                                         self.SEEK_STATE_FF_128X: self.SEEK_STATE_FF_64X,
688                                         self.SEEK_STATE_BACK_16X: self.SEEK_STATE_BACK_32X,
689                                         self.SEEK_STATE_BACK_32X: self.SEEK_STATE_BACK_64X,
690                                         self.SEEK_STATE_BACK_64X: self.SEEK_STATE_BACK_128X,
691                                         self.SEEK_STATE_BACK_128X: self.SEEK_STATE_BACK_128X,
692                                         self.SEEK_STATE_SM_HALF: self.SEEK_STATE_SM_QUARTER,
693                                         self.SEEK_STATE_SM_QUARTER: self.SEEK_STATE_SM_EIGHTH,
694                                         self.SEEK_STATE_SM_EIGHTH: self.SEEK_STATE_PAUSE
695                                 }
696                         self.setSeekState(lookup[self.seekstate]);
697                 
698         def fwdTimerFire(self):
699                 print "Display seek fwd"
700                 self.fwdKeyTimer.stop()
701                 self.fwdtimer = False
702                 self.session.openWithCallback(self.fwdSeekTo, MinuteInput)
703                 
704         def fwdSeekTo(self, minutes):
705                 print "Seek", minutes, "minutes forward"
706                 if minutes != 0:
707                         seekable = self.getSeek()
708                         if seekable is not None:
709                                 seekable.seekRelative(1, minutes * 60 * 90000)
710         
711         def rwdTimerFire(self):
712                 print "rwdTimerFire"
713                 self.rwdKeyTimer.stop()
714                 self.rwdtimer = False
715                 self.session.openWithCallback(self.rwdSeekTo, MinuteInput)
716         
717         def rwdSeekTo(self, minutes):
718                 print "rwdSeekTo"
719                 self.fwdSeekTo(0 - minutes)
720         
721         def checkSkipShowHideLock(self):
722                 wantlock = self.seekstate != self.SEEK_STATE_PLAY
723                 
724                 if self.lockedBecauseOfSkipping and not wantlock:
725                         self.unlockShow()
726                         self.lockedBecauseOfSkipping = False
727                 
728                 if wantlock and not self.lockedBecauseOfSkipping:
729                         self.lockShow()
730                         self.lockedBecauseOfSkipping = True
731
732         def __evEOF(self):
733                 if self.seekstate != self.SEEK_STATE_PLAY:
734                         self.setSeekState(self.SEEK_STATE_PAUSE)
735                         # HACK
736                         self.getSeek().seekRelative(1, -90000)
737                         self.setSeekState(self.SEEK_STATE_PLAY)
738                 else:
739                         self.setSeekState(self.SEEK_STATE_PAUSE)
740         
741         def __evSOF(self):
742                 self.setSeekState(self.SEEK_STATE_PLAY)
743                 self.doSeek(0)
744
745         def seekRelative(self, diff):
746                 seekable = self.getSeek()
747                 if seekable is not None:
748                         seekable.seekRelative(0, diff)
749
750 from Screens.PVRState import PVRState
751
752 class InfoBarPVRState:
753         def __init__(self):
754                 self.onPlayStateChanged.append(self.__playStateChanged)
755                 self.pvrStateDialog = self.session.instantiateDialog(PVRState)
756                 self.onShow.append(self.__mayShow)
757                 self.onHide.append(self.pvrStateDialog.hide)
758         
759         def __mayShow(self):
760                 if self.seekstate != self.SEEK_STATE_PLAY:
761                         self.pvrStateDialog.show()
762
763         def __playStateChanged(self, state):
764                 playstateString = state[3]
765                 self.pvrStateDialog["state"].setText(playstateString)
766                 self.__mayShow()
767
768 class InfoBarShowMovies:
769
770         # i don't really like this class. 
771         # it calls a not further specified "movie list" on up/down/movieList,
772         # so this is not more than an action map
773         def __init__(self):
774                 self["MovieListActions"] = HelpableActionMap(self, "InfobarMovieListActions", 
775                         {
776                                 "movieList": (self.showMovies, "movie list"),
777                                 "up": (self.showMovies, "movie list"),
778                                 "down": (self.showMovies, "movie list")
779                         })
780
781 # InfoBarTimeshift requires InfoBarSeek, instantiated BEFORE!
782
783 # Hrmf.
784 #
785 # Timeshift works the following way:
786 #                                         demux0   demux1                    "TimeshiftActions" "TimeshiftActivateActions" "SeekActions"
787 # - normal playback                       TUNER    unused      PLAY               enable                disable              disable
788 # - user presses "yellow" button.         TUNER    record      PAUSE              enable                disable              enable
789 # - user presess pause again              FILE     record      PLAY               enable                disable              enable
790 # - user fast forwards                    FILE     record      FF                 enable                disable              enable
791 # - end of timeshift buffer reached       TUNER    record      PLAY               enable                enable               disable
792 # - user backwards                        FILE     record      BACK  # !!         enable                disable              enable
793 #
794
795 # in other words:
796 # - when a service is playing, pressing the "timeshiftStart" button ("yellow") enables recording ("enables timeshift"),
797 # freezes the picture (to indicate timeshift), sets timeshiftMode ("activates timeshift")
798 # now, the service becomes seekable, so "SeekActions" are enabled, "TimeshiftEnableActions" are disabled.
799 # - the user can now PVR around
800 # - if it hits the end, the service goes into live mode ("deactivates timeshift", it's of course still "enabled")
801 # the service looses it's "seekable" state. It can still be paused, but just to activate timeshift right
802 # after!
803 # the seek actions will be disabled, but the timeshiftActivateActions will be enabled
804 # - if the user rewinds, or press pause, timeshift will be activated again
805
806 # note that a timeshift can be enabled ("recording") and
807 # activated (currently time-shifting).
808
809 class InfoBarTimeshift:
810         def __init__(self):
811                 self["TimeshiftActions"] = HelpableActionMap(self, "InfobarTimeshiftActions", 
812                         {
813                                 "timeshiftStart": (self.startTimeshift, "start timeshift"),  # the "yellow key"
814                                 "timeshiftStop": (self.stopTimeshift, "stop timeshift")      # currently undefined :), probably 'TV'
815                         }, prio=1)
816                 self["TimeshiftActivateActions"] = ActionMap(["InfobarTimeshiftActivateActions"],
817                         {
818                                 "timeshiftActivateEnd": self.activateTimeshiftEnd, # something like "pause key"
819                                 "timeshiftActivateEndAndPause": self.activateTimeshiftEndAndPause  # something like "backward key"
820                         }, prio=-1) # priority over record
821
822                 self.timeshift_enabled = 0
823                 self.timeshift_state = 0
824                 self.ts_pause_timer = eTimer()
825                 self.ts_pause_timer.timeout.get().append(self.pauseService)
826
827                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
828                         {
829                                 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged
830                         })
831         
832         def getTimeshift(self):
833                 service = self.session.nav.getCurrentService()
834                 return service.timeshift()
835
836         def startTimeshift(self):
837                 print "enable timeshift"
838                 ts = self.getTimeshift()
839                 if ts is None:
840                         self.session.open(MessageBox, _("Timeshift not possible!"), MessageBox.TYPE_ERROR)
841                         print "no ts interface"
842                         return
843                 
844                 if self.timeshift_enabled:
845                         print "hu, timeshift already enabled?"
846                 else:
847                         if not ts.startTimeshift():
848                                 self.timeshift_enabled = 1
849                                 
850                                 # PAUSE.
851                                 self.setSeekState(self.SEEK_STATE_PAUSE)
852                                 
853                                 # enable the "TimeshiftEnableActions", which will override
854                                 # the startTimeshift actions
855                                 self.__seekableStatusChanged()
856                         else:
857                                 print "timeshift failed"
858
859         def stopTimeshift(self):
860                 if not self.timeshift_enabled:
861                         return
862                 print "disable timeshift"
863                 ts = self.getTimeshift()
864                 if ts is None:
865                         return
866                 self.session.openWithCallback(self.stopTimeshiftConfirmed, MessageBox, _("Stop Timeshift?"), MessageBox.TYPE_YESNO)
867
868         def stopTimeshiftConfirmed(self, confirmed):
869                 if not confirmed:
870                         return
871
872                 ts = self.getTimeshift()
873                 if ts is None:
874                         return
875
876                 ts.stopTimeshift()
877                 self.timeshift_enabled = 0
878
879                 # disable actions
880                 self.__seekableStatusChanged()
881         
882         # activates timeshift, and seeks to (almost) the end
883         def activateTimeshiftEnd(self):
884                 ts = self.getTimeshift()
885                 
886                 if ts is None:
887                         return
888                 
889                 if ts.isTimeshiftActive():
890                         print "!! activate timeshift called - but shouldn't this be a normal pause?"
891                         self.pauseService()
892                 else:
893                         self.setSeekState(self.SEEK_STATE_PLAY)
894                         ts.activateTimeshift()
895                         self.seekRelative(0)
896         
897         # same as activateTimeshiftEnd, but pauses afterwards.
898         def activateTimeshiftEndAndPause(self):
899                 state = self.seekstate
900                 self.activateTimeshiftEnd()
901                 
902                 # well, this is "andPause", but it could be pressed from pause,
903                 # when pausing on the (fake-)"live" picture, so an un-pause
904                 # is perfectly ok.
905                 
906                 print "now, pauseService"
907                 if state == self.SEEK_STATE_PLAY:
908                         print "is PLAYING, start pause timer"
909                         self.ts_pause_timer.start(200, 1)
910                 else:
911                         print "unpause"
912                         self.unPauseService()
913         
914         def __seekableStatusChanged(self):
915                 enabled = False
916                 
917                 print "self.isSeekable", self.isSeekable()
918                 print "self.timeshift_enabled", self.timeshift_enabled
919                 
920                 # when this service is not seekable, but timeshift
921                 # is enabled, this means we can activate
922                 # the timeshift
923                 if not self.isSeekable() and self.timeshift_enabled:
924                         enabled = True
925
926                 print "timeshift activate:", enabled
927                 self["TimeshiftActivateActions"].setEnabled(enabled)
928
929 from RecordTimer import parseEvent
930
931 class InfoBarInstantRecord:
932         """Instant Record - handles the instantRecord action in order to 
933         start/stop instant records"""
934         def __init__(self):
935                 self["InstantRecordActions"] = HelpableActionMap(self, "InfobarInstantRecord",
936                         {
937                                 "instantRecord": (self.instantRecord, "Instant Record..."),
938                         })
939                 self.recording = None
940                 self["BlinkingPoint"] = BlinkingPixmapConditional()
941                 self.onLayoutFinish.append(self["BlinkingPoint"].hideWidget)
942                 self["BlinkingPoint"].setConnect(self.session.nav.RecordTimer.isRecording)
943
944         def stopCurrentRecording(self): 
945                 self.session.nav.RecordTimer.removeEntry(self.recording)
946                 self.recording = None
947
948         def startInstantRecording(self):
949                 serviceref = self.session.nav.getCurrentlyPlayingServiceReference()
950                 
951                 # try to get event info
952                 event = None
953                 try:
954                         service = self.session.nav.getCurrentService()
955                         info = service.info()
956                         ev = info.getEvent(0)
957                         event = ev
958                 except:
959                         pass
960                 
961                 if event is not None:
962                         data = parseEvent(event)
963                         begin = time.time()
964                         end = begin + 3600 * 10
965                         
966                         data = (begin, end, data[2], data[3], data[4])
967                 else:
968                         data = (time.time(), time.time() + 3600 * 10, "instant record", "", None)
969                 
970                 # fix me, description. 
971                 self.recording = self.session.nav.recordWithTimer(serviceref, *data)
972                 self.recording.dontSave = True
973                 
974                 #self["BlinkingPoint"].setConnect(lambda: self.recording.isRunning())
975                 
976         def isInstantRecordRunning(self):
977                 if self.recording != None:
978                         if self.recording.isRunning():
979                                 return True
980                 return False
981
982         def recordQuestionCallback(self, answer):
983                 if answer == False:
984                         return
985                 
986                 if self.isInstantRecordRunning():
987                         self.stopCurrentRecording()
988                 else:
989                         self.startInstantRecording()
990
991         def instantRecord(self):
992                 try:
993                         stat = os.stat(resolveFilename(SCOPE_HDD))
994                 except:
995                         self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
996                         return
997         
998                 if self.isInstantRecordRunning():
999                         self.session.openWithCallback(self.recordQuestionCallback, MessageBox, _("Do you want to stop the current\n(instant) recording?"))
1000                 else:
1001                         self.session.openWithCallback(self.recordQuestionCallback, MessageBox, _("Start recording?"))
1002
1003 from Screens.AudioSelection import AudioSelection
1004
1005 class InfoBarAudioSelection:
1006         def __init__(self):
1007                 self["AudioSelectionAction"] = HelpableActionMap(self, "InfobarAudioSelectionActions", 
1008                         {
1009                                 "audioSelection": (self.audioSelection, "Audio Options..."),
1010                         })
1011
1012         def audioSelection(self):
1013                 service = self.session.nav.getCurrentService()
1014                 audio = service.audioTracks()
1015                 n = audio.getNumberOfTracks()
1016                 if n > 0:
1017                         self.session.open(AudioSelection, audio)
1018
1019 from Screens.SubserviceSelection import SubserviceSelection
1020
1021 class InfoBarSubserviceSelection:
1022         def __init__(self):
1023                 self["SubserviceSelectionAction"] = HelpableActionMap(self, "InfobarSubserviceSelectionActions",
1024                         {
1025                                 "subserviceSelection": (self.subserviceSelection, "Subservice list..."),
1026                         })
1027
1028         def subserviceSelection(self):
1029                 service = self.session.nav.getCurrentService()
1030                 subservices = service.subServices()
1031                 n = subservices.getNumberOfSubservices()
1032                 if n > 0:
1033                         self.session.openWithCallback(self.subserviceSelected, SubserviceSelection, subservices)
1034
1035         def subserviceSelected(self, service):
1036                 if not service is None:
1037                         self.session.nav.playService(service)
1038
1039 class InfoBarAdditionalInfo:
1040         def __init__(self):
1041                 self["DolbyActive"] = Pixmap()
1042                 self["CryptActive"] = Pixmap()
1043                 self["FormatActive"] = Pixmap()
1044                 
1045                 self["ButtonRed"] = PixmapConditional(withTimer = False)
1046                 self["ButtonRed"].setConnect(lambda: harddiskmanager.HDDCount() > 0)
1047                 self.onLayoutFinish.append(self["ButtonRed"].update)
1048                 self["ButtonRedText"] = LabelConditional(text = _("Record"), withTimer = False)
1049                 self["ButtonRedText"].setConnect(lambda: harddiskmanager.HDDCount() > 0)
1050                 self.onLayoutFinish.append(self["ButtonRedText"].update)
1051
1052                 self["ButtonGreen"] = Pixmap()
1053                 self["ButtonGreenText"] = Label(_("Subservices"))
1054
1055                 self["ButtonYellow"] = PixmapConditional(withTimer = False)
1056                 self["ButtonYellow"].setConnect(lambda: harddiskmanager.HDDCount() > 0)
1057                 self["ButtonYellowText"] = LabelConditional(text = _("Timeshifting"), withTimer = False)
1058                 self["ButtonYellowText"].setConnect(lambda: harddiskmanager.HDDCount() > 0)
1059                 self.onLayoutFinish.append(self["ButtonYellow"].update)
1060                 self.onLayoutFinish.append(self["ButtonYellowText"].update)
1061
1062                 self["ButtonBlue"] = PixmapConditional(withTimer = False)
1063                 self["ButtonBlue"].setConnect(lambda: False)
1064                 self["ButtonBlueText"] = LabelConditional(text = _("Extensions"), withTimer = False)
1065                 self["ButtonBlueText"].setConnect(lambda: False)
1066                 self.onLayoutFinish.append(self["ButtonBlue"].update)
1067                 self.onLayoutFinish.append(self["ButtonBlueText"].update)
1068
1069                 self.session.nav.event.append(self.gotServiceEvent) # we like to get service events
1070
1071         def hideSubServiceIndication(self):
1072                 self["ButtonGreen"].hideWidget()
1073                 self["ButtonGreenText"].hide()
1074
1075         def showSubServiceIndication(self):
1076                 self["ButtonGreen"].showWidget()
1077                 self["ButtonGreenText"].show()
1078
1079         def checkFormat(self, service):
1080                 info = service.info()
1081                 if info is not None:
1082                         aspect = info.getInfo(iServiceInformation.sAspect)
1083                         if aspect in [ 3, 4, 7, 8, 0xB, 0xC, 0xF, 0x10 ]:
1084                                 self["FormatActive"].showWidget()
1085                         else:
1086                                 self["FormatActive"].hideWidget()
1087
1088         def checkSubservices(self, service):
1089                 if service.subServices().getNumberOfSubservices() > 0:
1090                         self.showSubServiceIndication()
1091                 else:
1092                         self.hideSubServiceIndication()
1093
1094         def checkDolby(self, service):
1095                 # FIXME
1096                 dolby = False
1097                 audio = service.audioTracks()
1098                 if audio is not None:
1099                         n = audio.getNumberOfTracks()
1100                         for x in range(n):
1101                                 i = audio.getTrackInfo(x)
1102                                 description = i.getDescription();
1103                                 if description.find("AC3") != -1 or description.find("DTS") != -1:
1104                                         dolby = True
1105                                         break
1106                 if dolby:
1107                         self["DolbyActive"].showWidget()
1108                 else:
1109                         self["DolbyActive"].hideWidget()
1110
1111         def checkCrypted(self, service):
1112                 info = service.info()
1113                 if info is not None:
1114                         if info.getInfo(iServiceInformation.sIsCrypted) > 0:
1115                                 self["CryptActive"].showWidget()
1116                         else:
1117                                 self["CryptActive"].hideWidget()
1118
1119         def gotServiceEvent(self, ev):
1120                 service = self.session.nav.getCurrentService()
1121                 if ev == iPlayableService.evUpdatedEventInfo:
1122                         self.checkSubservices(service)
1123                         self.checkFormat(service)
1124                 elif ev == iPlayableService.evUpdatedInfo:
1125                         self.checkCrypted(service)
1126                         self.checkDolby(service)
1127                 elif ev == iPlayableService.evEnd:
1128                         self.hideSubServiceIndication()
1129                         self["CryptActive"].hideWidget()
1130                         self["DolbyActive"].hideWidget()
1131                         self["FormatActive"].hideWidget()
1132
1133 class InfoBarNotifications:
1134         def __init__(self):
1135                 self.onExecBegin.append(self.checkNotifications)
1136                 Notifications.notificationAdded.append(self.checkNotificationsIfExecing)
1137         
1138         def checkNotificationsIfExecing(self):
1139                 if self.execing:
1140                         self.checkNotifications()
1141
1142         def checkNotifications(self):
1143                 if len(Notifications.notifications):
1144                         n = Notifications.notifications[0]
1145                         Notifications.notifications = Notifications.notifications[1:]
1146                         print "open",n
1147                         cb = n[0]
1148                         if cb is not None:
1149                                 self.session.openWithCallback(cb, *n[1:])
1150                         else:
1151                                 self.session.open(*n[1:])
1152
1153 class InfoBarServiceNotifications:
1154         def __init__(self):
1155                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1156                         {
1157                                 iPlayableService.evEnd: self.serviceHasEnded
1158                         })
1159
1160         def serviceHasEnded(self):
1161                 print "service end!"
1162
1163                 try:
1164                         self.setSeekState(self.SEEK_STATE_PLAY)
1165                 except:
1166                         pass
1167
1168 class InfoBarCueSheetSupport:
1169         CUT_TYPE_IN = 0
1170         CUT_TYPE_OUT = 1
1171         CUT_TYPE_MARK = 2
1172         
1173         def __init__(self):
1174                 self["CueSheetActions"] = HelpableActionMap(self, "InfobarCueSheetActions", 
1175                         {
1176                                 "jumpPreviousMark": (self.jumpPreviousMark, "jump to next marked position"),
1177                                 "jumpNextMark": (self.jumpNextMark, "jump to previous marked position"),
1178                                 "toggleMark": (self.toggleMark, "toggle a cut mark at the current position")
1179                         }, prio=1) 
1180                 
1181                 self.cut_list = [ ]
1182                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1183                         {
1184                                 iPlayableService.evStart: self.__serviceStarted,
1185                         })
1186
1187         def __serviceStarted(self):
1188                 print "new service started! trying to download cuts!"
1189                 self.downloadCuesheet()
1190
1191         def __getSeekable(self):
1192                 service = self.session.nav.getCurrentService()
1193                 if service is None:
1194                         return None
1195                 return service.seek()
1196
1197         def __getCurrentPosition(self):
1198                 seek = self.__getSeekable()
1199                 if seek is None:
1200                         return None
1201                 r = seek.getPlayPosition()
1202                 if r[0]:
1203                         return None
1204                 return long(r[1])
1205
1206         def jumpPreviousNextMark(self, cmp, alternative=None):
1207                 current_pos = self.__getCurrentPosition()
1208                 if current_pos is None:
1209                         return
1210                 mark = self.getNearestCutPoint(current_pos, cmp=cmp)
1211                 if mark is not None:
1212                         pts = mark[0]
1213                 elif alternative is not None:
1214                         pts = alternative
1215                 else:
1216                         return
1217
1218                 seekable = self.__getSeekable()
1219                 if seekable is not None:
1220                         seekable.seekTo(pts)
1221
1222         def jumpPreviousMark(self):
1223                 print "jumpPreviousMark"
1224                 # we add 2 seconds, so if the play position is <2s after
1225                 # the mark, the mark before will be used
1226                 self.jumpPreviousNextMark(lambda x: -x-5*90000, alternative=0)
1227
1228         def jumpNextMark(self):
1229                 print "jumpNextMark"
1230                 self.jumpPreviousNextMark(lambda x: x)
1231
1232         def getNearestCutPoint(self, pts, cmp=abs):
1233                 # can be optimized
1234                 nearest = None
1235                 for cp in self.cut_list:
1236                         diff = cmp(cp[0] - pts)
1237                         if diff >= 0 and (nearest is None or cmp(nearest[0] - pts) > diff):
1238                                 nearest = cp
1239                 return nearest
1240
1241         def toggleMark(self):
1242                 print "toggleMark"
1243                 current_pos = self.__getCurrentPosition()
1244                 if current_pos is None:
1245                         print "not seekable"
1246                         return
1247                 
1248                 print "current position: ", current_pos
1249
1250                 nearest_cutpoint = self.getNearestCutPoint(current_pos)
1251                 print "nearest_cutpoint: ", nearest_cutpoint
1252                 
1253                 if nearest_cutpoint is not None and abs(nearest_cutpoint[0] - current_pos) < 5*90000:
1254                         self.cut_list.remove(nearest_cutpoint)
1255                 else:
1256                         bisect.insort(self.cut_list, (current_pos, self.CUT_TYPE_MARK))
1257                 
1258                 self.uploadCuesheet()
1259
1260         def __getCuesheet(self):
1261                 service = self.session.nav.getCurrentService()
1262                 if service is None:
1263                         return None
1264                 return service.cueSheet()
1265
1266         def uploadCuesheet(self):
1267                 cue = self.__getCuesheet()
1268
1269                 if cue is None:
1270                         print "upload failed, no cuesheet interface"
1271                         return
1272                 cue.setCutList(self.cut_list)
1273
1274         def downloadCuesheet(self):
1275                 cue = self.__getCuesheet()
1276
1277                 if cue is None:
1278                         print "upload failed, no cuesheet interface"
1279                         return
1280                 self.cut_list = cue.getCutList()
1281
1282                 print "cuts:", self.cut_list