summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGary Bressler <garybressler@nc.rr.com>2010-04-27 14:04:50 -0400
committerGary Bressler <garybressler@nc.rr.com>2010-04-27 14:04:50 -0400
commitdf6c90f22f5cdd5b079a9a69a54f30104eab9146 (patch)
treedb68985cc6858faaab1728aa8972e298266b837f
parente3a1e19e87dbb3df413015bfc714283bc8602999 (diff)
Colorsgit add .! COLORSgit add .git add .geany index.txt
-rw-r--r--test.pngbin0 -> 1507837 bytes
-rw-r--r--unit_trace/viz/__init__.py2
-rw-r--r--unit_trace/viz/canvas.py314
-rw-r--r--unit_trace/viz/format.py2
-rw-r--r--unit_trace/viz/graph.py307
-rw-r--r--unit_trace/viz/renderer.py6
-rw-r--r--unit_trace/viz/schedule.py364
-rw-r--r--unit_trace/viz/viewer.py264
8 files changed, 901 insertions, 358 deletions
diff --git a/test.png b/test.png
new file mode 100644
index 0000000..ab2ebc0
--- /dev/null
+++ b/test.png
Binary files differ
diff --git a/unit_trace/viz/__init__.py b/unit_trace/viz/__init__.py
index 99ed99e..592f697 100644
--- a/unit_trace/viz/__init__.py
+++ b/unit_trace/viz/__init__.py
@@ -19,6 +19,8 @@ gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RU
19 None, (gtk.Adjustment, gtk.Adjustment)) 19 None, (gtk.Adjustment, gtk.Adjustment))
20gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 20gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
21 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) 21 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
22gobject.signal_new('update-sel-changes', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
23 None, (gobject.TYPE_PYOBJECT,))
22gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 24gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
23 None, (gtk.gdk.Event, gobject.TYPE_PYOBJECT)) 25 None, (gtk.gdk.Event, gobject.TYPE_PYOBJECT))
24gobject.signal_new('request-refresh-events', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 26gobject.signal_new('request-refresh-events', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
diff --git a/unit_trace/viz/canvas.py b/unit_trace/viz/canvas.py
index ea73d98..9ac0e8b 100644
--- a/unit_trace/viz/canvas.py
+++ b/unit_trace/viz/canvas.py
@@ -1,9 +1,9 @@
1#!/usr/bin/python 1#!/usr/bin/python
2 2
3"""Classes related to the drawing and area-selection primitives. Note that 3"""Classes related to the drawing and area-selection primitives. This module
4this file is quite low-level, in that its objects are mostly restricted to 4is quite low-level, in that its objects are mostly restricted to
5dealing with drawing the components of a real-time graph given coordinates 5dealing with just drawing the components of a real-time graph at specific
6rather than having an abstract knowledge of the graph's measurements or 6coordinates, rather than having any knowledge of the graph's measurements or
7any information about events.""" 7any information about events."""
8 8
9import math 9import math
@@ -14,16 +14,9 @@ import copy
14import util 14import util
15from format import * 15from format import *
16 16
17def snap(pos):
18 """Takes in an x- or y-coordinate ``pos'' and snaps it to the pixel grid.
19 This is necessary because integer coordinates in Cairo actually denote
20 the spaces between pixels, not the pixels themselves, so if we draw a
21 line of width 1 on integer coordinates, it will come out blurry unless we shift it,
22 since the line will get distributed over two pixels. We actually apply this to all
23 coordinates to make sure everything is aligned."""
24 return pos - 0.5
25
26class Surface(object): 17class Surface(object):
18 """Represents the actual surface that gets drawn on. Its size is the size of the
19 area that the user actually sees."""
27 def __init__(self, fname='temp', ctx=None): 20 def __init__(self, fname='temp', ctx=None):
28 self.virt_x = 0 21 self.virt_x = 0
29 self.virt_y = 0 22 self.virt_y = 0
@@ -40,9 +33,6 @@ class Surface(object):
40 def change_ctx(self, ctx): 33 def change_ctx(self, ctx):
41 self.ctx = ctx 34 self.ctx = ctx
42 35
43 def get_fname(self):
44 return self.fname
45
46 def write_out(self, fname): 36 def write_out(self, fname):
47 raise NotImplementedError 37 raise NotImplementedError
48 38
@@ -57,7 +47,7 @@ class Surface(object):
57 self.virt_y = y 47 self.virt_y = y
58 self.width = width 48 self.width = width
59 self.height = height 49 self.height = height
60 50
61 def set_scale(self, scale): 51 def set_scale(self, scale):
62 """Sets the scale factor.""" 52 """Sets the scale factor."""
63 self.scale = scale 53 self.scale = scale
@@ -83,9 +73,11 @@ class SVGSurface(Surface):
83 def renew(self, width, height): 73 def renew(self, width, height):
84 iwidth = int(math.ceil(width)) 74 iwidth = int(math.ceil(width))
85 iheight = int(math.ceil(height)) 75 iheight = int(math.ceil(height))
76 self.width = width
77 self.height = height
86 self.surface = cairo.SVGSurface(self.fname, iwidth, iheight) 78 self.surface = cairo.SVGSurface(self.fname, iwidth, iheight)
87 self.ctx = cairo.Context(self.surface) 79 self.ctx = cairo.Context(self.surface)
88 80
89 def write_out(self, fname): 81 def write_out(self, fname):
90 os.execl('cp', self.fname, fname) 82 os.execl('cp', self.fname, fname)
91 83
@@ -93,12 +85,14 @@ class ImageSurface(Surface):
93 def renew(self, width, height): 85 def renew(self, width, height):
94 iwidth = int(math.ceil(width)) 86 iwidth = int(math.ceil(width))
95 iheight = int(math.ceil(height)) 87 iheight = int(math.ceil(height))
88 self.width = width
89 self.height = height
96 self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, iwidth, iheight) 90 self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, iwidth, iheight)
97 self.ctx = cairo.Context(self.surface) 91 self.ctx = cairo.Context(self.surface)
98 92
99 def write_out(self, fname): 93 def write_out(self, fname):
100 if self.surface is None: 94 if self.surface is None:
101 raise ValueError('Don\'t own surface, can\'t write to to file') 95 raise ValueError("Don't own surface, can't write to file")
102 96
103 self.surface.write_to_png(fname) 97 self.surface.write_to_png(fname)
104 98
@@ -110,6 +104,9 @@ class Pattern(object):
110 self.color_list = color_list 104 self.color_list = color_list
111 self.stripe_size = stripe_size 105 self.stripe_size = stripe_size
112 106
107 def get_color_list(self):
108 return self.color_list
109
113 def render_on_canvas(self, canvas, x, y, width, height, fade=False): 110 def render_on_canvas(self, canvas, x, y, width, height, fade=False):
114 fade_span = min(width, Pattern.MAX_FADE_WIDTH) 111 fade_span = min(width, Pattern.MAX_FADE_WIDTH)
115 112
@@ -169,7 +166,8 @@ class Canvas(object):
169 or filling in bars, respectively.""" 166 or filling in bars, respectively."""
170 167
171 self.surface = surface 168 self.surface = surface
172 169 self.def_surface = None
170
173 self.width = int(math.ceil(width)) 171 self.width = int(math.ceil(width))
174 self.height = int(math.ceil(height)) 172 self.height = int(math.ceil(height))
175 self.item_clist = item_clist 173 self.item_clist = item_clist
@@ -179,15 +177,28 @@ class Canvas(object):
179 177
180 self.scale = 1.0 178 self.scale = 1.0
181 179
182 # clears the canvas. 180 def set_tmp_surface(self, surface):
183 def clear(self): 181 """Changes to a temporary surface, used, perhaps, for printing
184 raise NotImplementedError 182 or drawing to a file."""
185 183 if self.def_surface is not None:
184 raise ValueError
185
186 self.def_surface = self.surface
187 self.surface = surface
188
189 def revert_surface(self):
190 """Goes back to the default surface; that is, the one used for
191 drawing to the screen."""
192 if self.def_surface is not None:
193 self.surface = self.def_surface
194 self.def_surface = None
195
186 def set_scale(self, scale): 196 def set_scale(self, scale):
187 self.scale = scale 197 self.scale = scale
188 self.surface.set_scale(scale) 198 self.surface.set_scale(scale)
189 for event in self.selectable_regions: 199 for layer in self.selectable_regions:
190 self.selectable_regions[event].set_scale(scale) 200 for event in self.selectable_regions[layer]:
201 self.selectable_regions[layer][event].set_scale(scale)
191 202
192 def scaled(self, *coors): 203 def scaled(self, *coors):
193 """Scales a series of coordinates.""" 204 """Scales a series of coordinates."""
@@ -241,24 +252,6 @@ class Canvas(object):
241 252
242 self.draw_line((x, y), (x, y - height), (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS) 253 self.draw_line((x, y), (x, y - height), (0.0, 0.0, 0.0), GraphFormat.AXIS_THICKNESS)
243 254
244 def draw_y_axis_labels(self, x, y, height, item_list, item_size, fopts=None):
245 """Draws the item labels on the y-axis. ``item_list'' is the list
246 of strings to print, while item_size gives the vertical amount of
247 space that each item shall take up, in pixels."""
248 if fopts is None:
249 fopts = GraphFormat.DEF_FOPTS_ITEM
250
251 x -= GraphFormat.Y_AXIS_ITEM_GAP
252 y -= height - item_size / 2.0
253
254 orig_color = fopts.color
255 for ctr, item in enumerate(item_list):
256 fopts.color = self.get_item_color(ctr)
257 self.draw_label(item, x, y, fopts, AlignMode.RIGHT, AlignMode.CENTER)
258 y += item_size
259
260 fopts.color = orig_color
261
262 def draw_x_axis(self, x, y, start_tick, end_tick, maj_sep, min_per_maj): 255 def draw_x_axis(self, x, y, start_tick, end_tick, maj_sep, min_per_maj):
263 """Draws the x-axis, including all the major and minor ticks (but not the labels). 256 """Draws the x-axis, including all the major and minor ticks (but not the labels).
264 ``num_maj'' gives the number of major ticks, ``maj_sep'' the number of pixels between 257 ``num_maj'' gives the number of major ticks, ``maj_sep'' the number of pixels between
@@ -295,7 +288,7 @@ class Canvas(object):
295 minincr = incr / (min_per_maj * 1.0) 288 minincr = incr / (min_per_maj * 1.0)
296 289
297 cur = start * 1.0 290 cur = start * 1.0
298 291
299 for i in range(start_tick, end_tick + 1): 292 for i in range(start_tick, end_tick + 1):
300 text = util.format_float(cur, 2) 293 text = util.format_float(cur, 2)
301 self.draw_label(text, x, y, majfopts, AlignMode.CENTER, AlignMode.TOP) 294 self.draw_label(text, x, y, majfopts, AlignMode.CENTER, AlignMode.TOP)
@@ -358,6 +351,87 @@ class Canvas(object):
358 351
359 self.draw_rect(x, y, width, height, color, thickness, clip_side) 352 self.draw_rect(x, y, width, height, color, thickness, clip_side)
360 353
354 def draw_y_axis_item(self, x, y, axis_height, item, item_no, item_size, selected, fopts=None):
355 """Draws the item labels on the y-axis. ``item'' is the string to print,
356 while item_size gives the vertical amount of
357 space that each item shall take up, in pixels."""
358 if fopts is None:
359 fopts = GraphFormat.DEF_FOPTS_ITEM
360
361 x -= GraphFormat.Y_AXIS_ITEM_GAP
362 y -= axis_height - item_size / 2.0 - item_size * item_no
363
364 orig_color = fopts.color
365
366 if selected:
367 fopts.color = GraphFormat.HIGHLIGHT_COLOR
368 else:
369 fopts.color = self.get_item_color(item_no)
370 self.draw_label(item, x, y, fopts, AlignMode.RIGHT, AlignMode.CENTER)
371
372 fopts.color = orig_color
373
374 def add_sel_y_axis_item(self, x, y, axis_height, item, item_no, item_size, event, fopts=None):
375 if fopts is None:
376 fopts = GraphFormat.DEF_FOPTS_ITEM
377
378 x -= GraphFormat.Y_AXIS_ITEM_GAP
379 y -= axis_height - item_size / 2.0 - item_size * item_no
380
381 x, y, width, height, f_height = self.get_label_dim(item, x, y, fopts, AlignMode.RIGHT, AlignMode.CENTER, False)
382 #(x, y) gives bottom-left, not top-left
383 y -= height
384 self.add_sel_region(SelectableRegion(x, y, width, height, event))
385
386 def draw_top_item(self, x, y, axis_height, item, item_no, prev_items, selected, fopts=None):
387 """Draws the item labels at the top. ``item'' is the string to print,
388 while item_size gives the vertical amount of
389 space that each item shall take up, in pixels. ``prev_items'' is the set
390 of items to be printed before this item (although this method prints only
391 ``item'', ``prev_item'' is needed to find the proper offset"""
392 if fopts is None:
393 fopts = GraphFormat.DEF_FOPTS_ITEM
394
395 y -= axis_height + GraphFormat.TOP_ITEM_VERT_GAP
396
397 for prev_item in prev_items:
398 x += GraphFormat.TOP_ITEM_HORIZ_GAP + self.get_label_dim(prev_item, x, y, fopts,
399 AlignMode.LEFT, AlignMode.TOP)[2]
400
401 orig_color = fopts.color
402
403 if selected:
404 fopts.color = GraphFormat.HIGHLIGHT_COLOR
405 else:
406 fopts.color = self.get_bar_pattern(item_no).get_color_list()[0]
407
408 self.draw_label(item, x, y, fopts, AlignMode.LEFT, AlignMode.BOTTOM)
409
410 fopts.color = orig_color
411
412 def add_sel_top_item(self, x, y, axis_height, item, item_no, prev_items, event, fopts=None):
413 """Draws the item labels at the top. ``item'' is the string to print,
414 while item_size gives the vertical amount of
415 space that each item shall take up, in pixels. ``prev_items'' is the set
416 of items to be printed before this item (although this method prints only
417 ``item'', ``prev_item'' is needed to find the proper offset"""
418 if fopts is None:
419 fopts = GraphFormat.DEF_FOPTS_ITEM
420
421 y -= axis_height + GraphFormat.TOP_ITEM_VERT_GAP
422
423 for item in prev_items:
424 x += GraphFormat.TOP_ITEM_HORIZ_GAP + self.get_label_dim(item, x, y, fopts,
425 AlignMode.LEFT, AlignMode.BOTTOM)[2]
426
427 x, y, width, height, f_height = self.get_label_dim(item, x, y, fopts, AlignMode.LEFT, AlignMode.BOTTOM, False)
428 #(x, y) gives bottom-left, not top-left
429 y -= height
430 self.add_sel_region(SelectableRegion(x, y, width, height, event))
431
432 def add_sel_dummy(self, event):
433 self.add_sel_region(SelectableRegion(None, None, None, None, event))
434
361 def add_sel_bar(self, x, y, width, height, event): 435 def add_sel_bar(self, x, y, width, height, event):
362 self.add_sel_region(SelectableRegion(x, y, width, height, event)) 436 self.add_sel_region(SelectableRegion(x, y, width, height, event))
363 437
@@ -518,30 +592,31 @@ class Canvas(object):
518 def clear_selectable_regions(self): 592 def clear_selectable_regions(self):
519 self.selectable_regions = {} 593 self.selectable_regions = {}
520 594
521 #def clear_selectable_regions(self, real_x, real_y, width, height):
522 # x, y = self.surface.get_virt_coor(real_x, real_y)
523 # for event in self.selectable_regions.keys():
524 # if self.selectable_regions[event].intersects(x, y, width, height):
525 # del self.selectable_regions[event]
526
527 def add_sel_region(self, region): 595 def add_sel_region(self, region):
528 region.set_scale(self.scale) 596 region.set_scale(self.scale)
529 self.selectable_regions[region.get_event()] = region 597 layer = region.get_event().get_layer()
598 if layer not in self.selectable_regions:
599 self.selectable_regions[layer] = {}
600 self.selectable_regions[layer][region.get_event()] = region
530 601
531 def get_sel_region(self, event): 602 def get_sel_region(self, event):
532 return self.selectable_regions[event] 603 return self.selectable_regions[event.get_layer()][event]
533 604
534 def has_sel_region(self, event): 605 def has_sel_region(self, event):
535 return event in self.selectable_regions 606 return event.get_layer() in self.selectable_regions \
607 and event in self.selectable_regions[event.get_layer()]
536 608
537 def get_selected_regions(self, real_x, real_y, width, height): 609 def get_intersecting_regions(self, real_x, real_y, width, height):
538 x, y = self.surface.get_virt_coor(real_x, real_y) 610 x, y = self.surface.get_virt_coor(real_x, real_y)
539 611
540 selected = {} 612 selected = {}
541 for event in self.selectable_regions: 613 for layer in self.selectable_regions:
542 region = self.selectable_regions[event] 614 for event in self.selectable_regions[layer]:
543 if region.intersects(x, y, width, height): 615 region = self.selectable_regions[layer][event]
544 selected[event] = region 616 if region.intersects(x, y, width, height):
617 if layer not in selected:
618 selected[layer] = {}
619 selected[layer][event] = region
545 620
546 return selected 621 return selected
547 622
@@ -551,25 +626,21 @@ class Canvas(object):
551 626
552 x, y = self.surface.get_virt_coor_unscaled(0, 0) 627 x, y = self.surface.get_virt_coor_unscaled(0, 0)
553 width, height = self.unscaled(self.surface.width, self.surface.height) 628 width, height = self.unscaled(self.surface.width, self.surface.height)
554 629
555 self.fill_rect(x, y, width, height, (1.0, 1.0, 1.0), False) 630 self.fill_rect(x, y, width, height, (1.0, 1.0, 1.0), False)
556 631
557 def get_item_color(self, n): 632 def get_item_color(self, n):
558 """Gets the nth color in the item color list, which are the colors used to draw the items 633 return self.item_clist[n]
559 on the y-axis. Note that there are conceptually infinitely 634
560 many patterns because the patterns repeat -- that is, we just mod out by the size of the pattern
561 list when indexing."""
562 return self.item_clist[n % len(self.item_clist)]
563
564 def get_bar_pattern(self, n): 635 def get_bar_pattern(self, n):
565 """Gets the nth pattern in the bar pattern list, which is a list of surfaces that are used to 636 return self.bar_plist[n]
566 fill in the bars. Note that there are conceptually infinitely 637
567 many patterns because the patterns repeat -- that is, we just mod out by the size of the pattern 638 def set_item_color(self, n, color):
568 list when indexing.""" 639 self.item_clist[n] = color
569 if n < 0: 640
570 return self.bar_plist[-1] 641 def set_bar_pattern(self, n, pattern):
571 return self.bar_plist[n % (len(self.bar_plist) - 1)] 642 self.bar_plist[n] = pattern
572 643
573class CairoCanvas(Canvas): 644class CairoCanvas(Canvas):
574 """This is a basic class that stores and draws on a Cairo surface, 645 """This is a basic class that stores and draws on a Cairo surface,
575 using various primitives related to drawing a real-time graph (up-arrows, 646 using various primitives related to drawing a real-time graph (up-arrows,
@@ -707,41 +778,45 @@ class CairoCanvas(Canvas):
707 self._polyline_common(coor_list, color, thickness, do_snap) 778 self._polyline_common(coor_list, color, thickness, do_snap)
708 self.surface.ctx.fill() 779 self.surface.ctx.fill()
709 780
710 def _draw_label_common(self, text, x, y, fopts, x_bearing_factor, \ 781 def _get_label_dim_common(self, text, x, y, fopts, x_bearing_factor, \
711 f_descent_factor, width_factor, f_height_factor, do_snap=True): 782 f_descent_factor, width_factor, f_height_factor,
783 scale, do_snap=True):
712 """Helper function for drawing a label with some alignment. Instead of taking in an alignment, 784 """Helper function for drawing a label with some alignment. Instead of taking in an alignment,
713 it takes in the scale factor for the font extent parameters, which give the raw data of how much to adjust 785 it takes in the scale factor for the font extent parameters, which give the raw data of how much to adjust
714 the x and y parameters. Only should be used internally.""" 786 the x and y parameters. Only should be used internally."""
715 x, y = self.scaled(x, y) 787 surface = None
716 x, y = self.surface.get_real_coor(x, y) 788 if self.surface.ctx is None:
717 789 # surface lacks context of its own right now, so use temporary surface
718 self.surface.ctx.set_source_rgb(0.0, 0.0, 0.0) 790 # FIXME: possible bug here; the fonts are distorted (though they are invisible
719 791 # so it doesn't show up for us)
720 self.surface.ctx.select_font_face(fopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) 792 surface = self.surface.__class__()
721 self.surface.ctx.set_font_size(fopts.size * self.scale) 793 surface.renew(10, 10)
794 else:
795 surface = self.surface
796
797 surface.ctx.select_font_face(fopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
798 surface.ctx.set_font_size(fopts.size * scale)
722 799
723 fe = self.surface.ctx.font_extents() 800 fe = surface.ctx.font_extents()
724 f_ascent, f_descent, f_height = fe[:3] 801 f_ascent, f_descent, f_height = fe[:3]
725 802
726 te = self.surface.ctx.text_extents(text) 803 te = surface.ctx.text_extents(text)
727 x_bearing, y_bearing, width, height = te[:4] 804 x_bearing, y_bearing, width, height = te[:4]
728 805
729 actual_x = x - x_bearing * x_bearing_factor - width * width_factor 806 actual_x = x - x_bearing * x_bearing_factor - width * width_factor
730 actual_y = y - f_descent * f_descent_factor + f_height * f_height_factor 807 actual_y = y - f_descent * f_descent_factor + f_height * f_height_factor
731 808
732 self.surface.ctx.set_source_rgb(fopts.color[0], fopts.color[1], fopts.color[2])
733
734 if do_snap: 809 if do_snap:
735 self.surface.ctx.move_to(snap(actual_x), snap(actual_y)) 810 actual_x = snap(actual_x)
736 else: 811 actual_y = snap(actual_y)
737 self.surface.ctx.move_to(actual_x, actual_y) 812
738 813 return actual_x, actual_y, width, height, f_height
739 self.surface.ctx.show_text(text) 814
740 815 def get_label_dim(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT,
741 def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT, valign=AlignMode.BOTTOM, do_snap=True): 816 valign=AlignMode.BOTTOM, do_scale=True, do_snap=True):
742 """Draws a label with the given parameters, with the given horizontal and vertical justification. One can override 817 """Gets the dimensions of the label if it were to be drawn with the given parameters, and with the given
743 the color from ``fopts'' by passing something in to ``pattern'', which overrides the color with an arbitrary 818 justification. Note: this does everything except actually draw the label"""
744 pattern.""" 819
745 x_bearing_factor, f_descent_factor, width_factor, f_height_factor = 0.0, 0.0, 0.0, 0.0 820 x_bearing_factor, f_descent_factor, width_factor, f_height_factor = 0.0, 0.0, 0.0, 0.0
746 halign_factors = {AlignMode.LEFT : (0.0, 0.0), AlignMode.CENTER : (1.0, 0.5), AlignMode.RIGHT : (1.0, 1.0)} 821 halign_factors = {AlignMode.LEFT : (0.0, 0.0), AlignMode.CENTER : (1.0, 0.5), AlignMode.RIGHT : (1.0, 1.0)}
747 if halign not in halign_factors: 822 if halign not in halign_factors:
@@ -752,9 +827,25 @@ class CairoCanvas(Canvas):
752 if valign not in valign_factors: 827 if valign not in valign_factors:
753 raise ValueError('Invalid alignment value') 828 raise ValueError('Invalid alignment value')
754 f_descent_factor, f_height_factor = valign_factors[valign] 829 f_descent_factor, f_height_factor = valign_factors[valign]
755 830
756 self._draw_label_common(text, x, y, fopts, x_bearing_factor, \ 831 scale = 1.0
757 f_descent_factor, width_factor, f_height_factor, do_snap) 832 if do_scale:
833 scale = self.scale
834
835 return self._get_label_dim_common(text, x, y, fopts, x_bearing_factor,
836 f_descent_factor, width_factor, f_height_factor,
837 scale, do_snap)
838
839 def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT, valign=AlignMode.BOTTOM, do_snap=True):
840 """Draws a label with the given parameters, with the given horizontal and vertical justification."""
841
842 x, y = self.scaled(x, y)
843 actual_x, actual_y, width, height, f_height = self.get_label_dim(text, x, y, fopts, halign, valign, True, do_snap)
844 actual_x, actual_y = self.surface.get_real_coor(actual_x, actual_y)
845
846 self.surface.ctx.set_source_rgb(fopts.color[0], fopts.color[1], fopts.color[2])
847 self.surface.ctx.move_to(actual_x, actual_y)
848 self.surface.ctx.show_text(text)
758 849
759 def draw_label_with_sscripts(self, text, supscript, subscript, x, y, \ 850 def draw_label_with_sscripts(self, text, supscript, subscript, x, y, \
760 textfopts=GraphFormat.DEF_FOPTS_LABEL, sscriptfopts=GraphFormat.DEF_FOPTS_LABEL_SSCRIPT, \ 851 textfopts=GraphFormat.DEF_FOPTS_LABEL, sscriptfopts=GraphFormat.DEF_FOPTS_LABEL_SSCRIPT, \
@@ -782,9 +873,20 @@ class CairoCanvas(Canvas):
782 ytmp = y + f_height / 4.0 873 ytmp = y + f_height / 4.0
783 self.draw_label(subscript, xtmp, ytmp, sscriptfopts, halign, valign, do_snap) 874 self.draw_label(subscript, xtmp, ytmp, sscriptfopts, halign, valign, do_snap)
784 875
876def snap(pos):
877 """Takes in an x- or y-coordinate ``pos'' and snaps it to the pixel grid.
878 This is necessary because integer coordinates in Cairo actually denote
879 the spaces between pixels, not the pixels themselves, so if we draw a
880 line of width 1 on integer coordinates, it will come out blurry unless we shift it,
881 since the line will get distributed over two pixels. We actually apply this to all
882 coordinates to make sure everything is aligned."""
883 return pos - 0.5
884
785# represents a selectable region of the graph 885# represents a selectable region of the graph
786class SelectableRegion(object): 886class SelectableRegion(object):
787 def __init__(self, x, y, width, height, event): 887 def __init__(self, x, y, width, height, event):
888 """Sets up a region with the given dimensions and containing a
889 certain event."""
788 self.x = x 890 self.x = x
789 self.y = y 891 self.y = y
790 self.width = width 892 self.width = width
@@ -793,15 +895,23 @@ class SelectableRegion(object):
793 self.scale = 1.0 895 self.scale = 1.0
794 896
795 def get_dimensions(self): 897 def get_dimensions(self):
898 """Gets the dimensions of the region."""
796 return (self.x, self.y, self.width, self.height) 899 return (self.x, self.y, self.width, self.height)
797 900
798 def get_event(self): 901 def get_event(self):
902 """Gets the event associated with the region."""
799 return self.event 903 return self.event
800 904
801 def set_scale(self, scale): 905 def set_scale(self, scale):
802 self.scale = scale 906 self.scale = scale
803 907
908 def is_dummy(self):
909 return self.x is None or self.y is None
910
804 def intersects(self, x, y, width, height): 911 def intersects(self, x, y, width, height):
912 if self.is_dummy():
913 return False
914
805 return x <= (self.x + self.width) * self.scale \ 915 return x <= (self.x + self.width) * self.scale \
806 and x + width >= self.x * self.scale \ 916 and x + width >= self.x * self.scale \
807 and y <= (self.y + self.height) * self.scale \ 917 and y <= (self.y + self.height) * self.scale \
diff --git a/unit_trace/viz/format.py b/unit_trace/viz/format.py
index d3ed4fd..18dfd5a 100644
--- a/unit_trace/viz/format.py
+++ b/unit_trace/viz/format.py
@@ -37,6 +37,8 @@ class GraphFormat(object):
37 X_AXIS_MEASURE_OFS = 30 37 X_AXIS_MEASURE_OFS = 30
38 X_AXIS_LABEL_GAP = 10 38 X_AXIS_LABEL_GAP = 10
39 Y_AXIS_ITEM_GAP = 10 39 Y_AXIS_ITEM_GAP = 10
40 TOP_ITEM_VERT_GAP = 20
41 TOP_ITEM_HORIZ_GAP = 20
40 MAJ_TICK_SIZE = 20 42 MAJ_TICK_SIZE = 20
41 MIN_TICK_SIZE = 12 43 MIN_TICK_SIZE = 12
42 44
diff --git a/unit_trace/viz/graph.py b/unit_trace/viz/graph.py
index a10d9bd..fa0df99 100644
--- a/unit_trace/viz/graph.py
+++ b/unit_trace/viz/graph.py
@@ -14,7 +14,7 @@ class Graph(object):
14 DEF_ITEM_CLIST = [(0.3, 0.0, 0.0), (0.0, 0.3, 0.0), (0.0, 0.0, 0.3), (0.3, 0.3, 0.0), (0.0, 0.3, 0.3), 14 DEF_ITEM_CLIST = [(0.3, 0.0, 0.0), (0.0, 0.3, 0.0), (0.0, 0.0, 0.3), (0.3, 0.3, 0.0), (0.0, 0.3, 0.3),
15 (0.3, 0.0, 0.3)] 15 (0.3, 0.0, 0.3)]
16 16
17 def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, attrs=GraphFormat(), 17 def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, top_item_list, attrs=GraphFormat(),
18 item_clist=DEF_ITEM_CLIST, bar_plist=DEF_BAR_PLIST): 18 item_clist=DEF_ITEM_CLIST, bar_plist=DEF_BAR_PLIST):
19 # deal with possibly blank schedules 19 # deal with possibly blank schedules
20 if start_time is None: 20 if start_time is None:
@@ -29,6 +29,7 @@ class Graph(object):
29 self.start_time = start_time 29 self.start_time = start_time
30 self.end_time = end_time 30 self.end_time = end_time
31 self.y_item_list = y_item_list 31 self.y_item_list = y_item_list
32 self.top_item_list = top_item_list
32 self.num_maj = int(math.ceil((self.end_time - self.start_time) * 1.0 / self.attrs.time_per_maj)) + 1 33 self.num_maj = int(math.ceil((self.end_time - self.start_time) * 1.0 / self.attrs.time_per_maj)) + 1
33 34
34 width = self.num_maj * self.attrs.maj_sep + GraphFormat.X_AXIS_MEASURE_OFS + GraphFormat.WIDTH_PAD 35 width = self.num_maj * self.attrs.maj_sep + GraphFormat.X_AXIS_MEASURE_OFS + GraphFormat.WIDTH_PAD
@@ -59,11 +60,25 @@ class Graph(object):
59 60
60 #if surface.ctx is None: 61 #if surface.ctx is None:
61 # surface.renew(width, height) 62 # surface.renew(width, height)
62 63
63 self.canvas = CanvasType(width, height, item_clist, bar_plist, surface) 64 # item clist might end before
64 65 full_item_clist = []
65 def get_selected_regions(self, real_x, real_y, width, height): 66 full_bar_plist = []
66 return self.canvas.get_selected_regions(real_x, real_y, width, height) 67
68 for i in range(0, len(self.y_item_list)):
69 full_item_clist.append(item_clist[i % len(item_clist)])
70
71 for i in range(0, len(self.top_item_list)):
72 full_bar_plist.append(bar_plist[i % (len(bar_plist) - 1)])
73
74 # last bar pattern is a ``special pattern'' so put it at
75 # the end (and nowhere else)
76 full_bar_plist.append(bar_plist[-1])
77
78 self.canvas = CanvasType(width, height, full_item_clist, full_bar_plist, surface)
79
80 def get_intersecting_regions(self, real_x, real_y, width, height):
81 return self.canvas.get_intersecting_regions(real_x, real_y, width, height)
67 82
68 def get_width(self): 83 def get_width(self):
69 return self.width 84 return self.width
@@ -86,12 +101,31 @@ class Graph(object):
86 def has_sel_region(self, event): 101 def has_sel_region(self, event):
87 return self.canvas.has_sel_region(event) 102 return self.canvas.has_sel_region(event)
88 103
104 def get_item_color(self, no):
105 return self.canvas.get_item_color(no)
106
107 def get_bar_pattern(self, no):
108 return self.canvas.get_bar_pattern(no)
109
110 def set_bar_pattern(self, no, pattern):
111 self.canvas.set_bar_pattern(no, pattern)
112
89 def update_view(self, x, y, width, height, scale, ctx): 113 def update_view(self, x, y, width, height, scale, ctx):
90 """Proxy into the surface's pan.""" 114 """Updates the view's window location, scale factor, and Cairo context (i.e. where
115 the view currently ``is'')."""
91 self.canvas.surface.pan(x, y, width, height) 116 self.canvas.surface.pan(x, y, width, height)
92 self.canvas.set_scale(scale) 117 self.canvas.set_scale(scale)
93 self.canvas.surface.change_ctx(ctx) 118 self.canvas.surface.change_ctx(ctx)
94 119
120 def set_tmp_surface(self, surface):
121 """Changes to a temporary surface, instead of using the default surface (the
122 surface that writes to the screen."""
123 self.canvas.set_tmp_surface(surface)
124
125 def revert_surface(self):
126 """Changes back to the default surface."""
127 self.canvas.revert_surface()
128
95 def _recomp_min_max(self, start_time, end_time, start_item, end_item): 129 def _recomp_min_max(self, start_time, end_time, start_item, end_item):
96 if self.min_time is None or start_time < self.min_time: 130 if self.min_time is None or start_time < self.min_time:
97 self.min_time = start_time 131 self.min_time = start_time
@@ -158,24 +192,61 @@ class Graph(object):
158 def draw_skeleton(self, start_time, end_time, start_item, end_item): 192 def draw_skeleton(self, start_time, end_time, start_item, end_item):
159 self.draw_grid_at_time(start_time, end_time, start_item, end_item) 193 self.draw_grid_at_time(start_time, end_time, start_item, end_item)
160 self.draw_x_axis_with_labels_at_time(start_time, end_time) 194 self.draw_x_axis_with_labels_at_time(start_time, end_time)
161 self.draw_y_axis_with_labels() 195 self.draw_y_axis()
196
197 def render_all(self, sched):
198 """Renders an entire schedule onto the surface."""
199 self.canvas.whiteout()
200 start, end = sched.get_time_bounds()
201 self.draw_skeleton(start, end, 0, len(self.y_item_list))
202
203 slots = {}
204 all_events = {}
205 sched.get_time_slot_array().get_slots(slots,
206 start, end, 0, len(sched.get_task_list()),
207 self.list_type)
208 for event in sched.get_time_slot_array().get_events(slots, self.list_type,
209 schedule.EVENT_LIST):
210 if event.get_layer() not in all_events:
211 all_events[event.get_layer()] = {}
212 all_events[event.get_layer()][event] = None
213
214 self.render_events(all_events)
162 215
163 def render_surface(self, sched, regions, selectable=False): 216 def get_events_to_render(self, sched, regions, selectable=False):
164 raise NotImplementedError 217 slots = {}
165 218
166 def render_all(self, schedule): 219 self.min_time, self.max_time, self.min_item, self.max_item = None, None, None, None
167 raise NotImplementedError 220 for region in regions:
221 x, y, width, height = region
222 start_time, end_time, start_item, end_item = self.get_offset_params(x, y, width, height)
223 self._recomp_min_max(start_time, end_time, start_item, end_item)
224
225 sched.get_time_slot_array().get_slots(slots,
226 start_time, end_time, start_item, end_item,
227 self.list_type)
228
229 events_to_render = {}
230 for layer in Canvas.LAYERS:
231 events_to_render[layer] = {}
232
233 for event in sched.get_time_slot_array().get_events(slots,
234 self.list_type, schedule.EVENT_LIST):
235 events_to_render[event.get_layer()][event] = None
168 236
169 def get_events_to_render(self, sched, regions, selectable=False): 237 return events_to_render
170 raise NotImplementedError
171 238
172 def render_surface(self, sched, regions, selectable=False): 239 def render_surface(self, sched, regions, selectable=False):
173 if not selectable: 240 if not selectable:
174 self.canvas.whiteout() 241 self.canvas.whiteout()
175 else: 242 else:
176 self.canvas.clear_selectable_regions() 243 self.canvas.clear_selectable_regions()
177 self.render_events(self.get_events_to_render(sched, regions, selectable), selectable) 244 events = self.get_events_to_render(sched, regions, selectable)
178 245 if not selectable:
246 self.draw_skeleton(self.min_time, self.max_time,
247 self.min_item, self.max_item)
248 self.render_events(events, selectable)
249
179 def render_events(self, events, selectable=False): 250 def render_events(self, events, selectable=False):
180 for layer in Canvas.LAYERS: 251 for layer in Canvas.LAYERS:
181 prev_events = {} 252 prev_events = {}
@@ -207,7 +278,7 @@ class Graph(object):
207 self.canvas.draw_grid(self.origin[0], self.origin[1], self._get_y_axis_height(), 278 self.canvas.draw_grid(self.origin[0], self.origin[1], self._get_y_axis_height(),
208 start_tick, end_tick, start_item, end_item, self.attrs.maj_sep, self.attrs.y_item_size, \ 279 start_tick, end_tick, start_item, end_item, self.attrs.maj_sep, self.attrs.y_item_size, \
209 self.attrs.min_per_maj, True) 280 self.attrs.min_per_maj, True)
210 281
211 def draw_x_axis_with_labels_at_time(self, start_time, end_time): 282 def draw_x_axis_with_labels_at_time(self, start_time, end_time):
212 start_tick = max(0, self._get_bottom_tick(start_time)) 283 start_tick = max(0, self._get_bottom_tick(start_time))
213 end_tick = min(self.num_maj - 1, self._get_top_tick(end_time)) 284 end_tick = min(self.num_maj - 1, self._get_top_tick(end_time))
@@ -219,11 +290,51 @@ class Graph(object):
219 self.start_time + start_tick * self.attrs.time_per_maj, 290 self.start_time + start_tick * self.attrs.time_per_maj,
220 self.attrs.time_per_maj, False) 291 self.attrs.time_per_maj, False)
221 292
222 def draw_y_axis_with_labels(self): 293 def draw_y_axis(self):
223 self.canvas.draw_y_axis(self.origin[0], self.origin[1], self._get_y_axis_height()) 294 self.canvas.draw_y_axis(self.origin[0], self.origin[1], self._get_y_axis_height())
224 self.canvas.draw_y_axis_labels(self.origin[0], self.origin[1], self._get_y_axis_height(), \ 295 #self.canvas.draw_y_axis_labels(self.origin[0], self.origin[1], self._get_y_axis_height(), \
225 self.y_item_list, self.attrs.y_item_size) 296 # self.y_item_list, self.attrs.y_item_size)
226 297
298 def draw_y_axis_item(self, item_no, selected):
299 self.canvas.draw_y_axis_item(self.origin[0], self.origin[1], self._get_y_axis_height(),
300 self.y_item_list[item_no], item_no, self.attrs.y_item_size,
301 selected)
302
303 def add_sel_y_axis_item(self, item_no, event):
304 self.canvas.add_sel_y_axis_item(self.origin[0], self.origin[1], self._get_y_axis_height(),
305 self.y_item_list[item_no], item_no, self.attrs.y_item_size,
306 event)
307
308 def draw_top_item(self, item_no, selected):
309 self.canvas.draw_top_item(self.origin[0], self.origin[1], self._get_y_axis_height(),
310 self.top_item_list[item_no], item_no, self.top_item_list[0:item_no],
311 selected)
312
313 def add_sel_top_item(self, item_no, event):
314 self.canvas.add_sel_top_item(self.origin[0], self.origin[1], self._get_y_axis_height(),
315 self.top_item_list[item_no], item_no, self.top_item_list[0:item_no],
316 event)
317
318 def all_one_item_selected(self, selected):
319 """When the user selects some parts of the graph, it might be the case that all of the
320 parts lie under one ``item'' of the graph. In this case, certain operations are likely
321 possible when parts under different items are selected. So this method tells us whether
322 a set of selected items satisfies this criterion. It also gives the item selected if
323 so."""
324 raise NotImplementedError
325
326 def draw_cpu_item(self, item_no, selected):
327 raise NotImplementedError
328
329 def add_sel_cpu_item(self, item_no, event):
330 raise NotImplementedError
331
332 def draw_task_item(self, item_no, selected):
333 raise NotImplementedError
334
335 def add_sel_task_item(self, item_no, event):
336 raise NotImplementedError
337
227 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): 338 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
228 """Draws a suspension symbol for a dcertain task at an instant in time.""" 339 """Draws a suspension symbol for a dcertain task at an instant in time."""
229 raise NotImplementedError 340 raise NotImplementedError
@@ -288,35 +399,36 @@ class Graph(object):
288 raise NotImplementedError 399 raise NotImplementedError
289 400
290class TaskGraph(Graph): 401class TaskGraph(Graph):
291 def get_events_to_render(self, sched, regions, selectable=False): 402 def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, top_item_list, attrs=GraphFormat(),
292 slots = {} 403 item_clist=Graph.DEF_ITEM_CLIST, bar_plist=Graph.DEF_BAR_PLIST):
293 404 super(TaskGraph, self).__init__(CanvasType, surface, start_time, end_time, y_item_list, top_item_list,
294 self.min_time, self.max_time, self.min_item, self.max_item = None, None, None, None 405 attrs, item_clist, bar_plist)
295 for region in regions: 406 self.list_type = schedule.TimeSlotArray.TASK_LIST
296 x, y, width, height = region 407
297 start_time, end_time, start_item, end_item = self.get_offset_params(x, y, width, height) 408 def all_one_item_selected(self, selected):
298 self._recomp_min_max(start_time, end_time, start_item, end_item) 409 cpu = None
299 410 for layer in selected:
300 sched.get_time_slot_array().get_slots(slots, 411 for event in selected[layer]:
301 start_time, end_time, start_item, end_item, 412 if cpu is None:
302 schedule.TimeSlotArray.TASK_LIST) 413 cpu = event.get_cpu()
303 414 else:
304 415 if event.get_cpu() is None or event.get_cpu() != cpu:
305 if not selectable: 416 return None
306 self.draw_skeleton(self.min_time, self.max_time, 417
307 self.min_item, self.max_item) 418 return cpu
308 419
309 events_to_render = {} 420 def draw_cpu_item(self, item_no, selected):
310 for layer in Canvas.LAYERS: 421 self.draw_top_item(item_no, selected)
311 events_to_render[layer] = {} 422
312 423 def add_sel_cpu_item(self, item_no, event):
313 for event in sched.get_time_slot_array().get_events(slots, 424 self.add_sel_top_item(item_no, event)
314 schedule.TimeSlotArray.TASK_LIST, 425
315 schedule.EVENT_LIST): 426 def draw_task_item(self, item_no, selected):
316 events_to_render[event.get_layer()][event] = None 427 self.draw_y_axis_item(item_no, selected)
317 428
318 return events_to_render 429 def add_sel_task_item(self, item_no, event):
319 430 self.add_sel_y_axis_item(item_no, event)
431
320 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): 432 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
321 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR 433 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
322 x = self.get_time_xpos(time) 434 x = self.get_time_xpos(time)
@@ -446,71 +558,36 @@ class TaskGraph(Graph):
446 self.canvas.add_sel_mini_bar(x, y, width, height, event) 558 self.canvas.add_sel_mini_bar(x, y, width, height, event)
447 559
448class CpuGraph(Graph): 560class CpuGraph(Graph):
449 def get_events_to_render(self, sched, regions, selectable=False): 561 def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, top_item_list, attrs=GraphFormat(),
450 BOTTOM_EVENTS = [schedule.ReleaseEvent, schedule.DeadlineEvent, schedule.InversionStartEvent, 562 item_clist=Graph.DEF_ITEM_CLIST, bar_plist=Graph.DEF_BAR_PLIST):
451 schedule.InversionEndEvent, schedule.InversionDummy] 563 super(CpuGraph, self).__init__(CanvasType, surface, start_time, end_time, y_item_list, top_item_list,
452 TOP_EVENTS = [schedule.SuspendEvent, schedule.ResumeEvent, schedule.CompleteEvent, 564 attrs, item_clist, bar_plist)
453 schedule.SwitchAwayEvent, schedule.SwitchToEvent, schedule.IsRunningDummy] 565 self.list_type = schedule.TimeSlotArray.CPU_LIST
454 566
455 if not regions: 567 def all_one_item_selected(self, selected):
456 return {} 568 task_no = None
457 569 for layer in selected:
458 top_slots = {} 570 for event in selected[layer]:
459 bottom_slots = {} 571 if task_no is None and event.get_task() is not None:
460 572 task_no = event.get_task().get_task_no()
461 self.min_time, self.max_time, self.min_item, self.max_item = None, None, None, None 573 else:
462 for region in regions: 574 if event.get_task() is None or event.get_task().get_task_no() != task_no:
463 x, y, width, height = region 575 return None
464 start_time, end_time, start_item, end_item = self.get_offset_params(x, y, width, height) 576
465 self._recomp_min_max(start_time, end_time, start_item, end_item) 577 return task_no
466 578
467 sched.get_time_slot_array().get_slots(top_slots, 579 def draw_cpu_item(self, item_no, selected):
468 start_time, end_time, start_item, end_item, 580 self.draw_y_axis_item(item_no, selected)
469 schedule.TimeSlotArray.CPU_LIST) 581
470 582 def add_sel_cpu_item(self, item_no, event):
471 if end_item >= len(self.y_item_list): 583 self.add_sel_y_axis_item(item_no, event)
472 # we are far down enough that we should render the releases and deadlines and inversions, 584
473 # which appear near the x-axis 585 def draw_task_item(self, item_no, selected):
474 sched.get_time_slot_array().get_slots(bottom_slots, 586 self.draw_top_item(item_no, selected)
475 start_time, end_time, 0, sched.get_num_cpus(), 587
476 schedule.TimeSlotArray.CPU_LIST) 588 def add_sel_task_item(self, item_no, event):
477 589 self.add_sel_top_item(item_no, event)
478 if not selectable: 590
479 self.draw_skeleton(self.min_time, self.max_time,
480 self.min_item, self.max_item)
481
482 events_to_render = {}
483 for layer in Canvas.LAYERS:
484 events_to_render[layer] = {}
485
486 for event in sched.get_time_slot_array().get_events(top_slots,
487 schedule.TimeSlotArray.CPU_LIST,
488 TOP_EVENTS):
489 events_to_render[event.get_layer()][event] = None
490 for event in sched.get_time_slot_array().get_events(bottom_slots,
491 schedule.TimeSlotArray.CPU_LIST,
492 BOTTOM_EVENTS):
493 events_to_render[event.get_layer()][event] = None
494
495 return events_to_render
496
497 def render(self, schedule, start_time=None, end_time=None):
498 if end_time < start_time:
499 raise ValueError('start must be less than end')
500
501 if start_time is None:
502 start_time = self.start
503 if end_time is None:
504 end_time = self.end
505 start_slot = self.get_time_slot(start_time)
506 end_slot = min(len(self.time_slots), self.get_time_slot(end_time) + 1)
507
508 for layer in Canvas.LAYERS:
509 prev_events = {}
510 for i in range(start_slot, end_slot):
511 for event in self.time_slots[i]:
512 event.render(graph, layer, prev_events)
513
514 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): 591 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
515 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR 592 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
516 x = self.get_time_xpos(time) 593 x = self.get_time_xpos(time)
diff --git a/unit_trace/viz/renderer.py b/unit_trace/viz/renderer.py
index 056e1a5..1970b8a 100644
--- a/unit_trace/viz/renderer.py
+++ b/unit_trace/viz/renderer.py
@@ -12,13 +12,15 @@ class Renderer(object):
12 def prepare_task_graph(self, SurfaceType=ImageSurface, attrs=GraphFormat()): 12 def prepare_task_graph(self, SurfaceType=ImageSurface, attrs=GraphFormat()):
13 """Outputs the fully-rendered graph (y-axis = tasks) to a Cairo ImageSurface""" 13 """Outputs the fully-rendered graph (y-axis = tasks) to a Cairo ImageSurface"""
14 item_list = self.get_task_item_list() 14 item_list = self.get_task_item_list()
15 top_list = ['CPU %d' % i for i in range(0, self.schedule.get_num_cpus())]
15 start, end = self.schedule.get_time_bounds() 16 start, end = self.schedule.get_time_bounds()
16 self.graph = TaskGraph(CairoCanvas, SurfaceType(), start, end, item_list, attrs) 17 self.graph = TaskGraph(CairoCanvas, SurfaceType(), start, end, item_list, top_list, attrs)
17 18
18 def prepare_cpu_graph(self, SurfaceType=ImageSurface, attrs=GraphFormat()): 19 def prepare_cpu_graph(self, SurfaceType=ImageSurface, attrs=GraphFormat()):
19 item_list = ['CPU %d' % i for i in range(0, self.schedule.get_num_cpus())] 20 item_list = ['CPU %d' % i for i in range(0, self.schedule.get_num_cpus())]
21 top_list = self.get_task_item_list()
20 start, end = self.schedule.get_time_bounds() 22 start, end = self.schedule.get_time_bounds()
21 self.graph = CpuGraph(CairoCanvas, SurfaceType(), start, end, item_list, attrs) 23 self.graph = CpuGraph(CairoCanvas, SurfaceType(), start, end, item_list, top_list, attrs)
22 24
23 def render_graph_full(self): 25 def render_graph_full(self):
24 """Does the heavy lifting for rendering a task or CPU graph, by scanning the schedule 26 """Does the heavy lifting for rendering a task or CPU graph, by scanning the schedule
diff --git a/unit_trace/viz/schedule.py b/unit_trace/viz/schedule.py
index a44ce8d..82b7ea1 100644
--- a/unit_trace/viz/schedule.py
+++ b/unit_trace/viz/schedule.py
@@ -21,82 +21,106 @@ class TimeSlotArray(object):
21 21
22 TASK_LIST = 0 22 TASK_LIST = 0
23 CPU_LIST = 1 23 CPU_LIST = 1
24 24 LIST_TYPES = (TASK_LIST, CPU_LIST)
25
26 PRE_ITEM_NO = -1
27 POST_ITEM_NO = -2
28
29 PRE_SLOT = 'pre'
30 POST_SLOT = 'post'
31 ALL_SLOT = 'all'
32
25 def __init__(self, time_per_maj=None, num_tasks=0, num_cpus=0): 33 def __init__(self, time_per_maj=None, num_tasks=0, num_cpus=0):
26 if time_per_maj is None: 34 if time_per_maj is None:
27 self.array = None 35 self.array = None
28 return 36 return
29 37
30 self.time_per_maj = time_per_maj 38 self.time_per_maj = time_per_maj
31 self.list_sizes = { TimeSlotArray.TASK_LIST : num_tasks, TimeSlotArray.CPU_LIST : num_cpus } 39 self.list_sizes = { TimeSlotArray.TASK_LIST : num_tasks, TimeSlotArray.CPU_LIST : num_cpus}
32 self.array = {} 40 self.array = {}
33 41 self.min_slot = None
42 self.max_slot = None
43
34 for type in self.list_sizes: 44 for type in self.list_sizes:
35 num = self.list_sizes[type] 45 num = self.list_sizes[type]
36 self.array[type] = [] 46 self.array[type] = []
37 for j in range(0, num): 47 for i in range(0, num + 2): # two more for pre and post
38 # for each slot in the array, we need a list of all events under this type 48 # for each slot in the array, we need a list of all events under this type
39 # (for example, a list of all events that occur in this time slot, indexed 49 # (for example, a list of all events that occur in this time slot, indexed
40 # by task). 50 # by task).
41 self.array[type].append(dict(zip(EVENT_LIST, \ 51 self.array[type].append(dict(zip(EVENT_LIST,
42 [{} for j in range(0, len(EVENT_LIST))]))) 52 [{} for j in range(0, len(EVENT_LIST))])))
43 53
44 def get_time_slot(self, time): 54 def get_time_slot(self, time):
45 return int(time // self.time_per_maj) 55 return int(time // self.time_per_maj)
46 56
47 def _put_event_in_slot(self, list_type, no, klass, slot, event): 57 def put_event_in_slot(self, list_type, no, klass, slot, event):
48 if slot not in self.array[list_type][no][klass]: 58 if slot not in self.array[list_type][no][klass]:
49 self.array[list_type][no][klass][slot] = [] 59 self.array[list_type][no][klass][slot] = []
50 self.array[list_type][no][klass][slot].append(event) 60 self.array[list_type][no][klass][slot].append(event)
61
62 if self.min_slot is None or slot < self.min_slot:
63 self.min_slot = slot
64 if self.max_slot is None or slot > self.max_slot:
65 self.max_slot = slot
51 66
52 def add_event_to_time_slot(self, event): 67 def add_event_to_time_slot(self, event, item_nos):
53 task_no = event.get_job().get_task().get_task_no() 68 task_no = event.get_job().get_task().get_task_no()
54 cpu = event.get_cpu() 69 cpu = event.get_cpu()
55 time_slot = self.get_time_slot(event.get_time())
56 70
57 self._put_event_in_slot(TimeSlotArray.TASK_LIST, task_no, event.__class__, time_slot, event) 71 slot = self.get_time_slot(event.get_time())
58 self._put_event_in_slot(TimeSlotArray.CPU_LIST, cpu, event.__class__, time_slot, event) 72
73 for type in item_nos:
74 self.put_event_in_slot(type, item_nos[type], event.__class__, slot, event)
75
76 def fill_span_event_from_start(self, event, item_nos):
77 end_slot = None
78 if event.corresp_end_event is None:
79 end_slot = self.get_time_slot(event.get_schedule().end) + 1
80 else:
81 end_slot = self.get_time_slot(event.corresp_end_event.get_time())
82 start_slot = self.get_time_slot(event.get_time())
59 83
60 if event.__class__ in SPAN_END_EVENTS: 84 for slot in range(start_slot + 1, end_slot):
61 self.fill_span_event_from_end(event) 85 dummy = event.dummy_class()
86 dummy.corresp_start_event = event
87 dummy.corresp_end_event = event.corresp_end_event
88
89 for type in item_nos:
90 self.put_event_in_slot(type, item_nos[type], event.dummy_class, slot, dummy)
62 91
63 def fill_span_event_from_end(self, event): 92 def fill_span_event_from_end(self, event, item_nos):
64 start_slot = None 93 start_slot = None
65 if event.corresp_start_event is None: 94 if event.corresp_start_event is None:
66 start_slot = self.get_time_slot(event.get_job().get_task().get_schedule().start) - 1 95 start_slot = self.get_time_slot(event.get_schedule().start) - 1
67 else: 96 else:
68 start_slot = self.get_time_slot(event.corresp_start_event.get_time()) 97 start_slot = self.get_time_slot(event.corresp_start_event.get_time())
69 end_slot = self.get_time_slot(event.get_time()) 98 end_slot = self.get_time_slot(event.get_time())
70 99
71 for slot in range(start_slot + 1, end_slot): 100 for slot in range(start_slot + 1, end_slot):
72 task_no = event.get_job().get_task().get_task_no() 101 dummy = event.dummy_class()
73 cpu = event.get_cpu()
74
75 dummy = SPAN_END_EVENTS[event.__class__](task_no, cpu)
76 dummy.corresp_start_event = event.corresp_start_event 102 dummy.corresp_start_event = event.corresp_start_event
77 dummy.corresp_end_event = event 103 dummy.corresp_end_event = event
78 104
79 self._put_event_in_slot(TimeSlotArray.TASK_LIST, task_no, dummy.__class__, slot, dummy) 105 for type in item_nos:
80 self._put_event_in_slot(TimeSlotArray.CPU_LIST, cpu, dummy.__class__, slot, dummy) 106 self.put_event_in_slot(type, item_nos[type], event.dummy_class, slot, dummy)
81 107
82 def fill_span_event_from_start(self, event): 108 def add_item_dummies(self, sched):
83 end_slot = None 109 start = sched.get_time_bounds()[0]
84 if event.corresp_end_event is None: 110
85 end_slot = self.get_time_slot(event.get_job().get_task().get_schedule().end) + 1 111 for task in sched.get_tasks().values():
86 else: 112 dummy = TaskDummy(start, task)
87 end_slot = self.get_time_slot(event.corresp_end_event.get_time()) 113 self.put_event_in_slot(TimeSlotArray.TASK_LIST, task.get_task_no(), dummy.__class__,
88 start_slot = self.get_time_slot(event.get_time()) 114 TimeSlotArray.PRE_SLOT, dummy)
89 115 self.put_event_in_slot(TimeSlotArray.CPU_LIST, TimeSlotArray.PRE_ITEM_NO, dummy.__class__,
90 for slot in range(start_slot + 1, end_slot): 116 TimeSlotArray.ALL_SLOT, dummy)
91 task_no = event.get_job().get_task().get_task_no() 117
92 cpu = event.get_cpu() 118 for i in range(0, self.list_sizes[TimeSlotArray.CPU_LIST]):
93 119 dummy = CPUDummy(start, sched, i)
94 dummy = SPAN_START_EVENTS[event.__class__](task_no, cpu) 120 self.put_event_in_slot(TimeSlotArray.CPU_LIST, i, dummy.__class__,
95 dummy.corresp_start_event = event 121 TimeSlotArray.PRE_SLOT, dummy)
96 dummy.corresp_end_event = event.corresp_end_event 122 self.put_event_in_slot(TimeSlotArray.TASK_LIST, TimeSlotArray.PRE_ITEM_NO, dummy.__class__,
97 123 TimeSlotArray.ALL_SLOT, dummy)
98 self._put_event_in_slot(TimeSlotArray.TASK_LIST, task_no, dummy.__class__, slot, dummy)
99 self._put_event_in_slot(TimeSlotArray.CPU_LIST, cpu, dummy.__class__, slot, dummy)
100 124
101 def get_events(self, slots, list_type, event_types): 125 def get_events(self, slots, list_type, event_types):
102 for type in event_types: 126 for type in event_types:
@@ -107,25 +131,44 @@ class TimeSlotArray(object):
107 yield event 131 yield event
108 132
109 def get_slots(self, slots, start, end, start_no, end_no, list_type): 133 def get_slots(self, slots, start, end, start_no, end_no, list_type):
110 if self.array is None:
111 return # empty schedule
112
113 if start > end: 134 if start > end:
114 raise ValueError('Litmus is not a time machine') 135 raise ValueError('Litmus is not a time machine')
115 if start_no > end_no: 136 if start_no > end_no:
116 raise ValueError('start no should be less than end no') 137 raise ValueError('start no should be less than end no')
117 138
139 if self.min_slot is None:
140 # no events ever got added
141 return {}
142
118 start_slot = self.get_time_slot(start) 143 start_slot = self.get_time_slot(start)
119 end_slot = self.get_time_slot(end) + 1 144 end_slot = self.get_time_slot(end) + 1
145
146 item_no_list = []
147 if start_no < 0:
148 item_no_list.append(TimeSlotArray.PRE_ITEM_NO)
149 if end_no >= self.list_sizes[list_type]:
150 item_no_list.append(TimeSlotArray.POST_ITEM_NO)
120 start_no = max(0, start_no) 151 start_no = max(0, start_no)
121 end_no = min(self.list_sizes[list_type] - 1, end_no) 152 end_no = min(self.list_sizes[list_type] - 1, end_no)
122 153 item_no_list += range(start_no, end_no + 1)
154
155 edge_slots = [TimeSlotArray.ALL_SLOT]
156 if start_slot <= self.min_slot:
157 edge_slots.append(TimeSlotArray.PRE_SLOT)
158 if end_slot >= self.max_slot:
159 edge_slots.append(TimeSlotArray.POST_SLOT)
160
161 for slot in edge_slots:
162 slots[slot] = {}
163 for no in item_no_list:
164 slots[slot][no] = None
165
123 for slot in xrange(start_slot, end_slot + 1): 166 for slot in xrange(start_slot, end_slot + 1):
124 if slot not in slots: 167 if slot not in slots:
125 slots[slot] = {} 168 slots[slot] = {}
126 for no in xrange(start_no, end_no + 1): 169 for no in item_no_list:
127 slots[slot][no] = None 170 slots[slot][no] = None
128 171
129class Schedule(object): 172class Schedule(object):
130 """The total schedule (task system), consisting of a certain number of 173 """The total schedule (task system), consisting of a certain number of
131 tasks.""" 174 tasks."""
@@ -203,11 +246,14 @@ class Schedule(object):
203 # the range of whatever we read in. So we need to fill dummies starting from the initial 246 # the range of whatever we read in. So we need to fill dummies starting from the initial
204 # event all the way to the end of the graph, so that the renderer can see the event no matter 247 # event all the way to the end of the graph, so that the renderer can see the event no matter
205 # how far the user scrolls to the right. 248 # how far the user scrolls to the right.
206 for span_event in SPAN_START_EVENTS: 249 for event in EVENT_LIST:
207 event = switches[span_event] 250 switch_event = switches[event]
208 if event is not None: 251 if switch_event is not None:
209 self.time_slot_array.fill_span_event_from_start(event) 252 event.fill_span_event_from_start()
210 253
254 # add events that correspond to the tasks and CPUS, at the very beginning
255 self.time_slot_array.add_item_dummies(self)
256
211 def add_task(self, task): 257 def add_task(self, task):
212 if task.name in self.tasks: 258 if task.name in self.tasks:
213 raise ValueError("task already in list!") 259 raise ValueError("task already in list!")
@@ -311,12 +357,23 @@ class DummyEvent(object):
311 def get_cpu(self): 357 def get_cpu(self):
312 return self.cpu 358 return self.cpu
313 359
360 def get_schedule(self):
361 return self.get_task().get_schedule()
362
363 def get_task(self):
364 return self.get_job().get_task()
365
314 def get_job(self): 366 def get_job(self):
315 return self.job 367 return self.job
316 368
317 def get_layer(self): 369 def get_layer(self):
318 return self.layer 370 return self.layer
319 371
372 def is_selected(self):
373 """Returns whether the event has been selected by the user. (needed for rendering)"""
374 selected = self.get_schedule().get_selected()
375 return self.get_layer() in selected and self in selected[self.get_layer()]
376
320 def render(self, graph, layer, prev_events, selectable=False): 377 def render(self, graph, layer, prev_events, selectable=False):
321 """Method that the visualizer calls to tell the event to render itself 378 """Method that the visualizer calls to tell the event to render itself
322 Obviously only implemented by subclasses (actual event types) 379 Obviously only implemented by subclasses (actual event types)
@@ -363,12 +420,7 @@ class Event(DummyEvent):
363 us how we should render the event.""" 420 us how we should render the event."""
364 return self.erroneous 421 return self.erroneous
365 422
366 def is_selected(self): 423 def scan(self, cur_cpu, switches, item_nos=None):
367 """Returns whether the event has been selected by the user. (needed for rendering)"""
368 selected = self.get_job().get_task().get_schedule().get_selected()
369 return self.get_layer() in selected and self in selected[self.get_layer()]
370
371 def scan(self, cur_cpu, switches):
372 """Part of the procedure that walks through all the events and sets 424 """Part of the procedure that walks through all the events and sets
373 some parameters that are unknown at first. For instance, a SwitchAwayEvent 425 some parameters that are unknown at first. For instance, a SwitchAwayEvent
374 should know when the previous corresponding SwitchToEvent occurred, but 426 should know when the previous corresponding SwitchToEvent occurred, but
@@ -377,17 +429,86 @@ class Event(DummyEvent):
377 time in the scan, and ``switches'' gives the last time a certain switch 429 time in the scan, and ``switches'' gives the last time a certain switch
378 (e.g. SwitchToEvent, InversionStartEvent) occurred""" 430 (e.g. SwitchToEvent, InversionStartEvent) occurred"""
379 time = self.get_time() 431 time = self.get_time()
380 sched = self.get_job().get_task().get_schedule() 432 sched = self.get_schedule()
381 if sched.start is None or time < sched.start: 433 if sched.start is None or time < sched.start:
382 sched.start = time 434 sched.start = time
383 if sched.end is None or time > sched.end: 435 if sched.end is None or time > sched.end:
384 sched.end = time 436 sched.end = time
385 437
386 sched.get_time_slot_array().add_event_to_time_slot(self) 438 if item_nos is None:
387 439 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
388class ErrorEvent(Event): 440 TimeSlotArray.CPU_LIST : self.get_cpu() }
389 pass 441 sched.get_time_slot_array().add_event_to_time_slot(self, item_nos)
390 442 self.fill_span_event_from_end()
443
444 def fill_span_event_from_start(self):
445 """This method exists for events that can ``range'' over a period of time
446 (e.g. SwitchAway and SwitchTo). In case a start event is not paired with
447 an end event, or vice versa, we want to fill in dummy events to range all
448 the way to the beginning or end. Since most events occur only at a specific
449 time, this is usually a no-op."""
450 pass
451
452 def fill_span_event_from_end(self):
453 """The mirror image of the last method."""
454 pass
455
456class SpanEvent(Event):
457 def __init__(self, time, cpu, dummy_class):
458 super(SpanEvent, self).__init__(time, cpu)
459 self.dummy_class = dummy_class
460
461class SpanDummy(DummyEvent):
462 def __init__(self):
463 super(SpanDummy, self).__init__(None, None)
464
465 def get_task(self):
466 if self.corresp_start_event is not None:
467 return self.corresp_start_event.get_task()
468 if self.corresp_end_event is not None:
469 return self.corresp_end_event.get_task()
470 return None
471
472 def get_schedule(self):
473 if self.corresp_start_event is not None:
474 return self.corresp_start_event.get_schedule()
475 if self.corresp_end_event is not None:
476 return self.corresp_end_event.get_schedule()
477 return None
478
479 def get_job(self):
480 if self.corresp_start_event is not None:
481 return self.corresp_start_event.get_job()
482 if self.corresp_end_event is not None:
483 return self.corresp_end_event.get_job()
484 return None
485
486 def get_cpu(self):
487 if self.corresp_start_event is not None:
488 return self.corresp_start_event.get_cpu()
489 if self.corresp_end_event is not None:
490 return self.corresp_end_event.get_cpu()
491 return None
492
493 def get_time(self):
494 if self.corresp_start_event is not None:
495 return self.corresp_start_event.get_time()
496 if self.corresp_end_event is not None:
497 return self.corresp_end_event.get_time()
498 return None
499
500class StartSpanEvent(SpanEvent):
501 def fill_span_event_from_start(self):
502 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
503 TimeSlotArray.CPU_LIST : self.get_cpu() }
504 self.get_schedule().get_time_slot_array().fill_span_event_from_start(self, item_nos)
505
506class EndSpanEvent(SpanEvent):
507 def fill_span_event_from_end(self):
508 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
509 TimeSlotArray.CPU_LIST : self.get_cpu() }
510 self.get_schedule().get_time_slot_array().fill_span_event_from_end(self, item_nos)
511
391class SuspendEvent(Event): 512class SuspendEvent(Event):
392 def __init__(self, time, cpu): 513 def __init__(self, time, cpu):
393 super(SuspendEvent, self).__init__(time, cpu) 514 super(SuspendEvent, self).__init__(time, cpu)
@@ -459,9 +580,9 @@ class CompleteEvent(Event):
459 graph.draw_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 580 graph.draw_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
460 self.get_cpu(), self.is_selected()) 581 self.get_cpu(), self.is_selected())
461 582
462class SwitchToEvent(Event): 583class SwitchToEvent(StartSpanEvent):
463 def __init__(self, time, cpu): 584 def __init__(self, time, cpu):
464 super(SwitchToEvent, self).__init__(time, cpu) 585 super(SwitchToEvent, self).__init__(time, cpu, IsRunningDummy)
465 self.layer = Canvas.BOTTOM_LAYER 586 self.layer = Canvas.BOTTOM_LAYER
466 self.corresp_end_event = None 587 self.corresp_end_event = None
467 588
@@ -523,9 +644,9 @@ class SwitchToEvent(Event):
523 task_no, cpu, self.get_job().get_job_no(), 644 task_no, cpu, self.get_job().get_job_no(),
524 clip, self.is_selected()) 645 clip, self.is_selected())
525 646
526class SwitchAwayEvent(Event): 647class SwitchAwayEvent(EndSpanEvent):
527 def __init__(self, time, cpu): 648 def __init__(self, time, cpu):
528 super(SwitchAwayEvent, self).__init__(time, cpu) 649 super(SwitchAwayEvent, self).__init__(time, cpu, IsRunningDummy)
529 self.layer = Canvas.BOTTOM_LAYER 650 self.layer = Canvas.BOTTOM_LAYER
530 self.corresp_start_event = None 651 self.corresp_start_event = None
531 652
@@ -600,7 +721,9 @@ class ReleaseEvent(Event):
600 return 'Release' 721 return 'Release'
601 722
602 def scan(self, cur_cpu, switches): 723 def scan(self, cur_cpu, switches):
603 super(ReleaseEvent, self).scan(cur_cpu, switches) 724 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
725 TimeSlotArray.CPU_LIST : TimeSlotArray.POST_ITEM_NO }
726 super(ReleaseEvent, self).scan(cur_cpu, switches, item_nos)
604 727
605 def render(self, graph, layer, prev_events, selectable=False): 728 def render(self, graph, layer, prev_events, selectable=False):
606 prev_events[self] = None 729 prev_events[self] = None
@@ -622,7 +745,9 @@ class DeadlineEvent(Event):
622 return 'Deadline' 745 return 'Deadline'
623 746
624 def scan(self, cur_cpu, switches): 747 def scan(self, cur_cpu, switches):
625 super(DeadlineEvent, self).scan(cur_cpu, switches) 748 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
749 TimeSlotArray.CPU_LIST : TimeSlotArray.POST_ITEM_NO }
750 super(DeadlineEvent, self).scan(cur_cpu, switches, item_nos)
626 751
627 def render(self, graph, layer, prev_events, selectable=False): 752 def render(self, graph, layer, prev_events, selectable=False):
628 prev_events[self] = None 753 prev_events[self] = None
@@ -635,9 +760,9 @@ class DeadlineEvent(Event):
635 self.get_job().get_job_no(), self.is_selected()) 760 self.get_job().get_job_no(), self.is_selected())
636 761
637 762
638class InversionStartEvent(ErrorEvent): 763class InversionStartEvent(StartSpanEvent):
639 def __init__(self, time): 764 def __init__(self, time):
640 super(InversionStartEvent, self).__init__(time, Event.NO_CPU) 765 super(InversionStartEvent, self).__init__(time, Event.NO_CPU, InversionDummy)
641 self.layer = Canvas.BOTTOM_LAYER 766 self.layer = Canvas.BOTTOM_LAYER
642 self.corresp_end_event = None 767 self.corresp_end_event = None
643 768
@@ -665,13 +790,20 @@ class InversionStartEvent(ErrorEvent):
665 '\nCPU: ' + str(self.get_cpu()) + \ 790 '\nCPU: ' + str(self.get_cpu()) + \
666 '\nStart: ' + str(self.get_time()) + \ 791 '\nStart: ' + str(self.get_time()) + \
667 '\nEnd: ' + str(self.corresp_end_event.get_time()) 792 '\nEnd: ' + str(self.corresp_end_event.get_time())
668 793
794 def fill_span_event_from_start(self):
795 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
796 TimeSlotArray.CPU_LIST : TimeSlotArray.POST_ITEM_NO }
797 self.get_schedule().get_time_slot_array().fill_span_event_from_start(self, item_nos)
798
669 def scan(self, cur_cpu, switches): 799 def scan(self, cur_cpu, switches):
670 switches[InversionStartEvent] = self 800 switches[InversionStartEvent] = self
671 self.corresp_end_event = None 801 self.corresp_end_event = None
672 802
673 # the corresp_end_event should already be set 803 # the corresp_end_event should already be set
674 super(InversionStartEvent, self).scan(cur_cpu, switches) 804 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
805 TimeSlotArray.CPU_LIST : TimeSlotArray.POST_ITEM_NO }
806 super(InversionStartEvent, self).scan(cur_cpu, switches, item_nos)
675 807
676 def render(self, graph, layer, prev_events, selectable=False): 808 def render(self, graph, layer, prev_events, selectable=False):
677 if layer == self.layer: 809 if layer == self.layer:
@@ -696,9 +828,9 @@ class InversionStartEvent(ErrorEvent):
696 clip, self.is_selected()) 828 clip, self.is_selected())
697 829
698 830
699class InversionEndEvent(ErrorEvent): 831class InversionEndEvent(EndSpanEvent):
700 def __init__(self, time): 832 def __init__(self, time):
701 super(InversionEndEvent, self).__init__(time, Event.NO_CPU) 833 super(InversionEndEvent, self).__init__(time, Event.NO_CPU, InversionDummy)
702 self.layer = Canvas.BOTTOM_LAYER 834 self.layer = Canvas.BOTTOM_LAYER
703 self.corresp_start_event = None 835 self.corresp_start_event = None
704 836
@@ -720,6 +852,11 @@ class InversionEndEvent(ErrorEvent):
720 852
721 return self.corresp_start_event.str_long() 853 return self.corresp_start_event.str_long()
722 854
855 def fill_span_event_from_end(self):
856 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
857 TimeSlotArray.CPU_LIST : TimeSlotArray.POST_ITEM_NO }
858 self.get_schedule().get_time_slot_array().fill_span_event_from_end(self, item_nos)
859
723 def scan(self, cur_cpu, switches): 860 def scan(self, cur_cpu, switches):
724 self.corresp_start_event = switches[InversionStartEvent] 861 self.corresp_start_event = switches[InversionStartEvent]
725 862
@@ -733,7 +870,9 @@ class InversionEndEvent(ErrorEvent):
733 self.erroneous = True 870 self.erroneous = True
734 #print "inversion end was not matched by a corresponding inversion start" 871 #print "inversion end was not matched by a corresponding inversion start"
735 872
736 super(InversionEndEvent, self).scan(cur_cpu, switches) 873 item_nos = { TimeSlotArray.TASK_LIST : self.get_task().get_task_no(),
874 TimeSlotArray.CPU_LIST : TimeSlotArray.POST_ITEM_NO }
875 super(InversionEndEvent, self).scan(cur_cpu, switches, item_nos)
737 876
738 def render(self, graph, layer, prev_events, selectable=False): 877 def render(self, graph, layer, prev_events, selectable=False):
739 if self.corresp_start_event is None: 878 if self.corresp_start_event is None:
@@ -757,9 +896,9 @@ class InversionEndEvent(ErrorEvent):
757 return # already rendered the bar 896 return # already rendered the bar
758 self.corresp_start_event.render(graph, layer, prev_events, selectable) 897 self.corresp_start_event.render(graph, layer, prev_events, selectable)
759 898
760class InversionDummy(DummyEvent): 899class InversionDummy(SpanDummy):
761 def __init__(self, time, cpu): 900 def __init__(self):
762 super(InversionDummy, self).__init__(time, Event.NO_CPU) 901 super(InversionDummy, self).__init__()
763 self.layer = Canvas.BOTTOM_LAYER 902 self.layer = Canvas.BOTTOM_LAYER
764 903
765 def render(self, graph, layer, prev_events, selectable=False): 904 def render(self, graph, layer, prev_events, selectable=False):
@@ -772,9 +911,9 @@ class InversionDummy(DummyEvent):
772 return # we have already been rendered 911 return # we have already been rendered
773 self.corresp_start_event.render(graph, layer, prev_events, selectable) 912 self.corresp_start_event.render(graph, layer, prev_events, selectable)
774 913
775class IsRunningDummy(DummyEvent): 914class IsRunningDummy(SpanDummy):
776 def __init__(self, time, cpu): 915 def __init__(self):
777 super(IsRunningDummy, self).__init__(time, Event.NO_CPU) 916 super(IsRunningDummy, self).__init__()
778 self.layer = Canvas.BOTTOM_LAYER 917 self.layer = Canvas.BOTTOM_LAYER
779 918
780 def render(self, graph, layer, prev_events, selectable=False): 919 def render(self, graph, layer, prev_events, selectable=False):
@@ -787,11 +926,66 @@ class IsRunningDummy(DummyEvent):
787 return # we have already been rendered 926 return # we have already been rendered
788 self.corresp_start_event.render(graph, layer, prev_events, selectable) 927 self.corresp_start_event.render(graph, layer, prev_events, selectable)
789 928
929class TaskDummy(DummyEvent):
930 """A dummy event that represents an entire task. Used for rendering the task's name."""
931 def __init__(self, time, task):
932 super(TaskDummy, self).__init__(time, None)
933 self.task = task
934 self.layer = Canvas.BOTTOM_LAYER
935
936 def get_name(self):
937 return 'Task'
938
939 def __str__(self):
940 return 'Task ' + self.task.get_name()
941
942 def str_long(self):
943 return 'Task ' + self.task.get_name()
944
945 def get_task(self):
946 return self.task
947
948 def render(self, graph, layer, prev_events, selectable=False):
949 if selectable:
950 graph.add_sel_task_item(self.task.get_task_no(), self)
951 else:
952 graph.draw_task_item(self.task.get_task_no(), self.is_selected())
953
954class CPUDummy(DummyEvent):
955 """A dummy event that represents a certain CPU. Used for rendering the CPU's ``name''
956 (i.e. ``CPU #n'')"""
957 def __init__(self, time, sched, cpu):
958 super(CPUDummy, self).__init__(time, cpu)
959 self.sched = sched
960 self.layer = Canvas.BOTTOM_LAYER
961
962 def get_name(self):
963 return 'CPU'
964
965 def __str__(self):
966 return 'CPU #' + str(self.get_cpu())
967
968 def str_long(self):
969 return 'CPU #' + str(self.get_cpu())
970
971 def get_task(self):
972 return None
973
974 def get_schedule(self):
975 # we don't have a job, so get the schedule from the schedule passed in
976 return self.sched
977
978 def render(self, graph, layer, prev_events, selectable=False):
979 if selectable:
980 graph.add_sel_cpu_item(self.cpu, self)
981 else:
982 graph.draw_cpu_item(self.cpu, self.is_selected())
983
790EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None, 984EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None,
791 SwitchAwayEvent : None, SwitchToEvent : None, ReleaseEvent : None, 985 SwitchAwayEvent : None, SwitchToEvent : None, ReleaseEvent : None,
792 DeadlineEvent : None, IsRunningDummy : None, 986 DeadlineEvent : None, IsRunningDummy : None,
793 InversionStartEvent : None, InversionEndEvent : None, 987 InversionStartEvent : None, InversionEndEvent : None,
794 InversionDummy : None} 988 InversionDummy : None, TaskDummy : None, CPUDummy : None}
795 989
796SPAN_START_EVENTS = { SwitchToEvent : IsRunningDummy, InversionStartEvent : InversionDummy } 990SPAN_START_EVENTS = { SwitchToEvent : IsRunningDummy, InversionStartEvent : InversionDummy }
797SPAN_END_EVENTS = { SwitchAwayEvent : IsRunningDummy, InversionEndEvent : InversionDummy} 991SPAN_END_EVENTS = { SwitchAwayEvent : IsRunningDummy, InversionEndEvent : InversionDummy}
diff --git a/unit_trace/viz/viewer.py b/unit_trace/viz/viewer.py
index 5e707b8..33c84ff 100644
--- a/unit_trace/viz/viewer.py
+++ b/unit_trace/viz/viewer.py
@@ -4,7 +4,10 @@
4 4
5from schedule import * 5from schedule import *
6from renderer import * 6from renderer import *
7from graph import *
8from canvas import *
7from windows import * 9from windows import *
10from format import *
8 11
9import pygtk 12import pygtk
10import gtk 13import gtk
@@ -79,6 +82,23 @@ class GraphArea(gtk.DrawingArea):
79 self.connect('button-release-event', self.button_release) 82 self.connect('button-release-event', self.button_release)
80 self.connect('motion-notify-event', self.motion_notify) 83 self.connect('motion-notify-event', self.motion_notify)
81 84
85 def output_to_file(self, filename, ftype):
86 """Outputs the entire graph to a file."""
87 graph = self.renderer.get_graph()
88 surface = None
89 if ftype == 'png':
90 surface = ImageSurface()
91 elif ftype == 'svg':
92 surface = SVGSurface()
93 else:
94 raise ValueError
95
96 surface.renew(graph.get_width(), graph.get_height())
97 graph.set_tmp_surface(surface)
98 graph.render_all(self.renderer.get_schedule())
99 surface.write_out(filename)
100 graph.revert_surface()
101
82 def expose(self, widget, expose_event, data=None): 102 def expose(self, widget, expose_event, data=None):
83 ctx = widget.window.cairo_create() 103 ctx = widget.window.cairo_create()
84 graph = self.renderer.get_graph() 104 graph = self.renderer.get_graph()
@@ -204,7 +224,11 @@ class GraphArea(gtk.DrawingArea):
204 graph.get_attrs().y_item_size * GraphArea.VERT_PAGE_SCROLL_FACTOR / scale, 224 graph.get_attrs().y_item_size * GraphArea.VERT_PAGE_SCROLL_FACTOR / scale,
205 self.height / scale) 225 self.height / scale)
206 self.set_vvalue(vvalue) 226 self.set_vvalue(vvalue)
207 227
228 def refresh_all(self):
229 """Refreshes the entire visible screen."""
230 self._dirty(0, 0, self.width, self.height)
231
208 def refresh_events(self, sender, new, old, replace): 232 def refresh_events(self, sender, new, old, replace):
209 """Even if the selected areas change on one graph, they change 233 """Even if the selected areas change on one graph, they change
210 everywhere, and different events might be in different regions on 234 everywhere, and different events might be in different regions on
@@ -216,7 +240,7 @@ class GraphArea(gtk.DrawingArea):
216 refreshes the drawing of the graph that requested the change.""" 240 refreshes the drawing of the graph that requested the change."""
217 if self is not sender: 241 if self is not sender:
218 self.renderer.get_graph().render_events(new, True) 242 self.renderer.get_graph().render_events(new, True)
219 243
220 self._tag_events(new) 244 self._tag_events(new)
221 245
222 if self is sender: 246 if self is sender:
@@ -228,20 +252,23 @@ class GraphArea(gtk.DrawingArea):
228 else: 252 else:
229 self.renderer.get_schedule().remove_selected(old) 253 self.renderer.get_schedule().remove_selected(old)
230 self.renderer.get_schedule().add_selected(new) 254 self.renderer.get_schedule().add_selected(new)
255
256 self.emit('update-sel-changes', self.renderer.get_schedule().get_selected())
231 257
232 def _find_max_layer(self, regions): 258 def _find_max_layer(self, regions):
233 max_layer = Canvas.BOTTOM_LAYER 259 max_layer = -1
234 for event in regions: 260 for layer in regions:
235 if event.get_layer() > max_layer: 261 if layer > max_layer:
236 max_layer = event.get_layer() 262 max_layer = layer
237 return max_layer 263 return max_layer
238 264
239 def _dirty_events(self, events): 265 def _dirty_events(self, events):
240 # if an event changed selected status, update the bounding area 266 # if an event changed selected status, update the bounding area
241 for layer in events: 267 for layer in events:
242 for event in events[layer]: 268 for event in events[layer]:
243 x, y, width, height = events[layer][event][self].get_dimensions() 269 if not events[layer][event][self].is_dummy():
244 self._dirty_inflate((x - self.cur_x) * self.scale, 270 x, y, width, height = events[layer][event][self].get_dimensions()
271 self._dirty_inflate((x - self.cur_x) * self.scale,
245 (y - self.cur_y) * self.scale, 272 (y - self.cur_y) * self.scale,
246 width * self.scale, 273 width * self.scale,
247 height * self.scale, 274 height * self.scale,
@@ -284,15 +311,14 @@ class GraphArea(gtk.DrawingArea):
284 graph.render_surface(self.renderer.get_schedule(), [(motion_event.x, motion_event.y, 311 graph.render_surface(self.renderer.get_schedule(), [(motion_event.x, motion_event.y,
285 0, 0)], True) 312 0, 0)], True)
286 313
287 just_selected = graph.get_selected_regions(motion_event.x, motion_event.y, 0, 0) 314 just_selected = graph.get_intersecting_regions(motion_event.x, motion_event.y, 0, 0)
288 was_selected = self.renderer.get_schedule().get_selected() 315 was_selected = self.renderer.get_schedule().get_selected()
289 if not just_selected: 316 if not just_selected:
290 msg = '' 317 msg = ''
291 the_event = None 318 the_event = None
292 else: 319 else:
293 max_layer = self._find_max_layer(just_selected) 320 max_layer = self._find_max_layer(just_selected)
294 321 for event in just_selected[max_layer]:
295 for event in just_selected:
296 if event.get_layer() == max_layer: 322 if event.get_layer() == max_layer:
297 the_event = event 323 the_event = event
298 break 324 break
@@ -331,9 +357,11 @@ class GraphArea(gtk.DrawingArea):
331 graph.render_surface(self.renderer.get_schedule(), dirty_list, True) 357 graph.render_surface(self.renderer.get_schedule(), dirty_list, True)
332 for rect in dirty_list: 358 for rect in dirty_list:
333 rx, ry, rwidth, rheight = rect 359 rx, ry, rwidth, rheight = rect
334 for event in graph.get_selected_regions(rx, ry, rwidth, rheight): 360 i_regs = graph.get_intersecting_regions(rx, ry, rwidth, rheight)
335 if event.get_layer() in was_selected and event in was_selected[event.get_layer()]: 361 for layer in i_regs:
336 self._select_event(remove_selected, event) 362 for event in i_regs[layer]:
363 if event.get_layer() in was_selected and event in was_selected[event.get_layer()]:
364 self._select_event(remove_selected, event)
337 365
338 add_reg = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(x_p, y_p, width_p, height_p)) 366 add_reg = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(x_p, y_p, width_p, height_p))
339 add_reg.subtract(old_reg) 367 add_reg.subtract(old_reg)
@@ -344,8 +372,10 @@ class GraphArea(gtk.DrawingArea):
344 graph.render_surface(self.renderer.get_schedule(), dirty_list, True) 372 graph.render_surface(self.renderer.get_schedule(), dirty_list, True)
345 for rect in dirty_list: 373 for rect in dirty_list:
346 rx, ry, rwidth, rheight = rect 374 rx, ry, rwidth, rheight = rect
347 for event in graph.get_selected_regions(rx, ry, rwidth, rheight): 375 i_regs = graph.get_intersecting_regions(rx, ry, rwidth, rheight)
348 self._select_event(add_selected, event) 376 for layer in i_regs:
377 for event in i_regs[layer]:
378 self._select_event(add_selected, event)
349 379
350 self.band_rect = x, y, width, height 380 self.band_rect = x, y, width, height
351 self.emit('request-refresh-events', self, add_selected, remove_selected, False) 381 self.emit('request-refresh-events', self, add_selected, remove_selected, False)
@@ -362,9 +392,9 @@ class GraphArea(gtk.DrawingArea):
362 self.left_button_start_coor = (button_event.x, button_event.y) 392 self.left_button_start_coor = (button_event.x, button_event.y)
363 graph.render_surface(self.renderer.get_schedule(), \ 393 graph.render_surface(self.renderer.get_schedule(), \
364 [(button_event.x, button_event.y, 0, 0)], True) 394 [(button_event.x, button_event.y, 0, 0)], True)
365 395
366 just_selected = graph.get_selected_regions(button_event.x, button_event.y, 0, 0) 396 just_selected = graph.get_intersecting_regions(button_event.x, button_event.y, 0, 0)
367 397
368 max_layer = self._find_max_layer(just_selected) 398 max_layer = self._find_max_layer(just_selected)
369 399
370 was_selected = self.renderer.get_schedule().get_selected() 400 was_selected = self.renderer.get_schedule().get_selected()
@@ -382,21 +412,21 @@ class GraphArea(gtk.DrawingArea):
382 # not intuitive to click something and then have something 412 # not intuitive to click something and then have something
383 # below it get selected). Also, clicking something that 413 # below it get selected). Also, clicking something that
384 # is selected deselects it, if it's the only thing selected 414 # is selected deselects it, if it's the only thing selected
385 for event in just_selected: 415 if just_selected:
386 if event.get_layer() == max_layer: 416 for event in just_selected[max_layer]:
387 if not (more_than_one == 1 and event in was_selected): 417 if not (more_than_one == 1 and max_layer in was_selected \
418 and event in was_selected[max_layer]):
388 self._select_event(new_now_selected, event) 419 self._select_event(new_now_selected, event)
389 break # only pick one event when just clicking 420 break # only pick one event when just clicking
390 421
391 self.emit('request-refresh-events', self, new_now_selected, was_selected, True) 422 self.emit('request-refresh-events', self, new_now_selected, was_selected, True)
392 else: 423 else:
393 remove_selected = {} 424 remove_selected = {}
394 add_selected = {} 425 add_selected = {}
395 426
396 for event in just_selected: 427 if just_selected:
397 layer = event.get_layer() 428 for event in just_selected[max_layer]:
398 if layer == max_layer: 429 if max_layer in was_selected and event in was_selected[max_layer]:
399 if layer in was_selected and event in was_selected[layer]:
400 self._select_event(remove_selected, event) 430 self._select_event(remove_selected, event)
401 else: 431 else:
402 self._select_event(add_selected, event) 432 self._select_event(add_selected, event)
@@ -566,40 +596,68 @@ class MainWindow(gtk.Window):
566 596
567 def __init__(self): 597 def __init__(self):
568 super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL) 598 super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL)
569 599
570 self.add_events(gtk.gdk.BUTTON_PRESS_MASK) 600 self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
571 601
572 self.connect('delete_event', self.delete_event) 602 self.connect('delete_event', self.delete_event)
573 self.connect('destroy', self.die) 603 self.connect('destroy', self.die)
574 604
575 file_menu = gtk.Menu() 605 file_menu = gtk.Menu()
606 edit_menu = gtk.Menu()
576 view_menu = gtk.Menu() 607 view_menu = gtk.Menu()
577 608
578 agr = gtk.AccelGroup() 609 agr = gtk.AccelGroup()
579 self.add_accel_group(agr) 610 self.add_accel_group(agr)
580 611
612 # list of file items that are clickable if and only if at
613 # least one graph is viewable
614 self.sensitive_menu_items = []
615
616 save_item = gtk.ImageMenuItem('_Save Graph to File')
617 img = gtk.Image()
618 img.set_from_stock(gtk.STOCK_SAVE_AS, gtk.ICON_SIZE_MENU)
619 save_item.set_image(img)
620 key, mod = gtk.accelerator_parse('<Ctrl>S')
621 save_item.add_accelerator('activate', agr, key, mod,
622 gtk.ACCEL_VISIBLE)
623 save_item.connect('activate', self.save_item_activate)
624 save_item.set_sensitive(False)
625 self.sensitive_menu_items.append(save_item)
626
581 quit_item = gtk.ImageMenuItem(gtk.STOCK_QUIT, agr) 627 quit_item = gtk.ImageMenuItem(gtk.STOCK_QUIT, agr)
582 key, mod = gtk.accelerator_parse('Q') 628 key, mod = gtk.accelerator_parse('<Ctrl>Q')
583 quit_item.add_accelerator('activate', agr, key, mod, 629 quit_item.add_accelerator('activate', agr, key, mod,
584 gtk.ACCEL_VISIBLE) 630 gtk.ACCEL_VISIBLE)
585 quit_item.connect('activate', self.quit_item_activate) 631 quit_item.connect('activate', self.quit_item_activate)
586 quit_item.show()
587 632
633 file_menu.append(save_item)
588 file_menu.append(quit_item) 634 file_menu.append(quit_item)
589 635
590 file_item = gtk.MenuItem('_File', True) 636 file_item = gtk.MenuItem('_File', True)
591 file_item.set_submenu(file_menu) 637 file_item.set_submenu(file_menu)
592 file_item.show()
593 638
594 self.move_item = gtk.ImageMenuItem('_Move to Time') 639 move_item = gtk.ImageMenuItem('_Move to Time')
595 key, mod = gtk.accelerator_parse('<Ctrl>M') 640 key, mod = gtk.accelerator_parse('<Ctrl>M')
596 self.move_item.add_accelerator('activate', agr, key, mod, 641 move_item.add_accelerator('activate', agr, key, mod,
597 gtk.ACCEL_VISIBLE) 642 gtk.ACCEL_VISIBLE)
598 self.move_item.set_sensitive(False) 643 move_item.set_sensitive(False)
599 644 self.sensitive_menu_items.append(move_item)
600 self.move_item.connect('activate', self.move_to_time_activate) 645
601 self.move_item.show() 646 move_item.connect('activate', self.move_to_time_activate)
602 647
648 self.colors_item = gtk.ImageMenuItem('Co_lors')
649 key, mod = gtk.accelerator_parse('<Ctrl>L')
650 self.colors_item.add_accelerator('activate', agr, key, mod,
651 gtk.ACCEL_VISIBLE)
652 self.colors_item.set_sensitive(False)
653
654 self.colors_item.connect('activate', self.colors_activate)
655
656 edit_menu.append(self.colors_item)
657
658 edit_item = gtk.MenuItem('_Edit', True)
659 edit_item.set_submenu(edit_menu)
660
603 zoom_in_item = gtk.ImageMenuItem(gtk.STOCK_ZOOM_IN, agr) 661 zoom_in_item = gtk.ImageMenuItem(gtk.STOCK_ZOOM_IN, agr)
604 key, mod = gtk.accelerator_parse('<Ctrl>plus') 662 key, mod = gtk.accelerator_parse('<Ctrl>plus')
605 zoom_in_item.add_accelerator('activate', agr, key, mod, 663 zoom_in_item.add_accelerator('activate', agr, key, mod,
@@ -608,7 +666,6 @@ class MainWindow(gtk.Window):
608 zoom_in_item.add_accelerator('activate', agr, key, mod, 0) 666 zoom_in_item.add_accelerator('activate', agr, key, mod, 0)
609 667
610 zoom_in_item.connect('activate', self.zoom_in_item_activate) 668 zoom_in_item.connect('activate', self.zoom_in_item_activate)
611 zoom_in_item.show()
612 669
613 zoom_out_item = gtk.ImageMenuItem(gtk.STOCK_ZOOM_OUT, agr) 670 zoom_out_item = gtk.ImageMenuItem(gtk.STOCK_ZOOM_OUT, agr)
614 key, mod = gtk.accelerator_parse('<Ctrl>minus') 671 key, mod = gtk.accelerator_parse('<Ctrl>minus')
@@ -618,21 +675,19 @@ class MainWindow(gtk.Window):
618 zoom_out_item.add_accelerator('activate', agr, key, mod, 0) 675 zoom_out_item.add_accelerator('activate', agr, key, mod, 0)
619 676
620 zoom_out_item.connect('activate', self.zoom_out_item_activate) 677 zoom_out_item.connect('activate', self.zoom_out_item_activate)
621 zoom_out_item.show()
622 678
623 view_menu.append(self.move_item) 679 view_menu.append(move_item)
624 view_menu.append(zoom_in_item) 680 view_menu.append(zoom_in_item)
625 view_menu.append(zoom_out_item) 681 view_menu.append(zoom_out_item)
626 682
627 view_item = gtk.MenuItem('_View', True) 683 view_item = gtk.MenuItem('_View', True)
628 view_item.set_submenu(view_menu) 684 view_item.set_submenu(view_menu)
629 view_item.show()
630 685
631 menu_bar = gtk.MenuBar() 686 menu_bar = gtk.MenuBar()
632 menu_bar.append(file_item) 687 menu_bar.append(file_item)
688 menu_bar.append(edit_item)
633 menu_bar.append(view_item) 689 menu_bar.append(view_item)
634 690
635 menu_bar.show()
636 self.vbox = gtk.VBox(False, 0) 691 self.vbox = gtk.VBox(False, 0)
637 692
638 self.notebook = gtk.Notebook() 693 self.notebook = gtk.Notebook()
@@ -640,25 +695,24 @@ class MainWindow(gtk.Window):
640 self.notebook.last_page = -1 695 self.notebook.last_page = -1
641 self.notebook.connect('switch-page', self.switch_page) 696 self.notebook.connect('switch-page', self.switch_page)
642 697
643 self.notebook.show()
644
645 self.desc_label = gtk.Label('') 698 self.desc_label = gtk.Label('')
646 self.desc_label.set_alignment(0.0, 0.0) 699 self.desc_label.set_alignment(0.0, 0.0)
647 self.desc_label.show()
648 700
649 self.vbox.pack_start(menu_bar, False, False, 0) 701 self.vbox.pack_start(menu_bar, False, False, 0)
650 self.vbox.pack_start(self.notebook, True, True, 0) 702 self.vbox.pack_start(self.notebook, True, True, 0)
651 self.vbox.pack_start(self.desc_label, False, False, 0) 703 self.vbox.pack_start(self.desc_label, False, False, 0)
652 self.vbox.show()
653 704
654 self.add(self.vbox) 705 self.add(self.vbox)
655 706
656 self.info_win = InfoWindow() 707 self.info_win = InfoWindow()
657 708 self.color_dialog = gtk.ColorSelectionDialog('Choose Item Color')
709
710 self.cur_item_no = None
711
658 self.set_size_request(MainWindow.WINDOW_WIDTH_REQ, MainWindow.WINDOW_HEIGHT_REQ) 712 self.set_size_request(MainWindow.WINDOW_WIDTH_REQ, MainWindow.WINDOW_HEIGHT_REQ)
659 713
660 self.set_title('Unit-Trace Visualizer') 714 self.set_title('Unit-Trace Visualizer')
661 self.show() 715 self.show_all()
662 716
663 def connect_widgets(self, gwindow): 717 def connect_widgets(self, gwindow):
664 gwindow.get_graph_area().connect('update-event-description', self.update_event_description) 718 gwindow.get_graph_area().connect('update-event-description', self.update_event_description)
@@ -666,6 +720,7 @@ class MainWindow(gtk.Window):
666 gwindow.get_graph_area().connect('request-refresh-events', self.request_refresh_events) 720 gwindow.get_graph_area().connect('request-refresh-events', self.request_refresh_events)
667 gwindow.connect('request-zoom-in', self.zoom_in_item_activate) 721 gwindow.connect('request-zoom-in', self.zoom_in_item_activate)
668 gwindow.connect('request-zoom-out', self.zoom_out_item_activate) 722 gwindow.connect('request-zoom-out', self.zoom_out_item_activate)
723 gwindow.get_graph_area().connect('update-sel-changes', self.update_sel_changes)
669 724
670 def set_renderers(self, renderers): 725 def set_renderers(self, renderers):
671 for i in range(0, self.notebook.get_n_pages()): 726 for i in range(0, self.notebook.get_n_pages()):
@@ -675,15 +730,28 @@ class MainWindow(gtk.Window):
675 self.connect_widgets(gwindow) 730 self.connect_widgets(gwindow)
676 gwindow.show() 731 gwindow.show()
677 self.notebook.append_page(gwindow, gtk.Label(title)) 732 self.notebook.append_page(gwindow, gtk.Label(title))
733
678 if self.notebook.get_n_pages() > 0: 734 if self.notebook.get_n_pages() > 0:
679 self.notebook.get_nth_page(0).grab_focus() 735 self.notebook.get_nth_page(0).grab_focus()
680 736
681 if self.notebook.get_n_pages() > 0: 737 if self.notebook.get_n_pages() > 0:
682 self.move_item.set_sensitive(True) 738 for menu_item in self.sensitive_menu_items:
739 menu_item.set_sensitive(True)
683 else: 740 else:
684 self.move_item.set_sensitive(False) 741 for menu_item in self.sensitive_menu_items:
685 742 menu_item.set_sensitive(False)
686 743
744 def update_sel_changes(self, widget, selected):
745 """Modify the GUI appropriately to reflect a change in selection."""
746 self.selected = selected
747
748 self.cur_item_no = self.notebook.get_nth_page(self.notebook.last_page).get_graph_area() \
749 .get_graph().all_one_item_selected(selected)
750 if self.cur_item_no is not None:
751 self.colors_item.set_sensitive(True)
752 else:
753 self.colors_item.set_sensitive(False)
754
687 def switch_page(self, widget, page, page_num): 755 def switch_page(self, widget, page, page_num):
688 if self.notebook.get_nth_page(self.notebook.last_page) is not None: 756 if self.notebook.get_nth_page(self.notebook.last_page) is not None:
689 old_value = self.notebook.get_nth_page(self.notebook.last_page).get_hadjustment().get_value() 757 old_value = self.notebook.get_nth_page(self.notebook.last_page).get_hadjustment().get_value()
@@ -710,11 +778,12 @@ class MainWindow(gtk.Window):
710 for i in range(0, self.notebook.get_n_pages()): 778 for i in range(0, self.notebook.get_n_pages()):
711 if self.notebook.get_nth_page(i).get_graph_area() is not sender: 779 if self.notebook.get_nth_page(i).get_graph_area() is not sender:
712 self.notebook.get_nth_page(i).get_graph_area().refresh_events(sender, old, new, replace) 780 self.notebook.get_nth_page(i).get_graph_area().refresh_events(sender, old, new, replace)
713 break
714 for i in range(0, self.notebook.get_n_pages()): 781 for i in range(0, self.notebook.get_n_pages()):
782 self.notebook.get_nth_page(i).get_graph_area().renderer.graph
715 if self.notebook.get_nth_page(i).get_graph_area() is sender: 783 if self.notebook.get_nth_page(i).get_graph_area() is sender:
716 self.notebook.get_nth_page(i).get_graph_area().refresh_events(sender, old, new, replace) 784 self.notebook.get_nth_page(i).get_graph_area().refresh_events(sender, old, new, replace)
717 785 break
786
718 def move_to_time_activate(self, widget): 787 def move_to_time_activate(self, widget):
719 dialog = TextInputDialog('Move to Time', 'What time to move to?', self) 788 dialog = TextInputDialog('Move to Time', 'What time to move to?', self)
720 789
@@ -760,6 +829,93 @@ class MainWindow(gtk.Window):
760 for i in range(0, self.notebook.get_n_pages()): 829 for i in range(0, self.notebook.get_n_pages()):
761 self.notebook.get_nth_page(i).get_graph_area().zoom_out() 830 self.notebook.get_nth_page(i).get_graph_area().zoom_out()
762 831
832 def colors_activate(self, widget):
833 garea = self.notebook.get_nth_page(self.notebook.last_page).get_graph_area()
834 graph = garea.get_renderer().get_graph()
835
836
837 pattern = graph.get_bar_pattern(self.cur_item_no)
838
839 if len(pattern.get_color_list()) > 1:
840 print 'striped patterns not yet supported'
841
842 r, g, b = pattern.get_color_list()[0]
843 colorsel = self.color_dialog.get_color_selection()
844 cur_color = gtk.gdk.Color(int(r * 65535), int(g * 65535), int(b * 65535))
845 colorsel.set_previous_color(cur_color)
846 colorsel.set_current_color(cur_color)
847 colorsel.set_has_palette(True)
848
849 if self.color_dialog.run() == gtk.RESPONSE_OK:
850 color = colorsel.get_current_color()
851 r, g, b = color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0
852 graph.set_bar_pattern(self.cur_item_no, Pattern([(r, g, b)]))
853 garea.refresh_all()
854
855 self.color_dialog.hide()
856
857 def save_item_activate(self, widget):
858 ACCEPTED_TYPES = ('png', 'svg')
859
860 dialog = gtk.FileChooserDialog('Save to Graphics File', self,
861 gtk.FILE_CHOOSER_ACTION_SAVE,
862 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
863 gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
864
865 png_filter = gtk.FileFilter()
866 png_filter.add_mime_type('image/png')
867 png_filter.set_name('PNG image (*.png)')
868 dialog.add_filter(png_filter)
869
870 svg_filter = gtk.FileFilter()
871 svg_filter.add_mime_type('image/svg+xml')
872 svg_filter.set_name('SVG vector image (*.svg)')
873 dialog.add_filter(svg_filter)
874
875 filename = None
876 ftype = None
877 while True:
878 if dialog.run() == gtk.RESPONSE_ACCEPT:
879 filename = dialog.get_filename()
880 # parse filename for extension
881
882 parts = filename.split('.')
883 ftype = None
884 if len(parts) == 1:
885 filename += '.' + parts[0]
886 if dialog.get_filter() is png_filter:
887 ftype = 'png'
888 elif dialog.get_filter() is svg_filter:
889 ftype = 'svg'
890 break
891 else:
892 ftype = parts[-1]
893 if ftype in ACCEPTED_TYPES:
894 break
895 else:
896 err_dialog = gtk.MessageDialog(self, gtk.DIALOG_DESTROY_WITH_PARENT,
897 gtk.MESSAGE_ERROR,
898 gtk.BUTTONS_CLOSE,
899 'File type "' + ftype + '" not recognized')
900 err_dialog.set_title('File Type Error')
901 err_dialog.run()
902 err_dialog.destroy()
903 else:
904 filename = None
905 ftype = None
906 break
907
908 if filename is not None:
909 cursor = gtk.gdk.Cursor(gtk.gdk.WATCH)
910 dialog.get_window().set_cursor(cursor)
911 def idle_callback(dialog):
912 self.notebook.get_nth_page(self.notebook.last_page).get_graph_area().output_to_file(filename, ftype)
913 dialog.get_window().set_cursor(None)
914 dialog.destroy()
915 gobject.idle_add(idle_callback, dialog) # needed to get the cursor to display
916 else:
917 dialog.destroy()
918
763 def quit_item_activate(self, widget): 919 def quit_item_activate(self, widget):
764 self.destroy() 920 self.destroy()
765 921