diff options
author | Gary Bressler <garybressler@nc.rr.com> | 2010-04-27 14:04:50 -0400 |
---|---|---|
committer | Gary Bressler <garybressler@nc.rr.com> | 2010-04-27 14:04:50 -0400 |
commit | df6c90f22f5cdd5b079a9a69a54f30104eab9146 (patch) | |
tree | db68985cc6858faaab1728aa8972e298266b837f | |
parent | e3a1e19e87dbb3df413015bfc714283bc8602999 (diff) |
Colorsgit add .! COLORSgit add .git add .geany index.txt
-rw-r--r-- | test.png | bin | 0 -> 1507837 bytes | |||
-rw-r--r-- | unit_trace/viz/__init__.py | 2 | ||||
-rw-r--r-- | unit_trace/viz/canvas.py | 314 | ||||
-rw-r--r-- | unit_trace/viz/format.py | 2 | ||||
-rw-r--r-- | unit_trace/viz/graph.py | 307 | ||||
-rw-r--r-- | unit_trace/viz/renderer.py | 6 | ||||
-rw-r--r-- | unit_trace/viz/schedule.py | 364 | ||||
-rw-r--r-- | unit_trace/viz/viewer.py | 264 |
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)) |
20 | gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | 20 | gobject.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)) |
22 | gobject.signal_new('update-sel-changes', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | ||
23 | None, (gobject.TYPE_PYOBJECT,)) | ||
22 | gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | 24 | gobject.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)) |
24 | gobject.signal_new('request-refresh-events', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | 26 | gobject.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 |
4 | this file is quite low-level, in that its objects are mostly restricted to | 4 | is quite low-level, in that its objects are mostly restricted to |
5 | dealing with drawing the components of a real-time graph given coordinates | 5 | dealing with just drawing the components of a real-time graph at specific |
6 | rather than having an abstract knowledge of the graph's measurements or | 6 | coordinates, rather than having any knowledge of the graph's measurements or |
7 | any information about events.""" | 7 | any information about events.""" |
8 | 8 | ||
9 | import math | 9 | import math |
@@ -14,16 +14,9 @@ import copy | |||
14 | import util | 14 | import util |
15 | from format import * | 15 | from format import * |
16 | 16 | ||
17 | def 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 | |||
26 | class Surface(object): | 17 | class 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 | ||
573 | class CairoCanvas(Canvas): | 644 | class 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 | ||
876 | def 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 |
786 | class SelectableRegion(object): | 886 | class 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 | ||
290 | class TaskGraph(Graph): | 401 | class 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 | ||
448 | class CpuGraph(Graph): | 560 | class 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 | ||
129 | class Schedule(object): | 172 | class 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(), | |
388 | class 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 | |||
456 | class SpanEvent(Event): | ||
457 | def __init__(self, time, cpu, dummy_class): | ||
458 | super(SpanEvent, self).__init__(time, cpu) | ||
459 | self.dummy_class = dummy_class | ||
460 | |||
461 | class 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 | |||
500 | class 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 | |||
506 | class 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 | |||
391 | class SuspendEvent(Event): | 512 | class 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 | ||
462 | class SwitchToEvent(Event): | 583 | class 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 | ||
526 | class SwitchAwayEvent(Event): | 647 | class 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 | ||
638 | class InversionStartEvent(ErrorEvent): | 763 | class 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 | ||
699 | class InversionEndEvent(ErrorEvent): | 831 | class 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 | ||
760 | class InversionDummy(DummyEvent): | 899 | class 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 | ||
775 | class IsRunningDummy(DummyEvent): | 914 | class 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 | ||
929 | class 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 | |||
954 | class 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 | |||
790 | EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None, | 984 | EVENT_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 | ||
796 | SPAN_START_EVENTS = { SwitchToEvent : IsRunningDummy, InversionStartEvent : InversionDummy } | 990 | SPAN_START_EVENTS = { SwitchToEvent : IsRunningDummy, InversionStartEvent : InversionDummy } |
797 | SPAN_END_EVENTS = { SwitchAwayEvent : IsRunningDummy, InversionEndEvent : InversionDummy} | 991 | SPAN_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 | ||
5 | from schedule import * | 5 | from schedule import * |
6 | from renderer import * | 6 | from renderer import * |
7 | from graph import * | ||
8 | from canvas import * | ||
7 | from windows import * | 9 | from windows import * |
10 | from format import * | ||
8 | 11 | ||
9 | import pygtk | 12 | import pygtk |
10 | import gtk | 13 | import 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 | ||