instrumentation: add next-share/
[cs-p2p-next.git] / instrumentation / next-share / BaseLib / Video / Progress.py
1 # Written by Arno Bakker, Jan David Mol
2 # see LICENSE.txt for license information
3
4 import wx,time
5 import sys,os
6
7
8 from BaseLib.__init__ import LIBRARYNAME
9
10 class BufferInfo:
11     """ Arno: WARNING: the self.tricolore member is read by the MainThread and 
12         written by the network thread. As it is a fixed array with simple values, this
13         concurrency problem is ignored.
14     """
15     NOFILL = " "
16     SOMEFILL = ".-+="
17     ALLFILL = "#"
18
19     def __init__(self,numbuckets=100,full=False):
20         self.numbuckets = numbuckets
21         self.playable = False
22         self.movieselector = None
23         if full == True:
24             self.tricolore = [2] * self.numbuckets
25     
26     def set_numpieces(self,numpieces):
27         self.numpieces = numpieces
28         self.buckets = [0] * self.numbuckets
29         self.tricolore = [0] * self.numbuckets
30         #self.bucketsize = int(ceil(float(self.numpieces) / self.numbuckets))
31         self.bucketsize = float(self.numpieces) / float(self.numbuckets)
32         self.lastbucketsize = self.numpieces - int(float(self.numbuckets-1) * self.bucketsize)
33
34     def complete( self, piece ):
35         bucket = int(float(piece) / self.bucketsize)
36         
37         #print >>sys.stderr,"BUCKET",bucket,"piece",piece,"bucksize",self.bucketsize
38         # If there is a multi-file torrent that has been partially downloaded before we go
39         # to VOD, it can happen that pieces outside the range of the file selected are
40         # reported as complete here.
41         if bucket < 0 or bucket >= self.numbuckets:
42             return
43         
44         self.buckets[bucket] += 1
45
46         fill = self.buckets[bucket]
47         if bucket == self.numbuckets-1:
48             total = self.lastbucketsize
49         else:
50             total = int(self.bucketsize)
51             
52         if fill == 0:
53             colour = 0
54         elif fill >= total:
55             colour = 2
56         else:
57             colour = 1
58
59         self.tricolore[bucket] = colour
60
61     def str( self ):
62         def chr( fill, total ):
63             if fill == 0:
64                 return self.NOFILL
65             if fill >= int(total):
66                 return self.ALLFILL
67
68             index = int(float(fill*len(self.SOMEFILL))/total)
69             if index >= len(self.SOMEFILL):
70                 index = len(self.SOMEFILL)-1
71             return self.SOMEFILL[index]
72
73         chars = [chr( self.buckets[i], self.bucketsize ) for i in xrange(0,self.numbuckets-1)]
74         chars.append( chr( self.buckets[-1], self.lastbucketsize ) )
75         return "".join(chars)
76
77
78     def set_playable(self):
79         self.playable = True
80         
81     def get_playable(self):
82         return self.playable
83
84     def set_movieselector(self,movieselector):
85         self.movieselector = movieselector
86     
87     def get_bitrate(self):
88         if self.movieselector is not None:
89             return self.movieselector.get_bitrate()
90         else:
91             return 0.0
92
93     def get_blocks(self):
94         return self.tricolore
95
96
97 class ProgressInf:
98     def __init__(self):
99         self.bufferinfo = BufferInfo()
100         self.callback = None
101         
102     def get_bufferinfo(self):
103         return self.bufferinfo
104
105     def set_callback(self,callback):
106         self.callback = callback
107         
108     def bufferinfo_updated_callback(self):
109         if self.callback is not None:
110             self.callback()
111         
112
113
114 class ProgressBar(wx.Control):
115     #def __init__(self, parent, colours = ["#cfcfcf","#d7ffd7","#00ff00"], *args, **kwargs ):
116     #def __init__(self, parent, colours = ["#cfcfcf","#fde72d","#00ff00"], *args, **kwargs ):
117     #def __init__(self, parent, colours = ["#ffffff","#fde72d","#00ff00"], *args, **kwargs ):
118     def __init__(self, parent, colours = ["#ffffff","#92cddf","#006dc0"], *args, **kwargs ): ## "#ffffff","#CBCBCB","#ff3300"
119         self.colours = colours
120         self.pens    = [wx.Pen(c,0) for c in self.colours]
121         self.brushes = [wx.Brush(c) for c in self.colours]
122         self.reset()
123
124         style = wx.NO_BORDER
125         wx.Control.__init__(self, parent, -1, style=style)
126         self.SetMaxSize((-1,6))
127         self.SetMinSize((1,6))
128         self.SetBackgroundColour(wx.WHITE)
129
130         self.Bind(wx.EVT_PAINT, self.OnPaint)
131         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
132         self.SetSize((60,6))
133
134         self.progressinf = None
135
136     def AcceptsFocus(self):
137         return False
138
139     def OnEraseBackground(self, event):
140         pass # Or None
141     
142     def OnPaint(self, evt):
143         
144         # define condition
145         x,y,maxw,maxh = self.GetClientRect()
146         #dc.DrawRectangle(x,y,)
147         
148         arrowsize = 6
149         arrowspace = 1
150         numrect = float(len(self.blocks))
151
152         # create blocks
153         w = max(1,maxw/numrect)
154         h = maxh
155         
156         width, height = self.GetClientSizeTuple()
157         buffer = wx.EmptyBitmap(width, height)
158         #dc = wx.PaintDC(self)
159         dc = wx.BufferedPaintDC(self, buffer)
160         dc.BeginDrawing()
161         dc.Clear()
162         
163         rectangles = [(x+int(i*w),y,int(w)+1,h) for i in xrange(0,int(numrect))]
164
165         # draw the blocks
166         pens = [self.pens[c] for c in self.blocks]
167         brushes = [self.brushes[c] for c in self.blocks]
168                 
169         dc.DrawRectangleList(rectangles,pens,brushes)
170
171         dc.EndDrawing()
172
173     def set_pieces(self, blocks):
174         num = 50 # max number of blocks to show
175         if len(blocks) < num:
176             self.set_blocks([2*int(a) for a in blocks])
177         else:
178             sblocks = [0]*num
179             f = len(blocks)/num
180             for i in xrange(num):
181                 part = blocks[int(f*i):int(f*(i+1))]
182                 if True in part:
183                     sblocks[i] = 1
184                 if not False in part:
185                     sblocks[i] = 2
186             self.set_blocks(sblocks)
187         
188     def set_blocks(self,blocks):
189         """ Called by MainThread """
190         self.blocks = blocks
191         
192     def setNormalPercentage(self, perc):
193         perc = int(perc)
194         self.blocks = ([2]*perc)+([0]* (100-perc))
195
196     def reset(self,colour=0):
197         self.blocks = [colour] * 100
198         
199 class ProgressSlider(wx.Panel):
200     
201     def __init__(self, parent, utility, colours = ["#ffffff","#CBCBCB","#ff3300"], imgprefix= '', *args, **kwargs ):
202         self.colours = colours
203         #self.backgroundImage = wx.Image('')
204         self.progress      = 0.0
205         self.videobuffer  = 0.0
206         self.videoPosition = 0
207         self.timeposition = None
208         self.videoLength   = None
209         #wx.Control.__init__(self, parent, -1)
210         wx.Panel.__init__(self, parent, -1)
211         self.SetMaxSize((-1,25))
212         self.SetMinSize((1,25))
213         self.SetBackgroundColour(wx.WHITE)
214         self.utility = utility
215         self.bgImage = wx.Bitmap(os.path.join(self.utility.getPath(), LIBRARYNAME,'Video','Images',imgprefix+'background.png')) ## LIBRARYNAME
216         self.dotImage = wx.Bitmap(os.path.join(self.utility.getPath(), LIBRARYNAME,'Video','Images',imgprefix+'sliderDot.png')) ## LIBRARYNAME
217         self.dotImage_dis = wx.Bitmap(os.path.join(self.utility.getPath(), LIBRARYNAME,'Video','Images',imgprefix+'sliderDot_dis.png')) ## LIBRARYNAME
218         self.sliderPosition = None
219         self.rectHeight = 2
220         self.rectBorderColour = wx.LIGHT_GREY
221         self.textWidth = 70
222         self.margin = 10
223         self.doneColor = "#13bd00" # wx.RED 
224         self.bufferColor = "#0b7100" # wx.GREEN
225         self.sliderWidth = 0
226         self.selected = 2
227         self.range = (0,1)
228         self.dragging = False
229         self.allowDragging = False
230         self.Bind(wx.EVT_PAINT, self.OnPaint)
231         self.Bind(wx.EVT_SIZE, self.OnSize)
232         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
233         self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
234         #self.SetSize((-1,self.bgImage.GetSize()[1]))
235         
236     def AcceptsFocus(self):
237         return False
238
239     def OnEraseBackground(self, event):
240         pass # Or None
241     
242     def OnSize(self, event):
243         self.Refresh()
244     
245     def OnMouse(self, event):
246         if not self.allowDragging:
247             return
248         
249         pos = event.GetPosition()
250         if event.ButtonDown():
251             if self.onSliderButton(pos):
252                 print >> sys.stderr, 'ProgressSlider: Start drag'
253                 self.dragging = True
254             elif self.onSlider(pos): # click somewhere on the slider
255                 self.setSliderPosition(pos,True)
256         elif event.ButtonUp():
257             if self.dragging:
258                 print >> sys.stderr, 'ProgressSlider: End drag'
259                 self.setSliderPosition(pos, True)
260             self.dragging = False
261         elif event.Dragging():
262             if self.dragging:
263                 self.setSliderPosition(pos, False)
264         elif event.Leaving():
265             if self.dragging:
266                 self.setSliderPosition(pos,True)
267                 
268     def onSliderButton(self, pos):
269         if not self.sliderPosition:
270             return False
271         x,y = pos
272         bx, by = self.sliderPosition
273         dotSize = self.dotImage.GetSize()
274         return abs(x-bx) < dotSize[0]/2 and abs(y-by)<dotSize[1]/2
275         
276     def onSlider(self, pos):
277         x,y = pos
278         width, height = self.GetClientSizeTuple()
279         return (x > self.margin and x<= self.margin+self.sliderWidth and \
280                 abs(y - height/2) < self.rectHeight/2+4)
281         
282     def setSliderPosition(self, pos, ready):
283         x, y = pos
284         tmp_progress = (x-self.margin)/float(self.sliderWidth)
285         self.progress = min(1.0, max(0.0, tmp_progress))
286         self.videoPosition = self
287         self.Refresh()
288         if ready:
289             #theEvent = wx.ScrollEvent(pos=self.progress)
290             #self.GetEventHandler().ProcessEvent(theEvent)
291             #print >> sys.stderr, 'Posted event'
292             print >> sys.stderr, 'ProgressSlider: Set progress to : %f' % self.progress
293             self.sliderChangedAction()
294             
295     def sliderChangedAction(self):
296         self.GetParent().Seek()
297             
298         
299     def setSelected(self, sel):
300         self.selected = sel
301         self.Refresh()
302
303
304                 
305         
306     def setBufferFromPieces(self, pieces_complete):
307         if not pieces_complete:
308             self.videobuffer = 0.0
309             return
310         last_buffered_piece = 0
311         while last_buffered_piece<len(pieces_complete) and pieces_complete[last_buffered_piece]:
312             last_buffered_piece+=1
313         if last_buffered_piece == len(pieces_complete)-1:
314             last_buffered_piece += 1
315         
316         self.videobuffer = last_buffered_piece/float(len(pieces_complete)) 
317         #print >> sys.stderr, 'progress: %d/%d pieces continuous buffer (frac %f)' % \
318         #    (last_buffered_piece, len(pieces_complete), self.videobuffer)
319         
320                     
321             
322     def SetValue(self, b):
323         if self.range[0] == self.range[1]:
324             return
325         
326         if not self.dragging:
327             self.progress = max(0.0, min((b - self.range[0]) / float(self.range[1] - self.range[0]), 1.0))
328             self.Refresh()
329         
330     def GetValue(self):
331         print >>sys.stderr, 'ProgressSlider: %f, Range (%f, %f)' % (self.progress, self.range[0], self.range[1])
332         return self.progress * (self.range[1] - self.range[0])+ self.range[0]
333
334     def SetRange(self, a,b):
335         self.range = (a,b)
336     
337     def setVideoBuffer(self, buf):
338         self.videobuffer = buf
339     
340     def SetTimePosition(self, timepos, duration):
341         self.timeposition = timepos
342         self.videoLength = duration
343
344     def ResetTime(self):
345         self.videoLength = None
346         self.timeposition = None
347         self.Refresh()
348
349         
350     def formatTime(self, s):
351         longformat = time.strftime('%d:%H:%M:%S', time.gmtime(s))
352         if longformat.startswith('01:'):
353             longformat = longformat[3:]
354         while longformat.startswith('00:') and len(longformat) > len('00:00'):
355             longformat = longformat[3:]
356         return longformat
357     
358     def OnPaint(self, evt):
359         width, height = self.GetClientSizeTuple()
360         buffer = wx.EmptyBitmap(width, height)
361         #dc = wx.PaintDC(self)
362         dc = wx.BufferedPaintDC(self, buffer)
363         dc.BeginDrawing()
364         dc.Clear()
365         
366         # Draw background
367         bgSize = self.bgImage.GetSize()
368         for i in xrange(width/bgSize[0]+1):
369             dc.DrawBitmap(self.bgImage, i*bgSize[0],0)
370         
371         
372         self.sliderWidth = width-(3*self.margin+self.textWidth)
373         position = self.sliderWidth * self.progress
374         self.sliderPosition = position+self.margin, height/2
375         self.bufferlength = (self.videobuffer-self.progress) * self.sliderWidth
376         self.bufferlength = min(self.bufferlength, self.sliderWidth-position)
377         
378         # Time strings
379         if self.videoLength is not None:
380             durationString = self.formatTime(self.videoLength)
381         else:
382             durationString = '--:--'
383         if self.timeposition is not None:
384             timePositionString = self.formatTime(self.timeposition)
385         else:
386             timePositionString = '--:--'
387         
388         if width > 3*self.margin+self.textWidth:
389             # Draw slider rect
390             dc.SetPen(wx.Pen(self.rectBorderColour, 2))
391             dc.DrawRectangle(self.margin,height/2-self.rectHeight/2, self.sliderWidth, self.rectHeight)
392             # Draw slider rect inside
393             dc.SetPen(wx.Pen(self.doneColor, 0))
394             dc.SetBrush(wx.Brush(self.doneColor))
395             smallRectHeight = self.rectHeight - 2
396             dc.DrawRectangle(self.margin,height/2-smallRectHeight/2, position, smallRectHeight)
397             dc.SetBrush(wx.Brush(self.bufferColor))
398             dc.SetPen(wx.Pen(self.bufferColor, 0))
399             dc.DrawRectangle(position+self.margin,height/2-smallRectHeight/2, self.bufferlength, smallRectHeight)
400             # draw circle
401             dotSize = self.dotImage.GetSize()
402             if self.selected == 2:
403                 dc.DrawBitmap(self.dotImage_dis, position+self.margin-dotSize[0]/2, height/2-dotSize[1]/2, True)
404             else:
405                 dc.DrawBitmap(self.dotImage, position+self.margin-dotSize[0]/2, height/2-dotSize[1]/2, True)
406         if width > 2*self.margin+self.textWidth:
407             # Draw times
408             font = self.GetFont()
409             font.SetPointSize(8)
410             dc.SetFont(font)
411             dc.DrawText('%s / %s' % (timePositionString, durationString), width-self.margin-self.textWidth, height/2-dc.GetCharHeight()/2)
412
413         dc.EndDrawing()
414
415     def EnableDragging(self):
416         self.allowDragging = True
417         self.setSelected(1)
418         
419     def DisableDragging(self):
420         self.allowDragging = False
421         self.setSelected(2)
422
423  
424 class VolumeSlider(wx.Panel):
425     
426     def __init__(self, parent, utility, imgprefix=''):
427         self.progress      = 0.0
428         self.position = 0
429         
430         #wx.Control.__init__(self, parent, -1)
431         wx.Panel.__init__(self, parent, -1)
432         self.SetMaxSize((150,25))
433         self.SetMinSize((150,25))
434         self.SetBackgroundColour(wx.WHITE)
435         self.utility = utility
436         self.bgImage = wx.Bitmap(os.path.join(self.utility.getPath(), LIBRARYNAME,'Video','Images',imgprefix+'background.png')) ## LIBRARYNAME
437         self.dotImage = wx.Bitmap(os.path.join(self.utility.getPath(), LIBRARYNAME,'Video','Images',imgprefix+'sliderVolume.png')) ## LIBRARYNAME
438         self.sliderPosition = None
439         self.rectHeight = 2
440         self.rectBorderColour = wx.LIGHT_GREY
441         self.margin = 10
442         self.cursorsize = [4,19]
443         self.doneColor = wx.BLACK #wx.RED
444         self.sliderWidth = 0
445         self.range = (0,1)
446         self.dragging = False
447         self.Bind(wx.EVT_PAINT, self.OnPaint)
448         self.Bind(wx.EVT_SIZE, self.OnSize)
449         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
450         self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
451         #self.SetSize((-1,self.bgImage.GetSize()[1]))
452         
453     def AcceptsFocus(self):
454         return False
455
456     def OnEraseBackground(self, event):
457         pass # Or None
458     
459     def OnSize(self, event):
460         self.Refresh()
461     
462     def OnMouse(self, event):
463         pos = event.GetPosition()
464         if event.ButtonDown():
465             if self.onSliderButton(pos):
466                 print >> sys.stderr, 'VolumeSlider: Start drag'
467                 self.dragging = True
468             elif self.onSlider(pos): # click somewhere on the slider
469                 self.setSliderPosition(pos,True)
470         elif event.ButtonUp():
471             if self.dragging:
472                 print >> sys.stderr, 'VolumeSlider: End drag'
473                 self.setSliderPosition(pos, True)
474             self.dragging = False
475         elif event.Dragging():
476             if self.dragging:
477                 self.setSliderPosition(pos, False)
478         elif event.Leaving():
479             if self.dragging:
480                 self.setSliderPosition(pos,True)
481                 
482     def onSliderButton(self, pos):
483         if not self.sliderPosition:
484             return False
485         x,y = pos
486         bx, by = self.sliderPosition
487         extraGrip = 3 # 3px extra grip on sliderButton
488         return abs(x-bx) < self.cursorsize[0]/2+extraGrip and abs(y-by)<self.cursorsize[1]/2
489         
490     def onSlider(self, pos):
491         x,y = pos
492         width, height = self.GetClientSizeTuple()
493         return (x > self.margin and x<= self.margin+self.sliderWidth and \
494                 abs(y - height/2) < self.rectHeight/2+4)
495         
496     def setSliderPosition(self, pos, ready):
497         x, y = pos
498         tmp_progress = (x-self.margin)/float(self.sliderWidth)
499         self.progress = min(1.0, max(0.0, tmp_progress))
500         self.videoPosition = self
501         self.Refresh()
502         if ready:
503             #theEvent = wx.ScrollEvent(pos=self.progress)
504             #self.GetEventHandler().ProcessEvent(theEvent)
505             #print >> sys.stderr, 'Posted event'
506             print >> sys.stderr, 'VolumeSlider: Set progress to : %f' % self.progress
507             self.sliderChangedAction()
508             
509     def sliderChangedAction(self):
510         self.GetParent().SetVolume()
511             
512             
513     def SetValue(self, b):
514         if not self.dragging:
515             self.progress = min((b - self.range[0]) / float(self.range[1] - self.range[0]), 1.0)
516             self.Refresh()
517         
518     def GetValue(self):
519         print >>sys.stderr, 'VolumeSlider: %f, Range (%f, %f)' % (self.progress, self.range[0], self.range[1])
520         return self.progress * (self.range[1] - self.range[0])+ self.range[0]
521
522     def SetRange(self, a,b):
523         self.range = (a,b)
524     
525     def OnPaint(self, evt):
526         width, height = self.GetClientSizeTuple()
527         buffer = wx.EmptyBitmap(width, height)
528         #dc = wx.PaintDC(self)
529         dc = wx.BufferedPaintDC(self, buffer)
530         dc.BeginDrawing()
531         dc.Clear()
532         
533         # Draw background
534         bgSize = self.bgImage.GetSize()
535         for i in xrange(width/bgSize[0]+1):
536             dc.DrawBitmap(self.bgImage, i*bgSize[0],0)
537         
538         
539         self.sliderWidth = width-(2*self.margin)
540         position = self.sliderWidth * self.progress
541         self.sliderPosition = position+self.margin, height/2
542         
543         
544         if width > 2*self.margin:
545             # Draw slider rect
546             dc.SetPen(wx.Pen(self.rectBorderColour, 2))
547             dc.DrawRectangle(self.margin,height/2-self.rectHeight/2, self.sliderWidth, self.rectHeight)
548             # Draw slider rect inside
549             dc.SetPen(wx.Pen(self.doneColor, 0))
550             dc.SetBrush(wx.Brush(self.doneColor))
551             smallRectHeight = self.rectHeight - 2
552             dc.DrawRectangle(self.margin,height/2-smallRectHeight/2, position, smallRectHeight)
553             # draw slider button
554             dotSize = self.dotImage.GetSize()
555             dc.DrawBitmap(self.dotImage, position+self.margin-dotSize[0]/2, height/2-dotSize[1]/2, True)
556         dc.EndDrawing()
557
558