misc: merge branch 'release-staging-v19.0.0.0' into develop
[gem5.git] / util / minorview / view.py
1 # Copyright (c) 2013 ARM Limited
2 # All rights reserved
3 #
4 # The license below extends only to copyright in the software and shall
5 # not be construed as granting a license to any other intellectual
6 # property including but not limited to intellectual property relating
7 # to a hardware implementation of the functionality of the software
8 # licensed hereunder. You may use the software subject to the license
9 # terms below provided that you ensure that this notice is replicated
10 # unmodified and in its entirety in all distributions of the software,
11 # modified or unmodified, in source code or in binary form.
12 #
13 # Redistribution and use in source and binary forms, with or without
14 # modification, are permitted provided that the following conditions are
15 # met: redistributions of source code must retain the above copyright
16 # notice, this list of conditions and the following disclaimer;
17 # redistributions in binary form must reproduce the above copyright
18 # notice, this list of conditions and the following disclaimer in the
19 # documentation and/or other materials provided with the distribution;
20 # neither the name of the copyright holders nor the names of its
21 # contributors may be used to endorse or promote products derived from
22 # this software without specific prior written permission.
23 #
24 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
36 import pygtk
37 pygtk.require('2.0')
38 import gtk
39 import gobject
40 import cairo
41 import re
42
43 from point import Point
44 import parse
45 import colours
46 import model
47 from model import Id, BlobModel, BlobDataSelect, special_state_chars
48 import blobs
49
50 class BlobView(object):
51 """The canvas view of the pipeline"""
52 def __init__(self, model):
53 # A unit blob will appear at size blobSize inside a space of
54 # size pitch.
55 self.blobSize = Point(45.0, 45.0)
56 self.pitch = Point(60.0, 60.0)
57 self.origin = Point(50.0, 50.0)
58 # Some common line definitions to cut down on arbitrary
59 # set_line_widths
60 self.thickLineWidth = 10.0
61 self.thinLineWidth = 4.0
62 self.midLineWidth = 6.0
63 # The scale from the units of pitch to device units (nominally
64 # pixels for 1.0 to 1.0
65 self.masterScale = Point(1.0,1.0)
66 self.model = model
67 self.fillColour = colours.emptySlotColour
68 self.timeIndex = 0
69 self.time = 0
70 self.positions = []
71 self.controlbar = None
72 # The sequence number selector state
73 self.dataSelect = BlobDataSelect()
74 # Offset of this view's time from self.time used for miniviews
75 # This is actually an offset of the index into the array of times
76 # seen in the event file)
77 self.timeOffset = 0
78 # Maximum view size for initial window mapping
79 self.initialHeight = 600.0
80
81 # Overlays are speech bubbles explaining blob data
82 self.overlays = []
83
84 self.da = gtk.DrawingArea()
85 def draw(arg1, arg2):
86 self.redraw()
87 self.da.connect('expose_event', draw)
88
89 # Handy offsets from the blob size
90 self.blobIndent = (self.pitch - self.blobSize).scale(0.5)
91 self.blobIndentFactor = self.blobIndent / self.pitch
92
93 def add_control_bar(self, controlbar):
94 """Add a BlobController to this view"""
95 self.controlbar = controlbar
96
97 def draw_to_png(self, filename):
98 """Draw the view to a PNG file"""
99 surface = cairo.ImageSurface(
100 cairo.FORMAT_ARGB32,
101 self.da.get_allocation().width,
102 self.da.get_allocation().height)
103 cr = gtk.gdk.CairoContext(cairo.Context(surface))
104 self.draw_to_cr(cr)
105 surface.write_to_png(filename)
106
107 def draw_to_cr(self, cr):
108 """Draw to a given CairoContext"""
109 cr.set_source_color(colours.backgroundColour)
110 cr.set_line_width(self.thickLineWidth)
111 cr.paint()
112 cr.save()
113 cr.scale(*self.masterScale.to_pair())
114 cr.translate(*self.origin.to_pair())
115
116 positions = [] # {}
117
118 # Draw each blob
119 for blob in self.model.blobs:
120 blob_event = self.model.find_unit_event_by_time(
121 blob.unit, self.time)
122
123 cr.save()
124 pos = blob.render(cr, self, blob_event, self.dataSelect,
125 self.time)
126 cr.restore()
127 if pos is not None:
128 (centre, size) = pos
129 positions.append((blob, centre, size))
130
131 # Draw all the overlays over the top
132 for overlay in self.overlays:
133 overlay.show(cr)
134
135 cr.restore()
136
137 return positions
138
139 def redraw(self):
140 """Redraw the whole view"""
141 buffer = cairo.ImageSurface(
142 cairo.FORMAT_ARGB32,
143 self.da.get_allocation().width,
144 self.da.get_allocation().height)
145
146 cr = gtk.gdk.CairoContext(cairo.Context(buffer))
147 positions = self.draw_to_cr(cr)
148
149 # Assume that blobs are in order for depth so we want to
150 # hit the frontmost blob first if we search by position
151 positions.reverse()
152 self.positions = positions
153
154 # Paint the drawn buffer onto the DrawingArea
155 dacr = self.da.window.cairo_create()
156 dacr.set_source_surface(buffer, 0.0, 0.0)
157 dacr.paint()
158
159 buffer.finish()
160
161 def set_time_index(self, time):
162 """Set the time index for the view. A time index is an index into
163 the model's times array of seen event times"""
164 self.timeIndex = time + self.timeOffset
165 if len(self.model.times) != 0:
166 if self.timeIndex >= len(self.model.times):
167 self.time = self.model.times[len(self.model.times) - 1]
168 else:
169 self.time = self.model.times[self.timeIndex]
170 else:
171 self.time = 0
172
173 def get_pic_size(self):
174 """Return the size of ASCII-art picture of the pipeline scaled by
175 the blob pitch"""
176 return (self.origin + self.pitch *
177 (self.model.picSize + Point(1.0,1.0)))
178
179 def set_da_size(self):
180 """Set the DrawingArea size after scaling"""
181 self.da.set_size_request(10 , int(self.initialHeight))
182
183 class BlobController(object):
184 """The controller bar for the viewer"""
185 def __init__(self, model, view,
186 defaultEventFile="", defaultPictureFile=""):
187 self.model = model
188 self.view = view
189 self.playTimer = None
190 self.filenameEntry = gtk.Entry()
191 self.filenameEntry.set_text(defaultEventFile)
192 self.pictureEntry = gtk.Entry()
193 self.pictureEntry.set_text(defaultPictureFile)
194 self.timeEntry = None
195 self.defaultEventFile = defaultEventFile
196 self.startTime = None
197 self.endTime = None
198
199 self.otherViews = []
200
201 def make_bar(elems):
202 box = gtk.HBox(homogeneous=False, spacing=2)
203 box.set_border_width(2)
204 for widget, signal, handler in elems:
205 if signal is not None:
206 widget.connect(signal, handler)
207 box.pack_start(widget, False, True, 0)
208 return box
209
210 self.timeEntry = gtk.Entry()
211
212 t = gtk.ToggleButton('T')
213 t.set_active(False)
214 s = gtk.ToggleButton('S')
215 s.set_active(True)
216 p = gtk.ToggleButton('P')
217 p.set_active(True)
218 l = gtk.ToggleButton('L')
219 l.set_active(True)
220 f = gtk.ToggleButton('F')
221 f.set_active(True)
222 e = gtk.ToggleButton('E')
223 e.set_active(True)
224
225 # Should really generate this from above
226 self.view.dataSelect.ids = set("SPLFE")
227
228 self.bar = gtk.VBox()
229 self.bar.set_homogeneous(False)
230
231 row1 = make_bar([
232 (gtk.Button('Start'), 'clicked', self.time_start),
233 (gtk.Button('End'), 'clicked', self.time_end),
234 (gtk.Button('Back'), 'clicked', self.time_back),
235 (gtk.Button('Forward'), 'clicked', self.time_forward),
236 (gtk.Button('Play'), 'clicked', self.time_play),
237 (gtk.Button('Stop'), 'clicked', self.time_stop),
238 (self.timeEntry, 'activate', self.time_set),
239 (gtk.Label('Visible ids:'), None, None),
240 (t, 'clicked', self.toggle_id('T')),
241 (gtk.Label('/'), None, None),
242 (s, 'clicked', self.toggle_id('S')),
243 (gtk.Label('.'), None, None),
244 (p, 'clicked', self.toggle_id('P')),
245 (gtk.Label('/'), None, None),
246 (l, 'clicked', self.toggle_id('L')),
247 (gtk.Label('/'), None, None),
248 (f, 'clicked', self.toggle_id('F')),
249 (gtk.Label('.'), None, None),
250 (e, 'clicked', self.toggle_id('E')),
251 (self.filenameEntry, 'activate', self.load_events),
252 (gtk.Button('Reload'), 'clicked', self.load_events)
253 ])
254
255 self.bar.pack_start(row1, False, True, 0)
256 self.set_time_index(0)
257
258 def toggle_id(self, id):
259 """One of the sequence number selector buttons has been toggled"""
260 def toggle(button):
261 if button.get_active():
262 self.view.dataSelect.ids.add(id)
263 else:
264 self.view.dataSelect.ids.discard(id)
265
266 # Always leave one thing visible
267 if len(self.view.dataSelect.ids) == 0:
268 self.view.dataSelect.ids.add(id)
269 button.set_active(True)
270 self.view.redraw()
271 return toggle
272
273 def set_time_index(self, time):
274 """Set the time index in the view"""
275 self.view.set_time_index(time)
276
277 for view in self.otherViews:
278 view.set_time_index(time)
279 view.redraw()
280
281 self.timeEntry.set_text(str(self.view.time))
282
283 def time_start(self, button):
284 """Start pressed"""
285 self.set_time_index(0)
286 self.view.redraw()
287
288 def time_end(self, button):
289 """End pressed"""
290 self.set_time_index(len(self.model.times) - 1)
291 self.view.redraw()
292
293 def time_forward(self, button):
294 """Step forward pressed"""
295 self.set_time_index(min(self.view.timeIndex + 1,
296 len(self.model.times) - 1))
297 self.view.redraw()
298 gtk.gdk.flush()
299
300 def time_back(self, button):
301 """Step back pressed"""
302 self.set_time_index(max(self.view.timeIndex - 1, 0))
303 self.view.redraw()
304
305 def time_set(self, entry):
306 """Time dialogue changed. Need to find a suitable time
307 <= the entry's time"""
308 newTime = self.model.find_time_index(int(entry.get_text()))
309 self.set_time_index(newTime)
310 self.view.redraw()
311
312 def time_step(self):
313 """Time step while playing"""
314 if not self.playTimer \
315 or self.view.timeIndex == len(self.model.times) - 1:
316 self.time_stop(None)
317 return False
318 else:
319 self.time_forward(None)
320 return True
321
322 def time_play(self, play):
323 """Automatically advance time every 100 ms"""
324 if not self.playTimer:
325 self.playTimer = gobject.timeout_add(100, self.time_step)
326
327 def time_stop(self, play):
328 """Stop play pressed"""
329 if self.playTimer:
330 gobject.source_remove(self.playTimer)
331 self.playTimer = None
332
333 def load_events(self, button):
334 """Reload events file"""
335 self.model.load_events(self.filenameEntry.get_text(),
336 startTime=self.startTime, endTime=self.endTime)
337 self.set_time_index(min(len(self.model.times) - 1,
338 self.view.timeIndex))
339 self.view.redraw()
340
341 class Overlay(object):
342 """An Overlay is a speech bubble explaining the data in a blob"""
343 def __init__(self, model, view, point, blob):
344 self.model = model
345 self.view = view
346 self.point = point
347 self.blob = blob
348
349 def find_event(self):
350 """Find the event for a changing time and a fixed blob"""
351 return self.model.find_unit_event_by_time(self.blob.unit,
352 self.view.time)
353
354 def show(self, cr):
355 """Draw the overlay"""
356 event = self.find_event()
357
358 if event is None:
359 return
360
361 insts = event.find_ided_objects(self.model, self.blob.picChar,
362 False)
363
364 cr.set_line_width(self.view.thinLineWidth)
365 cr.translate(*(Point(0.0,0.0) - self.view.origin).to_pair())
366 cr.scale(*(Point(1.0,1.0) / self.view.masterScale).to_pair())
367
368 # Get formatted data from the insts to format into a table
369 lines = list(inst.table_line() for inst in insts)
370
371 text_size = 10.0
372 cr.set_font_size(text_size)
373
374 def text_width(str):
375 xb, yb, width, height, dx, dy = cr.text_extents(str)
376 return width
377
378 # Find the maximum number of columns and the widths of each column
379 num_columns = 0
380 for line in lines:
381 num_columns = max(num_columns, len(line))
382
383 widths = [0] * num_columns
384 for line in lines:
385 for i in xrange(0, len(line)):
386 widths[i] = max(widths[i], text_width(line[i]))
387
388 # Calculate the size of the speech bubble
389 column_gap = 1 * text_size
390 id_width = 6 * text_size
391 total_width = sum(widths) + id_width + column_gap * (num_columns + 1)
392 gap_step = Point(1.0, 0.0).scale(column_gap)
393
394 text_point = self.point
395 text_step = Point(0.0, text_size)
396
397 size = Point(total_width, text_size * len(insts))
398
399 # Draw the speech bubble
400 blobs.speech_bubble(cr, self.point, size, text_size)
401 cr.set_source_color(colours.backgroundColour)
402 cr.fill_preserve()
403 cr.set_source_color(colours.black)
404 cr.stroke()
405
406 text_point += Point(1.0,1.0).scale(2.0 * text_size)
407
408 id_size = Point(id_width, text_size)
409
410 # Draw the rows in the table
411 for i in xrange(0, len(insts)):
412 row_point = text_point
413 inst = insts[i]
414 line = lines[i]
415 blobs.striped_box(cr, row_point + id_size.scale(0.5),
416 id_size, inst.id.to_striped_block(self.view.dataSelect))
417 cr.set_source_color(colours.black)
418
419 row_point += Point(1.0, 0.0).scale(id_width)
420 row_point += text_step
421 # Draw the columns of each row
422 for j in xrange(0, len(line)):
423 row_point += gap_step
424 cr.move_to(*row_point.to_pair())
425 cr.show_text(line[j])
426 row_point += Point(1.0, 0.0).scale(widths[j])
427
428 text_point += text_step
429
430 class BlobWindow(object):
431 """The top-level window and its mouse control"""
432 def __init__(self, model, view, controller):
433 self.model = model
434 self.view = view
435 self.controller = controller
436 self.controlbar = None
437 self.window = None
438 self.miniViewCount = 0
439
440 def add_control_bar(self, controlbar):
441 self.controlbar = controlbar
442
443 def show_window(self):
444 self.window = gtk.Window()
445
446 self.vbox = gtk.VBox()
447 self.vbox.set_homogeneous(False)
448 if self.controlbar:
449 self.vbox.pack_start(self.controlbar, False, True, 0)
450 self.vbox.add(self.view.da)
451
452 if self.miniViewCount > 0:
453 self.miniViews = []
454 self.miniViewHBox = gtk.HBox(homogeneous=True, spacing=2)
455
456 # Draw mini views
457 for i in xrange(1, self.miniViewCount + 1):
458 miniView = BlobView(self.model)
459 miniView.set_time_index(0)
460 miniView.masterScale = Point(0.1, 0.1)
461 miniView.set_da_size()
462 miniView.timeOffset = i + 1
463 self.miniViews.append(miniView)
464 self.miniViewHBox.pack_start(miniView.da, False, True, 0)
465
466 self.controller.otherViews = self.miniViews
467 self.vbox.add(self.miniViewHBox)
468
469 self.window.add(self.vbox)
470
471 def show_event(picChar, event):
472 print '**** Comments for', event.unit, \
473 'at time', self.view.time
474 for name, value in event.pairs.iteritems():
475 print name, '=', value
476 for comment in event.comments:
477 print comment
478 if picChar in event.visuals:
479 # blocks = event.visuals[picChar].elems()
480 print '**** Colour data'
481 objs = event.find_ided_objects(self.model, picChar, True)
482 for obj in objs:
483 print ' '.join(obj.table_line())
484
485 def clicked_da(da, b):
486 point = Point(b.x, b.y)
487
488 overlay = None
489 for blob, centre, size in self.view.positions:
490 if point.is_within_box((centre, size)):
491 event = self.model.find_unit_event_by_time(blob.unit,
492 self.view.time)
493 if event is not None:
494 if overlay is None:
495 overlay = Overlay(self.model, self.view, point,
496 blob)
497 show_event(blob.picChar, event)
498 if overlay is not None:
499 self.view.overlays = [overlay]
500 else:
501 self.view.overlays = []
502
503 self.view.redraw()
504
505 # Set initial size and event callbacks
506 self.view.set_da_size()
507 self.view.da.add_events(gtk.gdk.BUTTON_PRESS_MASK)
508 self.view.da.connect('button-press-event', clicked_da)
509 self.window.connect('destroy', lambda(widget): gtk.main_quit())
510
511 def resize(window, event):
512 """Resize DrawingArea to match new window size"""
513 size = Point(float(event.width), float(event.height))
514 proportion = size / self.view.get_pic_size()
515 # Preserve aspect ratio
516 daScale = min(proportion.x, proportion.y)
517 self.view.masterScale = Point(daScale, daScale)
518 self.view.overlays = []
519
520 self.view.da.connect('configure-event', resize)
521
522 self.window.show_all()