1"""
2 File:
3 JetSegGraph.py
4
5 Contents and purpose:
6 Draws the event graph and progress bar
7
8 Copyright (c) 2008 Android Open Source Project
9
10 Licensed under the Apache License, Version 2.0 (the "License");
11 you may not use this file except in compliance with the License.
12 You may obtain a copy of the License at
13
14      http://www.apache.org/licenses/LICENSE-2.0
15
16 Unless required by applicable law or agreed to in writing, software
17 distributed under the License is distributed on an "AS IS" BASIS,
18 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 See the License for the specific language governing permissions and
20 limitations under the License.
21"""
22
23
24import  wx
25import logging
26
27from JetUtils import *
28from JetDefs import *
29
30GRAPH_COLORS = [
31                '#C0E272',
32                '#85CF89',
33                '#CF9683',
34                '#749EDE',
35                '#9FB5B1',
36                '#B095BF',
37                '#FE546D',
38                '#B3BB97',
39                '#FFFFB8',
40
41                ]
42
43PROGRESS_BAR = '#0000CC'
44EOS_BAR = '#095000'
45APP_BAR = '#B3BB97'
46
47
48class Marker():
49    """ Defines portions of the graph for events """
50    def __init__(self, sEventType, iEventId, sName, sStartMbt, sEndMbt, iStartMeasure, ppqn):
51        self.sEventType = sEventType
52        self.iEventId = iEventId
53        self.sName = sName
54        self.StartMbt = ConvertStrTimeToTuple(sStartMbt)
55        self.EndMbt = ConvertStrTimeToTuple(sEndMbt)
56        self.iStartMeasure = iStartMeasure
57        self.iStart = 0
58        self.iEnd = 0
59        self.iWidth = 0
60        self.iHeight = 0
61        self.iTop = 0
62        self.iUpdate = False
63        self.sColor = '#FFFFB8'
64        self.ppqn = ppqn
65        self.isDirty = False
66
67    def CalcCoord(self, step, height, ColorFct):
68        """ Calculates the coordinates in pixels for graphing the shaded regions """
69        #measures
70        iStartM = self.StartMbt[0] - self.iStartMeasure
71        iEndM = self.EndMbt[0] - self.iStartMeasure
72        self.iStart = step * iStartM
73        self.iEnd = step * iEndM
74        #beats
75        self.iStart = self.iStart + ((step / 4.0) * (self.StartMbt[1]-1))
76        self.iEnd = self.iEnd + ((step / 4.0) * (self.EndMbt[1]-1))
77        #ticks
78        pctTickOfBeat = (float(self.StartMbt[2]) / float(self.ppqn))
79        self.iStart = self.iStart + ((pctTickOfBeat * (step / 4.0)))
80        pctTickOfBeat = (float(self.EndMbt[2]) / float(self.ppqn))
81        self.iEnd = self.iEnd + ((pctTickOfBeat * (step / 4.0)))
82
83        self.iWidth = self.iEnd - self.iStart
84
85        self.iHeight = height
86        self.sColor = ColorFct()
87        self.iUpdate = False
88
89class SegmentGraph(wx.Panel):
90    """ Draws the player graph bar """
91    def __init__(self, parent, pos=wx.DefaultPosition, size=wx.DefaultSize, ClickCallbackFct=None, showLabels=True, showClips=True, showAppEvts=True):
92        wx.Panel.__init__(self, parent, -1, pos=pos, size=size, style=wx.BORDER_STATIC)
93        self.iLocationInMs = 0
94        self.iLengthInMs = 0
95        self.iLengthInMeasures = 0
96        self.iMarkerTop = 15
97        self.iScaleTop = 0
98        self.iEdges = 5
99        self.iStartMeasure = 0
100        self.iMidiMode = False
101        self.ClickCallbackFct = ClickCallbackFct
102        self.iColor = 0
103        self.showLabels = showLabels
104        self.showClips = showClips
105        self.showAppEvts = showAppEvts
106
107        self.font = wx.Font(8, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, 'Courier')
108
109        self.Markers = []
110        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
111        self.Bind(wx.EVT_PAINT, self.OnPaint)
112        self.Bind(wx.EVT_SIZE, self.OnSize)
113        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
114
115        #initialize buffer
116        self.OnSize(None)
117
118    def ClearGraph(self):
119        """ Clears the graph values """
120        self.iLocationInMs = 0
121        self.iLengthInMs = 0
122        self.iLengthInMeasures = 0
123        self.iMarkerTop = 15
124        self.iScaleTop = 0
125        self.iEdges = 5
126        self.iStartMeasure = 0
127        self.iMidiMode = False
128        self.iColor = 0
129        self.Markers = []
130        self.iLocationInMs = 0
131        self.DoDrawing()
132
133    def LoadSegment(self, segment, segMarker=None, iMidiMode=False, showLabels=True, showClips=True, showAppEvts=True):
134        """ Loads up the segment drawing the graph """
135        if segment is None:
136            self.ClearGraph()
137            return None
138        self.iMidiMode = iMidiMode
139        self.showLabels = showLabels
140        self.showClips = showClips
141        self.showAppEvts = showAppEvts
142        self.Markers = []
143        self.iLocationInMs = 0
144        info = MidiSegInfo(segment)
145        #disable graph for debugging
146        #return info
147        self.iLengthInMs = info.iLengthInMs
148        self.ppqn = info.ppqn
149        self.StartMbt = mbtFct(ConvertStrTimeToTuple(segment.start), 1)
150        self.EndMbt = mbtFct(ConvertStrTimeToTuple(segment.end), 1)
151        self.LengthMbt = None
152        self.iStartMeasure = self.StartMbt[0]
153        self.iLengthInMeasures = self.EndMbt[0] - self.StartMbt[0]
154
155        for jet_event in segment.jetevents:
156            if self.showClips and jet_event.event_type == JetDefs.E_CLIP:
157                self.AddMarker(JetDefs.E_CLIP, jet_event.event_id, jet_event.event_name, mbtFct(jet_event.event_start,1), mbtFct(jet_event.event_end,1), self.iStartMeasure, self.ppqn)
158            elif jet_event.event_type == JetDefs.E_EOS:
159                self.AddMarker(JetDefs.E_EOS, jet_event.event_id, jet_event.event_name, mbtFct(jet_event.event_end,1), mbtFct(jet_event.event_end,1), self.iStartMeasure, self.ppqn)
160            elif self.showAppEvts and jet_event.event_type == JetDefs.E_APP:
161                self.AddMarker(JetDefs.E_APP, jet_event.event_id, jet_event.event_name, mbtFct(jet_event.event_start,1), mbtFct(jet_event.event_end,1), self.iStartMeasure, self.ppqn)
162
163        if segMarker is not None:
164            self.AddMarker(JetDefs.E_CLIP, 0, segMarker[0], mbtFct(segMarker[1],1), mbtFct(segMarker[2],1), self.iStartMeasure, self.ppqn)
165
166        self.DoDrawing()
167        return info
168
169    def AddMarker(self, sEventType, iEventId, sName, sStartMbt, sEndMbt, iStartMeasure, ppqn):
170        """ Adds a marker to the list """
171        if not CompareMbt(sStartMbt, sEndMbt):
172            sEndMbt = sStartMbt
173        self.Markers.append(Marker(sEventType, iEventId, sName, sStartMbt, sEndMbt, iStartMeasure, ppqn))
174
175    def OnLeftDown(self, event):
176        """ Calls the function assicated with an event """
177        pt = event.GetPosition()
178        for Marker in self.Markers:
179            if pt[0] >= Marker.iStart and pt[0] <= Marker.iEnd and pt[1] >= Marker.iTop and pt[1] <= Marker.iTop + Marker.iHeight:
180                if self.ClickCallbackFct != None:
181                    self.ClickCallbackFct(Marker.sName, Marker.iEventId)
182
183    def GetAColor(self):
184        """ Gets a color """
185        color = GRAPH_COLORS[self.iColor]
186        self.iColor = self.iColor + 1
187        if self.iColor >= len(GRAPH_COLORS):
188            self.iColor = 0
189        return color
190
191    def OnSize(self, event=None):
192        """ Repaints for resizing of screen """
193        if OsWindows():
194            # The Buffer init is done here, to make sure the buffer is always
195            # the same size as the Window
196            Size  = self.GetClientSizeTuple()
197
198            # Make new offscreen bitmap: this bitmap will always have the
199            # current drawing in it, so it can be used to save the image to
200            # a file, or whatever.
201            self._Buffer = wx.EmptyBitmap(*Size)
202        self.DoDrawing(None)
203        if event is not None:
204            event.Skip()
205
206    def OnPaint(self, event=None):
207        """ Painting of windows """
208        if OsWindows():
209            dc = wx.BufferedPaintDC(self, self._Buffer)
210        else:
211            dc = wx.AutoBufferedPaintDC(self)
212            dc.Background = wx.Brush(wx.WHITE)
213        self.DoDrawing(dc)
214
215    def DoDrawing(self, dc=None):
216        """ Does the actual drawing of the control """
217        if dc is None:
218            if OsWindows():
219                dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
220            else:
221                dc = wx.AutoBufferedPaintDC(self)
222                dc.Background = wx.Brush(wx.WHITE)
223
224        dc.Clear()
225
226        self.iColor = 0
227        gWidth, gHeight = self.GetSize()
228        gWidth = gWidth - (self.iEdges * 2)
229        step = int(gWidth / (self.iLengthInMeasures + .01))
230
231        for Marker in self.Markers:
232            Marker.CalcCoord(step, gHeight, self.GetAColor)
233
234        """ eliminate overlaps; establish colors """
235        iClips = 0
236        iMarkers = 0
237        for index, Marker in enumerate(self.Markers):
238            if Marker.sEventType == JetDefs.E_CLIP:
239                iClips = iClips + 1
240                iOverlaps = 1
241                for index1, Marker1 in enumerate(self.Markers):
242                    if Marker.sEventType == JetDefs.E_CLIP:
243                        if index != index1 and not Marker1.iUpdate:
244                            if Marker.iStart <= Marker1.iStart and Marker.iEnd <= Marker1.iEnd and Marker.iEnd >= Marker1.iStart:
245                                iOverlaps = iOverlaps + 1
246                                Marker.iUpdate = True
247                                Marker1.iUpdate = True
248                            if not Marker.iUpdate and Marker.iStart >= Marker1.iStart and Marker.iEnd >= Marker1.iEnd and Marker.iStart <= Marker1.iEnd:
249                                iOverlaps = iOverlaps + 1
250                                Marker.iUpdate = True
251                                Marker1.iUpdate = True
252                if iOverlaps > 1:
253                    iTop = 0
254                    for index1, Marker1 in enumerate(self.Markers):
255                        if Marker.sEventType == JetDefs.E_CLIP:
256                            if Marker1.iUpdate:
257                                Marker1.iHeight = gHeight / iOverlaps
258                                Marker1.iTop = iTop * Marker1.iHeight
259                                iTop = iTop + 1
260            elif Marker.sEventType == JetDefs.E_APP:
261                iMarkers = iMarkers + 1
262
263        for Marker in self.Markers:
264            if Marker.sEventType == JetDefs.E_CLIP:
265                dc.SetPen(wx.Pen(Marker.sColor))
266                dc.SetBrush(wx.Brush(Marker.sColor))
267                dc.DrawRectangle(Marker.iStart + self.iEdges, Marker.iTop, Marker.iWidth, Marker.iHeight)
268                width, height = dc.GetTextExtent(Marker.sName)
269                k = ((Marker.iStart + Marker.iEnd) / 2) - (width/2) + self.iEdges
270                if self.showLabels or self.iMidiMode:
271                    dc.DrawText(Marker.sName, k, ((Marker.iTop+Marker.iHeight/2) - (height*.5)))
272                if self.iMidiMode:
273                    self.iMidiModeStart = Marker.iStart
274            elif Marker.sEventType == JetDefs.E_EOS:
275                dc.SetPen(wx.Pen(EOS_BAR))
276                dc.SetBrush(wx.Brush(EOS_BAR))
277                dc.DrawRectangle(Marker.iStart + self.iEdges, Marker.iTop, 1, Marker.iHeight)
278                width, height = dc.GetTextExtent(Marker.sName)
279                k = Marker.iStart - (width/2) + self.iEdges
280                dc.DrawText(Marker.sName, k, ((Marker.iTop+Marker.iHeight/2) - (height*.5)))
281            elif Marker.sEventType == JetDefs.E_APP:
282                dc.SetPen(wx.Pen(APP_BAR))
283                dc.SetBrush(wx.Brush(APP_BAR))
284                dc.DrawRectangle(Marker.iStart + self.iEdges, Marker.iTop, 1, Marker.iHeight)
285                width, height = dc.GetTextExtent(Marker.sName)
286                k = Marker.iStart - (width/2) + self.iEdges
287                if self.showLabels or self.iMidiMode:
288                    dc.DrawText(Marker.sName, k, ((Marker.iTop+Marker.iHeight/2) - (height*.5)))
289
290
291        """ Draw scale """
292        if gWidth == 0:
293            iDiv = 50
294        else:
295            iDiv = (gWidth)/18
296        if iDiv == 0:
297            iDiv = 50
298        scale = ((self.iLengthInMeasures / iDiv) + 1)
299        if scale == 0:
300            scale = 1
301        beatStep = step / 4.0
302        dc.SetFont(self.font)
303        j = 0
304        lastEnd = 0
305        num = range(self.iStartMeasure, self.iStartMeasure + self.iLengthInMeasures + 1, 1)
306        dc.SetPen(wx.Pen('#5C5142'))
307        for i in range(0, (self.iLengthInMeasures+1)*step, step):
308            k = i + self.iEdges
309            dc.DrawLine(k, self.iScaleTop, k, self.iScaleTop+8)
310            if i != (self.iLengthInMeasures)*step:
311                for iBeat in range(1,4):
312                    k = i+(iBeat * beatStep) + self.iEdges
313                    dc.DrawLine(k, self.iScaleTop, k, self.iScaleTop+4)
314            width, height = dc.GetTextExtent(str(num[j]))
315            k = i-(width/2) + self.iEdges
316            if k > lastEnd:
317                if j == 0 or (j % scale) == 0:
318                    dc.DrawText(str(num[j]), k, self.iScaleTop+8)
319                lastEnd = k + width
320            j = j + 1
321
322        """ Updates the location bar in case screen moved or resized """
323        if self.iLocationInMs > 0 and self.iLengthInMs > 0:
324            iOffset = 0
325            if self.iMidiMode:
326                iOffset = self.iMidiModeStart
327
328            till = gWidth * (self.iLocationInMs / self.iLengthInMs)
329            dc.SetPen(wx.Pen(PROGRESS_BAR))
330            dc.SetBrush(wx.Brush(PROGRESS_BAR))
331            dc.DrawRectangle(self.iEdges + iOffset, gHeight-6, till, 3)
332
333    def UpdateLocation(self, iLocationInMs):
334        """ Updates the location bar """
335        #disable graph for debugging
336        #return info
337
338        self.iLocationInMs = iLocationInMs
339        if self.iLocationInMs > 0 and self.iLengthInMs > 0:
340            if OsWindows():
341                dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
342            else:
343                dc = wx.AutoBufferedPaintDC(self)
344                dc.Background = wx.Brush(wx.WHITE)
345
346            iOffset = 0
347            if self.iMidiMode:
348                iOffset = self.iMidiModeStart
349
350            gWidth, gHeight = self.GetSize()
351            gWidth = gWidth - (self.iEdges * 2)
352
353            till = gWidth * (self.iLocationInMs / self.iLengthInMs)
354            dc.SetPen(wx.Pen(PROGRESS_BAR))
355            dc.SetBrush(wx.Brush(PROGRESS_BAR))
356            dc.DrawRectangle(self.iEdges + iOffset, gHeight-6, till, 3)
357            self.isDirty = True
358        else:
359            if self.isDirty:
360                self.DoDrawing()
361                self.isDirty = False
362