diff options
author | Gary Bressler <garybressler@nc.rr.com> | 2010-04-06 12:45:04 -0400 |
---|---|---|
committer | Gary Bressler <garybressler@nc.rr.com> | 2010-04-06 12:45:04 -0400 |
commit | c7e3aaebdba7bf880534abd91a383b5543cf0be4 (patch) | |
tree | 048977efdaaa3d60e93c3d21ba29c46a0bfe71c3 /unit_trace/viz | |
parent | 7fdb4dbbbca577efbeec47cd1364eb319346a0cc (diff) |
Making sure everything committed
Diffstat (limited to 'unit_trace/viz')
-rw-r--r-- | unit_trace/viz/__init__.py | 9 | ||||
-rw-r--r-- | unit_trace/viz/canvas.py (renamed from unit_trace/viz/draw.py) | 788 | ||||
-rw-r--r-- | unit_trace/viz/graph.py | 657 | ||||
-rw-r--r-- | unit_trace/viz/renderer.py | 2 | ||||
-rw-r--r-- | unit_trace/viz/schedule.py | 74 | ||||
-rw-r--r-- | unit_trace/viz/viewer.py | 407 | ||||
-rwxr-xr-x | unit_trace/viz/visualizer.py | 2 |
7 files changed, 1132 insertions, 807 deletions
diff --git a/unit_trace/viz/__init__.py b/unit_trace/viz/__init__.py index 11505b9..8ba4784 100644 --- a/unit_trace/viz/__init__.py +++ b/unit_trace/viz/__init__.py | |||
@@ -12,5 +12,10 @@ gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_ | |||
12 | None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) | 12 | None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) |
13 | gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | 13 | gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, |
14 | None, (gtk.gdk.Event, gobject.TYPE_PYOBJECT)) | 14 | None, (gtk.gdk.Event, gobject.TYPE_PYOBJECT)) |
15 | #gobject.signal_new('request-renderer-change', viewer.MainWindow, gobject.SIGNAL_RUN_FIRST, | 15 | gobject.signal_new('request-refresh-events', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, |
16 | # None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) | 16 | None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, |
17 | gobject.TYPE_PYOBJECT)) | ||
18 | gobject.signal_new('request-zoom-in', viewer.GraphWindow, gobject.SIGNAL_RUN_FIRST, | ||
19 | None, ()) | ||
20 | gobject.signal_new('request-zoom-out', viewer.GraphWindow, gobject.SIGNAL_RUN_FIRST, | ||
21 | None, ()) | ||
diff --git a/unit_trace/viz/draw.py b/unit_trace/viz/canvas.py index 8c48744..758dea3 100644 --- a/unit_trace/viz/draw.py +++ b/unit_trace/viz/canvas.py | |||
@@ -1,12 +1,17 @@ | |||
1 | #!/usr/bin/python | 1 | #!/usr/bin/python |
2 | 2 | ||
3 | """Classes related to the drawing and area-selection primitives. Note that | ||
4 | this file 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 | ||
6 | rather than having an abstract knowledge of the graph's measurements or | ||
7 | any information about events.""" | ||
8 | |||
3 | import math | 9 | import math |
4 | import cairo | 10 | import cairo |
5 | import os | 11 | import os |
6 | import copy | 12 | import copy |
7 | 13 | ||
8 | import util | 14 | import util |
9 | import schedule | ||
10 | from format import * | 15 | from format import * |
11 | 16 | ||
12 | def snap(pos): | 17 | def snap(pos): |
@@ -16,7 +21,7 @@ def snap(pos): | |||
16 | line of width 1 on integer coordinates, it will come out blurry unless we shift it, | 21 | line of width 1 on integer coordinates, it will come out blurry unless we shift it, |
17 | since the line will get distributed over two pixels. We actually apply this to all | 22 | since the line will get distributed over two pixels. We actually apply this to all |
18 | coordinates to make sure everything is aligned.""" | 23 | coordinates to make sure everything is aligned.""" |
19 | return pos | 24 | return pos - 0.5 |
20 | 25 | ||
21 | class Surface(object): | 26 | class Surface(object): |
22 | def __init__(self, fname='temp', ctx=None): | 27 | def __init__(self, fname='temp', ctx=None): |
@@ -25,6 +30,7 @@ class Surface(object): | |||
25 | self.surface = None | 30 | self.surface = None |
26 | self.width = 0 | 31 | self.width = 0 |
27 | self.height = 0 | 32 | self.height = 0 |
33 | self.scale = 1.0 | ||
28 | self.fname = fname | 34 | self.fname = fname |
29 | self.ctx = ctx | 35 | self.ctx = ctx |
30 | 36 | ||
@@ -39,9 +45,9 @@ class Surface(object): | |||
39 | 45 | ||
40 | def write_out(self, fname): | 46 | def write_out(self, fname): |
41 | raise NotImplementedError | 47 | raise NotImplementedError |
42 | 48 | ||
43 | def pan(self, x, y, width, height): | 49 | def pan(self, x, y, width, height): |
44 | """A surface might actually represent just a ``window'' into | 50 | """A surface actually represents just a ``window'' into |
45 | what we are drawing on. For instance, if we are scrolling through | 51 | what we are drawing on. For instance, if we are scrolling through |
46 | a graph, then the surface represents the area in the GUI window, | 52 | a graph, then the surface represents the area in the GUI window, |
47 | not the entire graph (visible or not). So this method basically | 53 | not the entire graph (visible or not). So this method basically |
@@ -51,6 +57,10 @@ class Surface(object): | |||
51 | self.virt_y = y | 57 | self.virt_y = y |
52 | self.width = width | 58 | self.width = width |
53 | self.height = height | 59 | self.height = height |
60 | |||
61 | def set_scale(self, scale): | ||
62 | """Sets the scale factor.""" | ||
63 | self.scale = scale | ||
54 | 64 | ||
55 | def get_real_coor(self, x, y): | 65 | def get_real_coor(self, x, y): |
56 | """Translates the coordinates (x, y) | 66 | """Translates the coordinates (x, y) |
@@ -58,7 +68,16 @@ class Surface(object): | |||
58 | that we should draw to. Note that these might actually be outside the | 68 | that we should draw to. Note that these might actually be outside the |
59 | bounds of the surface, | 69 | bounds of the surface, |
60 | if we want something outside the surface's ``window''.""" | 70 | if we want something outside the surface's ``window''.""" |
61 | return (x - self.virt_x, y - self.virt_y) | 71 | return (x - self.virt_x * self.scale, y - self.virt_y * self.scale) |
72 | |||
73 | def get_virt_coor(self, x, y): | ||
74 | """Does the inverse of the last method.""" | ||
75 | return (x + self.virt_x * self.scale, y + self.virt_y * self.scale) | ||
76 | |||
77 | def get_virt_coor_unscaled(self, x, y): | ||
78 | """Does the same, but removes the scale factor (i.e. behaves as if | ||
79 | the scale was 1.0 all along).""" | ||
80 | return (x / self.scale + self.virt_x, y / self.scale + self.virt_y) | ||
62 | 81 | ||
63 | class SVGSurface(Surface): | 82 | class SVGSurface(Surface): |
64 | def renew(self, width, height): | 83 | def renew(self, width, height): |
@@ -164,9 +183,20 @@ class Canvas(object): | |||
164 | def clear(self): | 183 | def clear(self): |
165 | raise NotImplementedError | 184 | raise NotImplementedError |
166 | 185 | ||
186 | def set_scale(self, scale): | ||
187 | self.scale = scale | ||
188 | self.surface.set_scale(scale) | ||
189 | for event in self.selectable_regions: | ||
190 | self.selectable_regions[event].set_scale(scale) | ||
191 | |||
167 | def scaled(self, *coors): | 192 | def scaled(self, *coors): |
193 | """Scales a series of coordinates.""" | ||
168 | return [coor * self.scale for coor in coors] | 194 | return [coor * self.scale for coor in coors] |
169 | 195 | ||
196 | def unscaled(self, *coors): | ||
197 | """Inverse of scale().""" | ||
198 | return [coor / self.scale for coor in coors] | ||
199 | |||
170 | def draw_rect(self, x, y, width, height, color, thickness, snap=True): | 200 | def draw_rect(self, x, y, width, height, color, thickness, snap=True): |
171 | """Draws a rectangle somewhere (border only).""" | 201 | """Draws a rectangle somewhere (border only).""" |
172 | raise NotImplementedError | 202 | raise NotImplementedError |
@@ -315,17 +345,7 @@ class Canvas(object): | |||
315 | for i in range(start_tick, end_tick + 1): | 345 | for i in range(start_tick, end_tick + 1): |
316 | self.draw_line((x, y), (x, y + line_height), GraphFormat.GRID_COLOR, GraphFormat.GRID_THICKNESS) | 346 | self.draw_line((x, y), (x, y + line_height), GraphFormat.GRID_COLOR, GraphFormat.GRID_THICKNESS) |
317 | x += maj_sep | 347 | x += maj_sep |
318 | 348 | ||
319 | def _draw_bar_border_common(self, x, y, width, height, color, thickness, clip_side): | ||
320 | if clip_side is None: | ||
321 | self.draw_rect(x, y, width, height, color, thickness) | ||
322 | elif clip_side == AlignMode.LEFT: | ||
323 | self.draw_polyline([(x, y), (x + width, y), (x + width, y + height), (x, y + height)], | ||
324 | color, thickness) | ||
325 | elif clip_side == AlignMode.RIGHT: | ||
326 | self.draw_polyline([(x + width, y), (x, y), (x, y + height), (x + width, y + height)], | ||
327 | color, thickness) | ||
328 | |||
329 | def draw_bar(self, x, y, width, height, n, clip_side, selected): | 349 | def draw_bar(self, x, y, width, height, n, clip_side, selected): |
330 | """Draws a bar with a certain set of dimensions, using pattern ``n'' from the | 350 | """Draws a bar with a certain set of dimensions, using pattern ``n'' from the |
331 | bar pattern list.""" | 351 | bar pattern list.""" |
@@ -336,7 +356,7 @@ class Canvas(object): | |||
336 | # use a pattern to be pretty | 356 | # use a pattern to be pretty |
337 | self.get_bar_pattern(n).render_on_canvas(self, x, y, width, height, True) | 357 | self.get_bar_pattern(n).render_on_canvas(self, x, y, width, height, True) |
338 | 358 | ||
339 | self._draw_bar_border_common(x, y, width, height, color, thickness, clip_side) | 359 | self.draw_rect(x, y, width, height, color, thickness, clip_side) |
340 | 360 | ||
341 | def add_sel_bar(self, x, y, width, height, event): | 361 | def add_sel_bar(self, x, y, width, height, event): |
342 | self.add_sel_region(SelectableRegion(x, y, width, height, event)) | 362 | self.add_sel_region(SelectableRegion(x, y, width, height, event)) |
@@ -354,7 +374,7 @@ class Canvas(object): | |||
354 | 374 | ||
355 | self.get_bar_pattern(n).render_on_canvas(self, x, y, width, height, True) | 375 | self.get_bar_pattern(n).render_on_canvas(self, x, y, width, height, True) |
356 | 376 | ||
357 | self._draw_bar_border_common(x, y, width, height, color, thickness, clip_side) | 377 | self.draw_rect(x, y, width, height, color, thickness, clip_side) |
358 | 378 | ||
359 | def add_sel_mini_bar(self, x, y, width, height, event): | 379 | def add_sel_mini_bar(self, x, y, width, height, event): |
360 | self.add_sel_region(SelectableRegion(x, y, width, height, event)) | 380 | self.add_sel_region(SelectableRegion(x, y, width, height, event)) |
@@ -494,23 +514,28 @@ class Canvas(object): | |||
494 | 514 | ||
495 | def add_sel_resume_triangle(self, x, y, height, event): | 515 | def add_sel_resume_triangle(self, x, y, height, event): |
496 | self.add_sel_region(SelectableRegion(x - height / 2.0, y, height / 2.0, height, event)) | 516 | self.add_sel_region(SelectableRegion(x - height / 2.0, y, height / 2.0, height, event)) |
497 | 517 | ||
498 | def clear_selectable_regions(self, real_x, real_y, width, height): | 518 | def clear_selectable_regions(self): |
499 | x = real_x + self.surface.virt_x | 519 | self.selectable_regions = {} |
500 | y = real_y + self.surface.virt_y | 520 | |
501 | for event in self.selectable_regions.keys(): | 521 | #def clear_selectable_regions(self, real_x, real_y, width, height): |
502 | if self.selectable_regions[event].intersects(x, y, width, height): | 522 | # x, y = self.surface.get_virt_coor(real_x, real_y) |
503 | del self.selectable_regions[event] | 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] | ||
504 | 526 | ||
505 | def add_sel_region(self, region): | 527 | def add_sel_region(self, region): |
528 | region.set_scale(self.scale) | ||
506 | self.selectable_regions[region.get_event()] = region | 529 | self.selectable_regions[region.get_event()] = region |
507 | 530 | ||
508 | def get_sel_region(self, event): | 531 | def get_sel_region(self, event): |
509 | return self.selectable_regions[event] | 532 | return self.selectable_regions[event] |
510 | 533 | ||
534 | def has_sel_region(self, event): | ||
535 | return event in self.selectable_regions | ||
536 | |||
511 | def get_selected_regions(self, real_x, real_y, width, height): | 537 | def get_selected_regions(self, real_x, real_y, width, height): |
512 | x = real_x + self.surface.virt_x | 538 | x, y = self.surface.get_virt_coor(real_x, real_y) |
513 | y = real_y + self.surface.virt_y | ||
514 | 539 | ||
515 | selected = {} | 540 | selected = {} |
516 | for event in self.selectable_regions: | 541 | for event in self.selectable_regions: |
@@ -520,10 +545,14 @@ class Canvas(object): | |||
520 | 545 | ||
521 | return selected | 546 | return selected |
522 | 547 | ||
523 | def whiteout(self, real_x, real_y, width, height): | 548 | def whiteout(self): |
524 | """Overwrites the surface completely white, but technically doesn't delete anything""" | 549 | """Overwrites the surface completely white, but technically doesn't delete anything""" |
525 | self.fill_rect(self.surface.virt_x + real_x, self.surface.virt_y + real_y, width, | 550 | # Make sure we don't scale here (we want to literally white out just this region) |
526 | height, (1.0, 1.0, 1.0), False) | 551 | |
552 | x, y = self.surface.get_virt_coor_unscaled(0, 0) | ||
553 | width, height = self.unscaled(self.surface.width, self.surface.height) | ||
554 | |||
555 | self.fill_rect(x, y, width, height, (1.0, 1.0, 1.0), False) | ||
527 | 556 | ||
528 | def get_item_color(self, n): | 557 | def get_item_color(self, n): |
529 | """Gets the nth color in the item color list, which are the colors used to draw the items | 558 | """Gets the nth color in the item color list, which are the colors used to draw the items |
@@ -569,19 +598,50 @@ class CairoCanvas(Canvas): | |||
569 | """Gets the Surface that we are drawing on in its current state.""" | 598 | """Gets the Surface that we are drawing on in its current state.""" |
570 | return self.surface | 599 | return self.surface |
571 | 600 | ||
572 | def _rect_common(self, x, y, width, height, color, thickness, do_snap=True): | 601 | def _rect_common(self, x, y, width, height, color, thickness, clip_side=None, do_snap=True): |
602 | EXTRA_FACTOR = 2.0 | ||
603 | |||
573 | x, y, width, height = self.scaled(x, y, width, height) | 604 | x, y, width, height = self.scaled(x, y, width, height) |
574 | x, y = self.surface.get_real_coor(x, y) | 605 | x, y = self.surface.get_real_coor(x, y) |
606 | max_width = self.surface.width + EXTRA_FACTOR * thickness | ||
607 | max_height = self.surface.height + EXTRA_FACTOR * thickness | ||
608 | |||
609 | # if dimensions are really large this can cause Cairo problems -- | ||
610 | # so clip it to the size of the surface, which is the only part we see anyway | ||
611 | if x < 0: | ||
612 | width += x | ||
613 | x = 0 | ||
614 | if y < 0: | ||
615 | height += y | ||
616 | y = 0 | ||
617 | if width > max_width: | ||
618 | width = max_width | ||
619 | if height > max_height: | ||
620 | height = max_height | ||
621 | |||
575 | if do_snap: | 622 | if do_snap: |
576 | self.surface.ctx.rectangle(snap(x), snap(y), width, height) | 623 | x = snap(x) |
624 | y = snap(y) | ||
625 | |||
626 | if clip_side == AlignMode.LEFT: | ||
627 | self.surface.ctx.move_to(x, y) | ||
628 | self.surface.ctx.line_to(x + width, y) | ||
629 | self.surface.ctx.line_to(x + width, y + height) | ||
630 | self.surface.ctx.line_to(x, y + height) | ||
631 | elif clip_side == AlignMode.RIGHT: | ||
632 | self.surface.ctx.move_to(x + width, y) | ||
633 | self.surface.ctx.line_to(x, y) | ||
634 | self.surface.ctx.line_to(x, y + height) | ||
635 | self.surface.ctx.line_to(x + width, y + height) | ||
577 | else: | 636 | else: |
637 | # don't clip one edge of the rectangle -- just draw a Cairo rectangle | ||
578 | self.surface.ctx.rectangle(x, y, width, height) | 638 | self.surface.ctx.rectangle(x, y, width, height) |
579 | 639 | ||
580 | self.surface.ctx.set_line_width(thickness * self.scale) | 640 | self.surface.ctx.set_line_width(thickness * self.scale) |
581 | self.surface.ctx.set_source_rgb(color[0], color[1], color[2]) | 641 | self.surface.ctx.set_source_rgb(color[0], color[1], color[2]) |
582 | 642 | ||
583 | def draw_rect(self, x, y, width, height, color, thickness, do_snap=True): | 643 | def draw_rect(self, x, y, width, height, color, thickness, clip_side=None, do_snap=True): |
584 | self._rect_common(x, y, width, height, color, thickness, do_snap) | 644 | self._rect_common(x, y, width, height, color, thickness, clip_side, do_snap) |
585 | self.surface.ctx.stroke() | 645 | self.surface.ctx.stroke() |
586 | 646 | ||
587 | def fill_rect(self, x, y, width, height, color, do_snap=True): | 647 | def fill_rect(self, x, y, width, height, color, do_snap=True): |
@@ -605,7 +665,7 @@ class CairoCanvas(Canvas): | |||
605 | if do_snap: | 665 | if do_snap: |
606 | self.surface.ctx.rectangle(snap(x), snap(y), width, height) | 666 | self.surface.ctx.rectangle(snap(x), snap(y), width, height) |
607 | else: | 667 | else: |
608 | self.surface.ctx.rectangle(snap(x), snap(y), width, height) | 668 | self.surface.ctx.rectangle(x, y, width, height) |
609 | self.surface.ctx.fill() | 669 | self.surface.ctx.fill() |
610 | 670 | ||
611 | def draw_line(self, p0, p1, color, thickness, do_snap=True): | 671 | def draw_line(self, p0, p1, color, thickness, do_snap=True): |
@@ -625,7 +685,9 @@ class CairoCanvas(Canvas): | |||
625 | self.surface.ctx.stroke() | 685 | self.surface.ctx.stroke() |
626 | 686 | ||
627 | def _polyline_common(self, coor_list, color, thickness, do_snap=True): | 687 | def _polyline_common(self, coor_list, color, thickness, do_snap=True): |
628 | real_coor_list = [self.surface.get_real_coor(coor[0], coor[1]) for coor in coor_list] | 688 | scaled_coor_list = [self.scaled(coor[0], coor[1]) for coor in coor_list] |
689 | real_coor_list = [self.surface.get_real_coor(coor[0], coor[1]) for coor in scaled_coor_list] | ||
690 | |||
629 | self.surface.ctx.move_to(real_coor_list[0][0], real_coor_list[0][1]) | 691 | self.surface.ctx.move_to(real_coor_list[0][0], real_coor_list[0][1]) |
630 | if do_snap: | 692 | if do_snap: |
631 | for i in range(0, len(real_coor_list)): | 693 | for i in range(0, len(real_coor_list)): |
@@ -634,7 +696,7 @@ class CairoCanvas(Canvas): | |||
634 | for coor in real_coor_list[1:]: | 696 | for coor in real_coor_list[1:]: |
635 | self.surface.ctx.line_to(coor[0], coor[1]) | 697 | self.surface.ctx.line_to(coor[0], coor[1]) |
636 | 698 | ||
637 | self.surface.ctx.set_line_width(thickness) | 699 | self.surface.ctx.set_line_width(thickness * self.scale) |
638 | self.surface.ctx.set_source_rgb(color[0], color[1], color[2]) | 700 | self.surface.ctx.set_source_rgb(color[0], color[1], color[2]) |
639 | 701 | ||
640 | def draw_polyline(self, coor_list, color, thickness, do_snap=True): | 702 | def draw_polyline(self, coor_list, color, thickness, do_snap=True): |
@@ -656,7 +718,7 @@ class CairoCanvas(Canvas): | |||
656 | self.surface.ctx.set_source_rgb(0.0, 0.0, 0.0) | 718 | self.surface.ctx.set_source_rgb(0.0, 0.0, 0.0) |
657 | 719 | ||
658 | self.surface.ctx.select_font_face(fopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) | 720 | self.surface.ctx.select_font_face(fopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) |
659 | self.surface.ctx.set_font_size(fopts.size) | 721 | self.surface.ctx.set_font_size(fopts.size * self.scale) |
660 | 722 | ||
661 | fe = self.surface.ctx.font_extents() | 723 | fe = self.surface.ctx.font_extents() |
662 | f_ascent, f_descent, f_height = fe[:3] | 724 | f_ascent, f_descent, f_height = fe[:3] |
@@ -728,6 +790,7 @@ class SelectableRegion(object): | |||
728 | self.width = width | 790 | self.width = width |
729 | self.height = height | 791 | self.height = height |
730 | self.event = event | 792 | self.event = event |
793 | self.scale = 1.0 | ||
731 | 794 | ||
732 | def get_dimensions(self): | 795 | def get_dimensions(self): |
733 | return (self.x, self.y, self.width, self.height) | 796 | return (self.x, self.y, self.width, self.height) |
@@ -735,643 +798,12 @@ class SelectableRegion(object): | |||
735 | def get_event(self): | 798 | def get_event(self): |
736 | return self.event | 799 | return self.event |
737 | 800 | ||
738 | def intersects(self, x, y, width, height): | 801 | def set_scale(self, scale): |
739 | return x <= self.x + self.width and x + width >= self.x and y <= self.y + self.height and y + height >= self.y | 802 | self.scale = scale |
740 | |||
741 | class Graph(object): | ||
742 | DEF_BAR_PLIST = [Pattern([(0.0, 0.9, 0.9)]), Pattern([(0.9, 0.3, 0.0)]), Pattern([(0.9, 0.7, 0.0)]), | ||
743 | Pattern([(0.0, 0.0, 0.8)]), Pattern([(0.0, 0.2, 0.9)]), Pattern([(0.0, 0.6, 0.6)]), | ||
744 | Pattern([(0.75, 0.75, 0.75)])] | ||
745 | 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), | ||
746 | (0.3, 0.0, 0.3)] | ||
747 | |||
748 | def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, attrs=GraphFormat(), | ||
749 | item_clist=DEF_ITEM_CLIST, bar_plist=DEF_BAR_PLIST): | ||
750 | # deal with possibly blank schedules | ||
751 | if start_time is None: | ||
752 | start_time = 0 | ||
753 | if end_time is None: | ||
754 | end_time = 0 | ||
755 | |||
756 | if start_time > end_time: | ||
757 | raise ValueError("Litmus is not a time machine") | ||
758 | |||
759 | self.attrs = attrs | ||
760 | self.start_time = start_time | ||
761 | self.end_time = end_time | ||
762 | self.y_item_list = y_item_list | ||
763 | self.num_maj = int(math.ceil((self.end_time - self.start_time) * 1.0 / self.attrs.time_per_maj)) + 1 | ||
764 | |||
765 | width = self.num_maj * self.attrs.maj_sep + GraphFormat.X_AXIS_MEASURE_OFS + GraphFormat.WIDTH_PAD | ||
766 | height = (len(self.y_item_list) + 1) * self.attrs.y_item_size + GraphFormat.HEIGHT_PAD | ||
767 | |||
768 | # We need to stretch the width in order to fit the y-axis labels. To do this we need | ||
769 | # the extents information, but we haven't set up a surface yet, so we just use a | ||
770 | # temporary one. | ||
771 | extra_width = 0.0 | ||
772 | dummy_surface = surface.__class__() | ||
773 | dummy_surface.renew(10, 10) | ||
774 | |||
775 | dummy_surface.ctx.select_font_face(self.attrs.item_fopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) | ||
776 | dummy_surface.ctx.set_font_size(self.attrs.item_fopts.size) | ||
777 | for item in self.y_item_list: | ||
778 | dummy_surface.ctx.set_source_rgb(0.0, 0.0, 0.0) | ||
779 | te = dummy_surface.ctx.text_extents(item) | ||
780 | cur_width = te[2] | ||
781 | if cur_width > extra_width: | ||
782 | extra_width = cur_width | ||
783 | |||
784 | width += extra_width | ||
785 | |||
786 | self.origin = (extra_width + GraphFormat.WIDTH_PAD / 2.0, height - GraphFormat.HEIGHT_PAD / 2.0) | ||
787 | |||
788 | self.width = width | ||
789 | self.height = height | ||
790 | |||
791 | #if surface.ctx is None: | ||
792 | # surface.renew(width, height) | ||
793 | |||
794 | self.canvas = CanvasType(width, height, item_clist, bar_plist, surface) | ||
795 | |||
796 | def get_selected_regions(self, real_x, real_y, width, height): | ||
797 | return self.canvas.get_selected_regions(real_x, real_y, width, height) | ||
798 | |||
799 | def get_width(self): | ||
800 | return self.width | ||
801 | |||
802 | def get_height(self): | ||
803 | return self.height | ||
804 | |||
805 | def get_origin(self): | ||
806 | return self.origin | ||
807 | |||
808 | def get_attrs(self): | ||
809 | return self.attrs | ||
810 | |||
811 | def add_sel_region(self, region): | ||
812 | self.canvas.add_sel_region(region) | ||
813 | |||
814 | def get_sel_region(self, event): | ||
815 | return self.canvas.get_sel_region(event) | ||
816 | |||
817 | def update_view(self, x, y, width, height, ctx): | ||
818 | """Proxy into the surface's pan.""" | ||
819 | self.canvas.surface.pan(x, y, width, height) | ||
820 | self.canvas.surface.change_ctx(ctx) | ||
821 | |||
822 | def _recomp_min_max(self, start_time, end_time, start_item, end_item): | ||
823 | if self.min_time is None or start_time < self.min_time: | ||
824 | self.min_time = start_time | ||
825 | if self.max_time is None or end_time > self.max_time: | ||
826 | self.max_time = end_time | ||
827 | if self.min_item is None or start_item < self.min_item: | ||
828 | self.min_item = start_item | ||
829 | if self.max_item is None or end_item > self.max_item: | ||
830 | self.max_item = end_item | ||
831 | |||
832 | def _get_time_xpos(self, time): | ||
833 | """get x so that x is at instant ``time'' on the graph""" | ||
834 | return self.origin[0] + GraphFormat.X_AXIS_MEASURE_OFS + 1.0 * (time - self.start_time) / self.attrs.time_per_maj * self.attrs.maj_sep | ||
835 | |||
836 | def _get_item_ypos(self, item_no): | ||
837 | """get y so that y is where the top of a bar would be in item #n's area""" | ||
838 | return self.origin[1] - self._get_y_axis_height() + self.attrs.y_item_size * (item_no + 0.5 - GraphFormat.BAR_SIZE_FACTOR / 2.0) | ||
839 | |||
840 | def _get_bar_width(self, start_time, end_time): | ||
841 | return 1.0 * (end_time - start_time) / self.attrs.time_per_maj * self.attrs.maj_sep | ||
842 | |||
843 | def _get_bar_height(self): | ||
844 | return self.attrs.y_item_size * GraphFormat.BAR_SIZE_FACTOR | ||
845 | |||
846 | def _get_mini_bar_height(self): | ||
847 | return self.attrs.y_item_size * GraphFormat.MINI_BAR_SIZE_FACTOR | ||
848 | |||
849 | def _get_mini_bar_ofs(self): | ||
850 | return self.attrs.y_item_size * (GraphFormat.MINI_BAR_SIZE_FACTOR + GraphFormat.BAR_MINI_BAR_GAP_FACTOR) | ||
851 | |||
852 | def _get_y_axis_height(self): | ||
853 | return (len(self.y_item_list) + 1) * self.attrs.y_item_size | ||
854 | |||
855 | def _get_bottom_tick(self, time): | ||
856 | return int(math.floor((time - self.start_time) / self.attrs.time_per_maj)) | ||
857 | |||
858 | def _get_top_tick(self, time): | ||
859 | return int(math.ceil((time - self.start_time) / self.attrs.time_per_maj)) | ||
860 | |||
861 | def get_surface(self): | ||
862 | """Gets the underlying surface.""" | ||
863 | return self.canvas.get_surface() | ||
864 | |||
865 | def xcoor_to_time(self, x): | ||
866 | #x = self.origin[0] + GraphFormat.X_AXIS_MEASURE_OFS + (time - self.start) / self.attrs.time_per_maj * self.attrs.maj_sep | ||
867 | return (x - self.origin[0] - GraphFormat.X_AXIS_MEASURE_OFS) / self.attrs.maj_sep \ | ||
868 | * self.attrs.time_per_maj + self.start_time | ||
869 | |||
870 | def ycoor_to_item_no(self, y): | ||
871 | return int((y - self.origin[1] + self._get_y_axis_height()) // self.attrs.y_item_size) | ||
872 | |||
873 | def get_offset_params(self, real_x, real_y, width, height): | ||
874 | start_time = self.xcoor_to_time(self.canvas.surface.virt_x + real_x) | ||
875 | end_time = self.xcoor_to_time(self.canvas.surface.virt_x + real_x + width) | ||
876 | |||
877 | start_item = self.ycoor_to_item_no(self.canvas.surface.virt_y + real_y) | ||
878 | end_item = 2 + self.ycoor_to_item_no(self.canvas.surface.virt_y + real_y + height) | ||
879 | 803 | ||
880 | return (start_time, end_time, start_item, end_item) | 804 | def intersects(self, x, y, width, height): |
881 | 805 | return x <= (self.x + self.width) * self.scale \ | |
882 | def draw_skeleton(self, start_time, end_time, start_item, end_item): | 806 | and x + width >= self.x * self.scale \ |
883 | self.draw_grid_at_time(start_time, end_time, start_item, end_item) | 807 | and y <= (self.y + self.height) * self.scale \ |
884 | self.draw_x_axis_with_labels_at_time(start_time, end_time) | 808 | and y + height >= self.y * self.scale |
885 | self.draw_y_axis_with_labels() | ||
886 | |||
887 | def render_surface(self, sched, regions, selectable=False): | ||
888 | raise NotImplementedError | ||
889 | |||
890 | def render_all(self, schedule): | ||
891 | raise NotImplementedError | ||
892 | |||
893 | def render_events(self, event_list): | ||
894 | for layer in Canvas.LAYERS: | ||
895 | prev_events = {} | ||
896 | for event in event_list: | ||
897 | event.render(self, layer, prev_events) | ||
898 | |||
899 | def draw_axes(self, x_axis_label, y_axis_label): | ||
900 | """Draws and labels the axes according to the parameters that we were initialized | ||
901 | with.""" | ||
902 | self.draw_grid_at_time(self.start_time, self.end_time, 0, len(self.attrs.y_item_list) - 1) | ||
903 | |||
904 | self.canvas.draw_x_axis(self.origin[0], self.origin[1], self.num_maj, self.attrs.maj_sep, self.attrs.min_per_maj) | ||
905 | self.canvas.draw_y_axis(self.origin[0], self.origin[1], self._get_y_axis_height()) | ||
906 | self.canvas.draw_x_axis_labels(self.origin[0], self.origin[1], 0, self.num_maj - 1,\ | ||
907 | self.attrs.maj_sep, self.attrs.min_per_maj, self.start_time, \ | ||
908 | self.attrs.time_per_maj, self.attrs.show_min, self.attrs.majfopts, self.attrs.minfopts) | ||
909 | self.canvas.draw_y_axis_labels(self.origin[0], self.origin[1], self._get_y_axis_height(), self.y_item_list, \ | ||
910 | self.attrs.y_item_size, self.attrs.item_fopts) | ||
911 | |||
912 | def draw_grid_at_time(self, start_time, end_time, start_item, end_item): | ||
913 | """Draws the grid, but only in a certain time and item range.""" | ||
914 | start_tick = max(0, self._get_bottom_tick(start_time)) | ||
915 | end_tick = min(self.num_maj - 1, self._get_top_tick(end_time)) | ||
916 | |||
917 | start_item = max(0, start_item) | ||
918 | end_item = min(len(self.y_item_list), end_item) | ||
919 | |||
920 | self.canvas.draw_grid(self.origin[0], self.origin[1], self._get_y_axis_height(), | ||
921 | start_tick, end_tick, start_item, end_item, self.attrs.maj_sep, self.attrs.y_item_size, \ | ||
922 | self.attrs.min_per_maj, True) | ||
923 | |||
924 | def draw_x_axis_with_labels_at_time(self, start_time, end_time): | ||
925 | start_tick = max(0, self._get_bottom_tick(start_time)) | ||
926 | end_tick = min(self.num_maj - 1, self._get_top_tick(end_time)) | ||
927 | |||
928 | self.canvas.draw_x_axis(self.origin[0], self.origin[1], start_tick, end_tick, \ | ||
929 | self.attrs.maj_sep, self.attrs.min_per_maj) | ||
930 | self.canvas.draw_x_axis_labels(self.origin[0], self.origin[1], start_tick, \ | ||
931 | end_tick, self.attrs.maj_sep, self.attrs.min_per_maj, | ||
932 | self.start_time + start_tick * self.attrs.time_per_maj, | ||
933 | self.attrs.time_per_maj, False) | ||
934 | |||
935 | def draw_y_axis_with_labels(self): | ||
936 | self.canvas.draw_y_axis(self.origin[0], self.origin[1], self._get_y_axis_height()) | ||
937 | self.canvas.draw_y_axis_labels(self.origin[0], self.origin[1], self._get_y_axis_height(), \ | ||
938 | self.y_item_list, self.attrs.y_item_size) | ||
939 | |||
940 | def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): | ||
941 | """Draws a suspension symbol for a certain task at an instant in time.""" | ||
942 | raise NotImplementedError | ||
943 | |||
944 | def add_sel_suspend_triangle_at_time(self, time, task_no, cpu_no, event): | ||
945 | """Same as above, except instead of drawing adds a selectable region at | ||
946 | a certain time.""" | ||
947 | raise NotImplementedError | ||
948 | |||
949 | def draw_resume_triangle_at_time(self, time, task_no, cpu_no, selected=False): | ||
950 | """Draws a resumption symbol for a certain task at an instant in time.""" | ||
951 | raise NotImplementedError | ||
952 | |||
953 | def add_sel_resume_triangle_at_time(self, time, task_no, cpu_no, event): | ||
954 | """Same as above, except instead of drawing adds a selectable region at | ||
955 | a certain time.""" | ||
956 | raise NotImplementedError | ||
957 | |||
958 | def draw_completion_marker_at_time(self, time, task_no, cpu_no, selected=False): | ||
959 | """Draws a completion marker for a certain task at an instant in time.""" | ||
960 | raise NotImplementedError | ||
961 | |||
962 | def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event): | ||
963 | """Same as above, except instead of drawing adds a selectable region at | ||
964 | a certain time.""" | ||
965 | raise NotImplementedError | ||
966 | |||
967 | def draw_release_arrow_at_time(self, time, task_no, job_no, selected=False): | ||
968 | """Draws a release arrow at a certain time for some task and job""" | ||
969 | raise NotImplementedError | ||
970 | |||
971 | def add_sel_release_arrow_at_time(self, time, task_no, event): | ||
972 | """Same as above, except instead of drawing adds a selectable region at | ||
973 | a certain time.""" | ||
974 | raise NotImplementedError | ||
975 | |||
976 | def draw_deadline_arrow_at_time(self, time, task_no, job_no, selected=False): | ||
977 | """Draws a deadline arrow at a certain time for some task and job""" | ||
978 | raise NotImplementedError | ||
979 | |||
980 | def add_sel_deadline_arrow_at_time(self, time, task_no, event): | ||
981 | """Same as above, except instead of drawing adds a selectable region at | ||
982 | a certain time.""" | ||
983 | raise NotImplementedError | ||
984 | |||
985 | def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None): | ||
986 | """Draws a bar over a certain time period for some task, optionally labelling it.""" | ||
987 | raise NotImplementedError | ||
988 | |||
989 | def add_sel_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
990 | """Same as above, except instead of drawing adds a selectable region at | ||
991 | a certain time.""" | ||
992 | raise NotImplementedError | ||
993 | |||
994 | def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, clip_side=None, job_no=None): | ||
995 | """Draws a mini bar over a certain time period for some task, optionally labelling it.""" | ||
996 | raise NotImplementedError | ||
997 | |||
998 | def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
999 | """Same as above, except instead of drawing adds a selectable region at | ||
1000 | a certain time.""" | ||
1001 | raise NotImplementedError | ||
1002 | |||
1003 | class TaskGraph(Graph): | ||
1004 | def render_surface(self, sched, regions, selectable=False): | ||
1005 | events_to_render = {} | ||
1006 | for layer in Canvas.LAYERS: | ||
1007 | events_to_render[layer] = {} | ||
1008 | |||
1009 | for region in regions: | ||
1010 | x, y, width, height = region | ||
1011 | if not selectable: | ||
1012 | self.canvas.whiteout(x, y, width, height) | ||
1013 | else: | ||
1014 | self.canvas.clear_selectable_regions(x, y, width, height) | ||
1015 | |||
1016 | self.min_time, self.max_time, self.min_item, self.max_item = None, None, None, None | ||
1017 | for region in regions: | ||
1018 | x, y, width, height = region | ||
1019 | start_time, end_time, start_item, end_item = self.get_offset_params(x, y, width, height) | ||
1020 | self._recomp_min_max(start_time, end_time, start_item, end_item) | ||
1021 | |||
1022 | for event in sched.get_time_slot_array().iter_over_period( | ||
1023 | start_time, end_time, start_item, end_item, | ||
1024 | schedule.TimeSlotArray.TASK_LIST, schedule.EVENT_LIST): | ||
1025 | events_to_render[event.get_layer()][event] = None | ||
1026 | |||
1027 | if not selectable: | ||
1028 | self.draw_skeleton(self.min_time, self.max_time, | ||
1029 | self.min_item, self.max_item) | ||
1030 | |||
1031 | #if not selectable: | ||
1032 | # for layer in events_to_render: | ||
1033 | # print 'task render on layer', layer, ':', [str(e) for e in events_to_render[layer].keys()] | ||
1034 | |||
1035 | for layer in Canvas.LAYERS: | ||
1036 | prev_events = {} | ||
1037 | for event in events_to_render[layer]: | ||
1038 | event.render(self, layer, prev_events, selectable) | ||
1039 | |||
1040 | def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): | ||
1041 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
1042 | x = self._get_time_xpos(time) | ||
1043 | y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
1044 | self.canvas.draw_suspend_triangle(x, y, height, selected) | ||
1045 | |||
1046 | def add_sel_suspend_triangle_at_time(self, time, task_no, cpu_no, event): | ||
1047 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
1048 | x = self._get_time_xpos(time) | ||
1049 | y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
1050 | |||
1051 | self.canvas.add_sel_suspend_triangle(x, y, height, event) | ||
1052 | |||
1053 | def draw_resume_triangle_at_time(self, time, task_no, cpu_no, selected=False): | ||
1054 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
1055 | x = self._get_time_xpos(time) | ||
1056 | y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
1057 | |||
1058 | self.canvas.draw_resume_triangle(x, y, height, selected) | ||
1059 | |||
1060 | def add_sel_resume_triangle_at_time(self, time, task_no, cpu_no, event): | ||
1061 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
1062 | x = self._get_time_xpos(time) | ||
1063 | y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
1064 | |||
1065 | self.canvas.add_sel_resume_triangle(x, y, height, event) | ||
1066 | |||
1067 | def draw_completion_marker_at_time(self, time, task_no, cpu_no, selected=False): | ||
1068 | height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR | ||
1069 | x = self._get_time_xpos(time) | ||
1070 | y = self._get_item_ypos(task_no) + self._get_bar_height() - height | ||
1071 | |||
1072 | self.canvas.draw_completion_marker(x, y, height, selected) | ||
1073 | |||
1074 | def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event): | ||
1075 | height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR | ||
1076 | |||
1077 | x = self._get_time_xpos(time) | ||
1078 | y = self._get_item_ypos(task_no) + self._get_bar_height() - height | ||
1079 | |||
1080 | self.canvas.add_sel_completion_marker(x, y, height, event) | ||
1081 | |||
1082 | def draw_release_arrow_at_time(self, time, task_no, job_no=None, selected=False): | ||
1083 | height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR | ||
1084 | |||
1085 | x = self._get_time_xpos(time) | ||
1086 | y = self._get_item_ypos(task_no) + self._get_bar_height() - height | ||
1087 | |||
1088 | self.canvas.draw_release_arrow_big(x, y, height, selected) | ||
1089 | |||
1090 | def add_sel_release_arrow_at_time(self, time, task_no, event): | ||
1091 | height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR | ||
1092 | |||
1093 | x = self._get_time_xpos(time) | ||
1094 | y = self._get_item_ypos(task_no) + self._get_bar_height() - height | ||
1095 | |||
1096 | self.canvas.add_sel_release_arrow_big(x, y, height, event) | ||
1097 | |||
1098 | def draw_deadline_arrow_at_time(self, time, task_no, job_no=None, selected=False): | ||
1099 | height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR | ||
1100 | |||
1101 | x = self._get_time_xpos(time) | ||
1102 | y = self._get_item_ypos(task_no) | ||
1103 | |||
1104 | self.canvas.draw_deadline_arrow_big(x, y, height, selected) | ||
1105 | |||
1106 | def add_sel_deadline_arrow_at_time(self, time, task_no, event): | ||
1107 | height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR | ||
1108 | |||
1109 | x = self._get_time_xpos(time) | ||
1110 | y = self._get_item_ypos(task_no) | ||
1111 | |||
1112 | self.canvas.add_sel_deadline_arrow_big(x, y, height, event) | ||
1113 | |||
1114 | def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None, selected=False): | ||
1115 | if start_time > end_time: | ||
1116 | raise ValueError("Litmus is not a time machine") | ||
1117 | |||
1118 | x = self._get_time_xpos(start_time) | ||
1119 | y = self._get_item_ypos(task_no) | ||
1120 | width = self._get_bar_width(start_time, end_time) | ||
1121 | height = self._get_bar_height() | ||
1122 | |||
1123 | self.canvas.draw_bar(x, y, width, height, cpu_no, clip_side, selected) | ||
1124 | |||
1125 | # if a job number is specified, we want to draw a superscript and subscript for the task and job number, respectively | ||
1126 | if job_no is not None: | ||
1127 | x += GraphFormat.BAR_LABEL_OFS | ||
1128 | y += self.attrs.y_item_size * GraphFormat.BAR_SIZE_FACTOR / 2.0 | ||
1129 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
1130 | GraphFormat.DEF_FOPTS_BAR, GraphFormat.DEF_FOPTS_BAR_SSCRIPT, AlignMode.LEFT, AlignMode.CENTER) | ||
1131 | |||
1132 | def add_sel_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
1133 | if start_time > end_time: | ||
1134 | raise ValueError("Litmus is not a time machine") | ||
1135 | |||
1136 | x = self._get_time_xpos(start_time) | ||
1137 | y = self._get_item_ypos(task_no) | ||
1138 | width = self._get_bar_width(start_time, end_time) | ||
1139 | height = self._get_bar_height() | ||
1140 | |||
1141 | self.canvas.add_sel_bar(x, y, width, height, event) | ||
1142 | |||
1143 | def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None, selected=False): | ||
1144 | if start_time > end_time: | ||
1145 | raise ValueError("Litmus is not a time machine") | ||
1146 | |||
1147 | x = self._get_time_xpos(start_time) | ||
1148 | y = self._get_item_ypos(task_no) - self._get_mini_bar_ofs() | ||
1149 | width = self._get_bar_width(start_time, end_time) | ||
1150 | height = self._get_mini_bar_height() | ||
1151 | |||
1152 | self.canvas.draw_mini_bar(x, y, width, height, Canvas.NULL_PATTERN, clip_side, selected) | ||
1153 | |||
1154 | if job_no is not None: | ||
1155 | x += GraphFormat.MINI_BAR_LABEL_OFS | ||
1156 | y += self.attrs.y_item_size * GraphFormat.MINI_BAR_SIZE_FACTOR / 2.0 | ||
1157 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
1158 | GraphFormat.DEF_FOPTS_MINI_BAR, GraphFormat.DEF_FOPTS_MINI_BAR_SSCRIPT, AlignMode.LEFT, AlignMode.CENTER) | ||
1159 | |||
1160 | def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
1161 | x = self._get_time_xpos(start_time) | ||
1162 | y = self._get_item_ypos(task_no) - self._get_mini_bar_ofs() | ||
1163 | width = self._get_bar_width(start_time, end_time) | ||
1164 | height = self._get_mini_bar_height() | ||
1165 | |||
1166 | self.canvas.add_sel_mini_bar(x, y, width, height, event) | ||
1167 | |||
1168 | class CpuGraph(Graph): | ||
1169 | def render_surface(self, sched, regions, selectable=False): | ||
1170 | BOTTOM_EVENTS = [schedule.ReleaseEvent, schedule.DeadlineEvent, schedule.InversionStartEvent, | ||
1171 | schedule.InversionEndEvent, schedule.InversionDummy] | ||
1172 | TOP_EVENTS = [schedule.SuspendEvent, schedule.ResumeEvent, schedule.CompleteEvent, | ||
1173 | schedule.SwitchAwayEvent, schedule.SwitchToEvent, schedule.IsRunningDummy] | ||
1174 | |||
1175 | events_to_render = {} | ||
1176 | for layer in Canvas.LAYERS: | ||
1177 | events_to_render[layer] = {} | ||
1178 | |||
1179 | self.min_time, self.max_time, self.min_item, self.max_item = None, None, None, None | ||
1180 | for region in regions: | ||
1181 | x, y, width, height = region | ||
1182 | if not selectable: | ||
1183 | #self.canvas.whiteout(x, y, width, height) | ||
1184 | self.canvas.whiteout(0, 0, self.canvas.surface.width, self.canvas.surface.height) | ||
1185 | else: | ||
1186 | self.canvas.clear_selectable_regions(x, y, width, height) | ||
1187 | |||
1188 | for region in regions: | ||
1189 | x, y, width, height = region | ||
1190 | start_time, end_time, start_item, end_item = self.get_offset_params(x, y, width, height) | ||
1191 | self._recomp_min_max(start_time, end_time, start_item, end_item) | ||
1192 | |||
1193 | for event in sched.get_time_slot_array().iter_over_period( | ||
1194 | start_time, end_time, start_item, end_item, | ||
1195 | schedule.TimeSlotArray.CPU_LIST, | ||
1196 | TOP_EVENTS): | ||
1197 | events_to_render[event.get_layer()][event] = None | ||
1198 | |||
1199 | if end_item >= len(self.y_item_list): | ||
1200 | # we are far down enough that we should render the releases and deadlines and inversions, | ||
1201 | # which appear near the x-axis | ||
1202 | x, y, width, height = region | ||
1203 | for event in sched.get_time_slot_array().iter_over_period( | ||
1204 | start_time, end_time, 0, sched.get_num_cpus(), | ||
1205 | schedule.TimeSlotArray.CPU_LIST, | ||
1206 | BOTTOM_EVENTS): | ||
1207 | events_to_render[event.get_layer()][event] = None | ||
1208 | |||
1209 | if not selectable: | ||
1210 | self.draw_skeleton(self.min_time, self.max_time, | ||
1211 | self.min_item, self.max_item) | ||
1212 | |||
1213 | for layer in Canvas.LAYERS: | ||
1214 | prev_events = {} | ||
1215 | for event in events_to_render[layer]: | ||
1216 | event.render(self, layer, prev_events, selectable) | ||
1217 | |||
1218 | def render(self, schedule, start_time=None, end_time=None): | ||
1219 | if end_time < start_time: | ||
1220 | raise ValueError('start must be less than end') | ||
1221 | |||
1222 | if start_time is None: | ||
1223 | start_time = self.start | ||
1224 | if end_time is None: | ||
1225 | end_time = self.end | ||
1226 | start_slot = self.get_time_slot(start_time) | ||
1227 | end_slot = min(len(self.time_slots), self.get_time_slot(end_time) + 1) | ||
1228 | |||
1229 | for layer in Canvas.LAYERS: | ||
1230 | prev_events = {} | ||
1231 | for i in range(start_slot, end_slot): | ||
1232 | for event in self.time_slots[i]: | ||
1233 | event.render(graph, layer, prev_events) | ||
1234 | |||
1235 | def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): | ||
1236 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
1237 | x = self._get_time_xpos(time) | ||
1238 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
1239 | self.canvas.draw_suspend_triangle(x, y, height, selected) | ||
1240 | |||
1241 | def add_sel_suspend_triangle_at_time(self, time, task_no, cpu_no, event): | ||
1242 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
1243 | x = self._get_time_xpos(time) | ||
1244 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
1245 | |||
1246 | self.canvas.add_sel_suspend_triangle(x, y, height, event) | ||
1247 | |||
1248 | def draw_resume_triangle_at_time(self, time, task_no, cpu_no, selected=False): | ||
1249 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
1250 | x = self._get_time_xpos(time) | ||
1251 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
1252 | |||
1253 | self.canvas.draw_resume_triangle(x, y, height, selected) | ||
1254 | |||
1255 | def add_sel_resume_triangle_at_time(self, time, task_no, cpu_no, event): | ||
1256 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
1257 | x = self._get_time_xpos(time) | ||
1258 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
1259 | |||
1260 | self.canvas.add_sel_resume_triangle(x, y, height, event) | ||
1261 | |||
1262 | def draw_completion_marker_at_time(self, time, task_no, cpu_no, selected=False): | ||
1263 | height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR | ||
1264 | x = self._get_time_xpos(time) | ||
1265 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() - height | ||
1266 | |||
1267 | self.canvas.draw_completion_marker(x, y, height, selected) | ||
1268 | |||
1269 | def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event): | ||
1270 | height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR | ||
1271 | |||
1272 | x = self._get_time_xpos(time) | ||
1273 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() - height | ||
1274 | |||
1275 | self.canvas.add_sel_completion_marker(x, y, height, event) | ||
1276 | |||
1277 | def draw_release_arrow_at_time(self, time, task_no, job_no=None, selected=False): | ||
1278 | if job_no is None and task_no is not None: | ||
1279 | raise ValueError("Must specify a job number along with the task number") | ||
1280 | |||
1281 | height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR | ||
1282 | |||
1283 | x = self._get_time_xpos(time) | ||
1284 | y = self.origin[1] - height | ||
1285 | |||
1286 | self.canvas.draw_release_arrow_small(x, y, height, selected) | ||
1287 | |||
1288 | if task_no is not None: | ||
1289 | y -= GraphFormat.ARROW_LABEL_OFS | ||
1290 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
1291 | GraphFormat.DEF_FOPTS_ARROW, GraphFormat.DEF_FOPTS_ARROW_SSCRIPT, \ | ||
1292 | AlignMode.CENTER, AlignMode.BOTTOM) | ||
1293 | |||
1294 | def add_sel_release_arrow_at_time(self, time, task_no, event): | ||
1295 | height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR | ||
1296 | |||
1297 | x = self._get_time_xpos(time) | ||
1298 | y = self.origin[1] - height | ||
1299 | |||
1300 | self.canvas.add_sel_release_arrow_small(x, y, height, event) | ||
1301 | |||
1302 | def draw_deadline_arrow_at_time(self, time, task_no, job_no=None, selected=False): | ||
1303 | if job_no is None and task_no is not None: | ||
1304 | raise ValueError("Must specify a job number along with the task number") | ||
1305 | |||
1306 | height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR | ||
1307 | |||
1308 | x = self._get_time_xpos(time) | ||
1309 | y = self.origin[1] - height | ||
1310 | |||
1311 | self.canvas.draw_deadline_arrow_small(x, y, height, selected) | ||
1312 | |||
1313 | if task_no is not None: | ||
1314 | y -= GraphFormat.ARROW_LABEL_OFS | ||
1315 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
1316 | GraphFormat.DEF_FOPTS_ARROW, GraphFormat.DEF_FOPTS_ARROW_SSCRIPT, \ | ||
1317 | AlignMode.CENTER, AlignMode.BOTTOM) | ||
1318 | |||
1319 | def add_sel_deadline_arrow_at_time(self, time, task_no, event): | ||
1320 | height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR | ||
1321 | |||
1322 | x = self._get_time_xpos(time) | ||
1323 | y = self.origin[1] - height | ||
1324 | |||
1325 | self.canvas.add_sel_deadline_arrow_small(x, y, height, event) | ||
1326 | |||
1327 | def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None, selected=False): | ||
1328 | if start_time > end_time: | ||
1329 | raise ValueError("Litmus is not a time machine") | ||
1330 | |||
1331 | x = self._get_time_xpos(start_time) | ||
1332 | y = self._get_item_ypos(cpu_no) | ||
1333 | width = self._get_bar_width(start_time, end_time) | ||
1334 | height = self._get_bar_height() | ||
1335 | |||
1336 | self.canvas.draw_bar(x, y, width, height, task_no, clip_side, selected) | ||
1337 | |||
1338 | # if a job number is specified, we want to draw a superscript and subscript for the task and job number, respectively | ||
1339 | if job_no is not None: | ||
1340 | x += GraphFormat.BAR_LABEL_OFS | ||
1341 | y += self.attrs.y_item_size * GraphFormat.BAR_SIZE_FACTOR / 2.0 | ||
1342 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
1343 | GraphFormat.DEF_FOPTS_BAR, GraphFormat.DEF_FOPTS_BAR_SSCRIPT, \ | ||
1344 | AlignMode.LEFT, AlignMode.CENTER) | ||
1345 | |||
1346 | def add_sel_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
1347 | x = self._get_time_xpos(start_time) | ||
1348 | y = self._get_item_ypos(cpu_no) | ||
1349 | width = self._get_bar_width(start_time, end_time) | ||
1350 | height = self._get_bar_height() | ||
1351 | |||
1352 | self.canvas.add_sel_bar(x, y, width, height, event) | ||
1353 | |||
1354 | def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None, selected=False): | ||
1355 | if start_time > end_time: | ||
1356 | raise ValueError("Litmus is not a time machine") | ||
1357 | |||
1358 | x = self._get_time_xpos(start_time) | ||
1359 | y = self._get_item_ypos(len(self.y_item_list)) | ||
1360 | width = self._get_bar_width(start_time, end_time) | ||
1361 | height = self._get_mini_bar_height() | ||
1362 | |||
1363 | self.canvas.draw_mini_bar(x, y, width, height, task_no, clip_side, selected) | ||
1364 | |||
1365 | if job_no is not None: | ||
1366 | x += GraphFormat.MINI_BAR_LABEL_OFS | ||
1367 | y += self.attrs.y_item_size * GraphFormat.MINI_BAR_SIZE_FACTOR / 2.0 | ||
1368 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
1369 | GraphFormat.DEF_FOPTS_MINI_BAR, GraphFormat.DEF_FOPTS_MINI_BAR_SSCRIPT, AlignMode.LEFT, AlignMode.CENTER) | ||
1370 | |||
1371 | def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
1372 | x = self._get_time_xpos(start_time) | ||
1373 | y = self._get_item_ypos(len(self.y_item_list)) | ||
1374 | width = self._get_bar_width(start_time, end_time) | ||
1375 | height = self._get_mini_bar_height() | ||
1376 | 809 | ||
1377 | self.canvas.add_sel_mini_bar(x, y, width, height, event) | ||
diff --git a/unit_trace/viz/graph.py b/unit_trace/viz/graph.py new file mode 100644 index 0000000..6982e7c --- /dev/null +++ b/unit_trace/viz/graph.py | |||
@@ -0,0 +1,657 @@ | |||
1 | import util | ||
2 | import schedule | ||
3 | from format import * | ||
4 | from canvas import * | ||
5 | |||
6 | """The higher-level components of the rendering engine. The graph classes deal with more abstract dimensions | ||
7 | than the canvas classes (time and task/cpu number rather than plain coordinates). Also, graphs know how to | ||
8 | update themselves, unlike the Canvas which can only overwrite itself.""" | ||
9 | |||
10 | class Graph(object): | ||
11 | DEF_BAR_PLIST = [Pattern([(0.0, 0.9, 0.9)]), Pattern([(0.9, 0.3, 0.0)]), Pattern([(0.9, 0.7, 0.0)]), | ||
12 | Pattern([(0.0, 0.0, 0.8)]), Pattern([(0.0, 0.2, 0.9)]), Pattern([(0.0, 0.6, 0.6)]), | ||
13 | Pattern([(0.75, 0.75, 0.75)])] | ||
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)] | ||
16 | |||
17 | def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, attrs=GraphFormat(), | ||
18 | item_clist=DEF_ITEM_CLIST, bar_plist=DEF_BAR_PLIST): | ||
19 | # deal with possibly blank schedules | ||
20 | if start_time is None: | ||
21 | start_time = 0 | ||
22 | if end_time is None: | ||
23 | end_time = 0 | ||
24 | |||
25 | if start_time > end_time: | ||
26 | raise ValueError("Litmus is not a time machine") | ||
27 | |||
28 | self.attrs = attrs | ||
29 | self.start_time = start_time | ||
30 | self.end_time = end_time | ||
31 | self.y_item_list = y_item_list | ||
32 | self.num_maj = int(math.ceil((self.end_time - self.start_time) * 1.0 / self.attrs.time_per_maj)) + 1 | ||
33 | |||
34 | width = self.num_maj * self.attrs.maj_sep + GraphFormat.X_AXIS_MEASURE_OFS + GraphFormat.WIDTH_PAD | ||
35 | height = (len(self.y_item_list) + 1) * self.attrs.y_item_size + GraphFormat.HEIGHT_PAD | ||
36 | |||
37 | # We need to stretch the width in order to fit the y-axis labels. To do this we need | ||
38 | # the extents information, but we haven't set up a surface yet, so we just use a | ||
39 | # temporary one. | ||
40 | extra_width = 0.0 | ||
41 | dummy_surface = surface.__class__() | ||
42 | dummy_surface.renew(10, 10) | ||
43 | |||
44 | dummy_surface.ctx.select_font_face(self.attrs.item_fopts.name, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) | ||
45 | dummy_surface.ctx.set_font_size(self.attrs.item_fopts.size) | ||
46 | for item in self.y_item_list: | ||
47 | dummy_surface.ctx.set_source_rgb(0.0, 0.0, 0.0) | ||
48 | te = dummy_surface.ctx.text_extents(item) | ||
49 | cur_width = te[2] | ||
50 | if cur_width > extra_width: | ||
51 | extra_width = cur_width | ||
52 | |||
53 | width += extra_width | ||
54 | |||
55 | self.origin = (extra_width + GraphFormat.WIDTH_PAD / 2.0, height - GraphFormat.HEIGHT_PAD / 2.0) | ||
56 | |||
57 | self.width = width | ||
58 | self.height = height | ||
59 | |||
60 | #if surface.ctx is None: | ||
61 | # surface.renew(width, height) | ||
62 | |||
63 | self.canvas = CanvasType(width, height, item_clist, bar_plist, surface) | ||
64 | |||
65 | def get_selected_regions(self, real_x, real_y, width, height): | ||
66 | return self.canvas.get_selected_regions(real_x, real_y, width, height) | ||
67 | |||
68 | def get_width(self): | ||
69 | return self.width | ||
70 | |||
71 | def get_height(self): | ||
72 | return self.height | ||
73 | |||
74 | def get_origin(self): | ||
75 | return self.origin | ||
76 | |||
77 | def get_attrs(self): | ||
78 | return self.attrs | ||
79 | |||
80 | def add_sel_region(self, region): | ||
81 | self.canvas.add_sel_region(region) | ||
82 | |||
83 | def get_sel_region(self, event): | ||
84 | return self.canvas.get_sel_region(event) | ||
85 | |||
86 | def has_sel_region(self, event): | ||
87 | return self.canvas.has_sel_region(event) | ||
88 | |||
89 | def update_view(self, x, y, width, height, scale, ctx): | ||
90 | """Proxy into the surface's pan.""" | ||
91 | self.canvas.surface.pan(x, y, width, height) | ||
92 | self.canvas.set_scale(scale) | ||
93 | self.canvas.surface.change_ctx(ctx) | ||
94 | |||
95 | 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: | ||
97 | self.min_time = start_time | ||
98 | if self.max_time is None or end_time > self.max_time: | ||
99 | self.max_time = end_time | ||
100 | if self.min_item is None or start_item < self.min_item: | ||
101 | self.min_item = start_item | ||
102 | if self.max_item is None or end_item > self.max_item: | ||
103 | self.max_item = end_item | ||
104 | |||
105 | def _get_time_xpos(self, time): | ||
106 | """get x so that x is at instant ``time'' on the graph""" | ||
107 | return self.origin[0] + GraphFormat.X_AXIS_MEASURE_OFS + 1.0 * (time - self.start_time) / self.attrs.time_per_maj * self.attrs.maj_sep | ||
108 | |||
109 | def _get_item_ypos(self, item_no): | ||
110 | """get y so that y is where the top of a bar would be in item #n's area""" | ||
111 | return self.origin[1] - self._get_y_axis_height() + self.attrs.y_item_size * (item_no + 0.5 - GraphFormat.BAR_SIZE_FACTOR / 2.0) | ||
112 | |||
113 | def _get_bar_width(self, start_time, end_time): | ||
114 | return 1.0 * (end_time - start_time) / self.attrs.time_per_maj * self.attrs.maj_sep | ||
115 | |||
116 | def _get_bar_height(self): | ||
117 | return self.attrs.y_item_size * GraphFormat.BAR_SIZE_FACTOR | ||
118 | |||
119 | def _get_mini_bar_height(self): | ||
120 | return self.attrs.y_item_size * GraphFormat.MINI_BAR_SIZE_FACTOR | ||
121 | |||
122 | def _get_mini_bar_ofs(self): | ||
123 | return self.attrs.y_item_size * (GraphFormat.MINI_BAR_SIZE_FACTOR + GraphFormat.BAR_MINI_BAR_GAP_FACTOR) | ||
124 | |||
125 | def _get_y_axis_height(self): | ||
126 | return (len(self.y_item_list) + 1) * self.attrs.y_item_size | ||
127 | |||
128 | def _get_bottom_tick(self, time): | ||
129 | return int(math.floor((time - self.start_time) / self.attrs.time_per_maj)) | ||
130 | |||
131 | def _get_top_tick(self, time): | ||
132 | return int(math.ceil((time - self.start_time) / self.attrs.time_per_maj)) | ||
133 | |||
134 | def get_surface(self): | ||
135 | """Gets the underlying surface.""" | ||
136 | return self.canvas.get_surface() | ||
137 | |||
138 | def xcoor_to_time(self, x): | ||
139 | #x = self.origin[0] + GraphFormat.X_AXIS_MEASURE_OFS + (time - self.start) / self.attrs.time_per_maj * self.attrs.maj_sep | ||
140 | return (x - self.origin[0] - GraphFormat.X_AXIS_MEASURE_OFS) / self.attrs.maj_sep \ | ||
141 | * self.attrs.time_per_maj + self.start_time | ||
142 | |||
143 | def ycoor_to_item_no(self, y): | ||
144 | return int((y - self.origin[1] + self._get_y_axis_height()) // self.attrs.y_item_size) | ||
145 | |||
146 | def get_offset_params(self, real_x, real_y, width, height): | ||
147 | x_start, y_start = self.canvas.surface.get_virt_coor_unscaled(real_x, real_y) | ||
148 | x_end, y_end = self.canvas.surface.get_virt_coor_unscaled(real_x + width, real_y + height) | ||
149 | |||
150 | start_time = self.xcoor_to_time(x_start) | ||
151 | end_time = self.xcoor_to_time(x_end) | ||
152 | |||
153 | start_item = self.ycoor_to_item_no(y_start) | ||
154 | end_item = 1 + self.ycoor_to_item_no(y_end) | ||
155 | |||
156 | return (start_time, end_time, start_item, end_item) | ||
157 | |||
158 | 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) | ||
160 | self.draw_x_axis_with_labels_at_time(start_time, end_time) | ||
161 | self.draw_y_axis_with_labels() | ||
162 | |||
163 | def render_surface(self, sched, regions, selectable=False): | ||
164 | raise NotImplementedError | ||
165 | |||
166 | def render_all(self, schedule): | ||
167 | raise NotImplementedError | ||
168 | |||
169 | def get_events_to_render(self, sched, regions, selectable=False): | ||
170 | raise NotImplementedError | ||
171 | |||
172 | def render_surface(self, sched, regions, selectable=False): | ||
173 | if not selectable: | ||
174 | self.canvas.whiteout() | ||
175 | else: | ||
176 | self.canvas.clear_selectable_regions() | ||
177 | self.render_events(self.get_events_to_render(sched, regions, selectable), selectable) | ||
178 | |||
179 | def render_events(self, events, selectable=False): | ||
180 | for layer in Canvas.LAYERS: | ||
181 | prev_events = {} | ||
182 | if layer in events: | ||
183 | for event in events[layer]: | ||
184 | event.render(self, layer, prev_events, selectable) | ||
185 | |||
186 | def draw_axes(self, x_axis_label, y_axis_label): | ||
187 | """Draws and labels the axes according to the parameters that we were initialized | ||
188 | with.""" | ||
189 | self.draw_grid_at_time(self.start_time, self.end_time, 0, len(self.attrs.y_item_list) - 1) | ||
190 | |||
191 | self.canvas.draw_x_axis(self.origin[0], self.origin[1], self.num_maj, self.attrs.maj_sep, self.attrs.min_per_maj) | ||
192 | self.canvas.draw_y_axis(self.origin[0], self.origin[1], self._get_y_axis_height()) | ||
193 | self.canvas.draw_x_axis_labels(self.origin[0], self.origin[1], 0, self.num_maj - 1,\ | ||
194 | self.attrs.maj_sep, self.attrs.min_per_maj, self.start_time, \ | ||
195 | self.attrs.time_per_maj, self.attrs.show_min, self.attrs.majfopts, self.attrs.minfopts) | ||
196 | self.canvas.draw_y_axis_labels(self.origin[0], self.origin[1], self._get_y_axis_height(), self.y_item_list, \ | ||
197 | self.attrs.y_item_size, self.attrs.item_fopts) | ||
198 | |||
199 | def draw_grid_at_time(self, start_time, end_time, start_item, end_item): | ||
200 | """Draws the grid, but only in a certain time and item range.""" | ||
201 | start_tick = max(0, self._get_bottom_tick(start_time)) | ||
202 | end_tick = min(self.num_maj - 1, self._get_top_tick(end_time)) | ||
203 | |||
204 | start_item = max(0, start_item) | ||
205 | end_item = min(len(self.y_item_list), end_item) | ||
206 | |||
207 | 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, \ | ||
209 | self.attrs.min_per_maj, True) | ||
210 | |||
211 | def draw_x_axis_with_labels_at_time(self, start_time, end_time): | ||
212 | start_tick = max(0, self._get_bottom_tick(start_time)) | ||
213 | end_tick = min(self.num_maj - 1, self._get_top_tick(end_time)) | ||
214 | |||
215 | self.canvas.draw_x_axis(self.origin[0], self.origin[1], start_tick, end_tick, \ | ||
216 | self.attrs.maj_sep, self.attrs.min_per_maj) | ||
217 | self.canvas.draw_x_axis_labels(self.origin[0], self.origin[1], start_tick, \ | ||
218 | end_tick, self.attrs.maj_sep, self.attrs.min_per_maj, | ||
219 | self.start_time + start_tick * self.attrs.time_per_maj, | ||
220 | self.attrs.time_per_maj, False) | ||
221 | |||
222 | def draw_y_axis_with_labels(self): | ||
223 | 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(), \ | ||
225 | self.y_item_list, self.attrs.y_item_size) | ||
226 | |||
227 | 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.""" | ||
229 | raise NotImplementedError | ||
230 | |||
231 | def add_sel_suspend_triangle_at_time(self, time, task_no, cpu_no, event): | ||
232 | """Same as above, except instead of drawing adds a selectable region at | ||
233 | a certain time.""" | ||
234 | raise NotImplementedError | ||
235 | |||
236 | def draw_resume_triangle_at_time(self, time, task_no, cpu_no, selected=False): | ||
237 | """Draws a resumption symbol for a certain task at an instant in time.""" | ||
238 | raise NotImplementedError | ||
239 | |||
240 | def add_sel_resume_triangle_at_time(self, time, task_no, cpu_no, event): | ||
241 | """Same as above, except instead of drawing adds a selectable region at | ||
242 | a certain time.""" | ||
243 | raise NotImplementedError | ||
244 | |||
245 | def draw_completion_marker_at_time(self, time, task_no, cpu_no, selected=False): | ||
246 | """Draws a completion marker for a certain task at an instant in time.""" | ||
247 | raise NotImplementedError | ||
248 | |||
249 | def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event): | ||
250 | """Same as above, except instead of drawing adds a selectable region at | ||
251 | a certain time.""" | ||
252 | raise NotImplementedError | ||
253 | |||
254 | def draw_release_arrow_at_time(self, time, task_no, job_no, selected=False): | ||
255 | """Draws a release arrow at a certain time for some task and job""" | ||
256 | raise NotImplementedError | ||
257 | |||
258 | def add_sel_release_arrow_at_time(self, time, task_no, event): | ||
259 | """Same as above, except instead of drawing adds a selectable region at | ||
260 | a certain time.""" | ||
261 | raise NotImplementedError | ||
262 | |||
263 | def draw_deadline_arrow_at_time(self, time, task_no, job_no, selected=False): | ||
264 | """Draws a deadline arrow at a certain time for some task and job""" | ||
265 | raise NotImplementedError | ||
266 | |||
267 | def add_sel_deadline_arrow_at_time(self, time, task_no, event): | ||
268 | """Same as above, except instead of drawing adds a selectable region at | ||
269 | a certain time.""" | ||
270 | raise NotImplementedError | ||
271 | |||
272 | def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None): | ||
273 | """Draws a bar over a certain time period for some task, optionally labelling it.""" | ||
274 | raise NotImplementedError | ||
275 | |||
276 | def add_sel_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
277 | """Same as above, except instead of drawing adds a selectable region at | ||
278 | a certain time.""" | ||
279 | raise NotImplementedError | ||
280 | |||
281 | def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, clip_side=None, job_no=None): | ||
282 | """Draws a mini bar over a certain time period for some task, optionally labelling it.""" | ||
283 | raise NotImplementedError | ||
284 | |||
285 | def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
286 | """Same as above, except instead of drawing adds a selectable region at | ||
287 | a certain time.""" | ||
288 | raise NotImplementedError | ||
289 | |||
290 | class TaskGraph(Graph): | ||
291 | def get_events_to_render(self, sched, regions, selectable=False): | ||
292 | slots = {} | ||
293 | |||
294 | self.min_time, self.max_time, self.min_item, self.max_item = None, None, None, None | ||
295 | for region in regions: | ||
296 | x, y, width, height = region | ||
297 | start_time, end_time, start_item, end_item = self.get_offset_params(x, y, width, height) | ||
298 | self._recomp_min_max(start_time, end_time, start_item, end_item) | ||
299 | |||
300 | sched.get_time_slot_array().get_slots(slots, | ||
301 | start_time, end_time, start_item, end_item, | ||
302 | schedule.TimeSlotArray.TASK_LIST) | ||
303 | |||
304 | |||
305 | if not selectable: | ||
306 | self.draw_skeleton(self.min_time, self.max_time, | ||
307 | self.min_item, self.max_item) | ||
308 | |||
309 | events_to_render = {} | ||
310 | for layer in Canvas.LAYERS: | ||
311 | events_to_render[layer] = {} | ||
312 | |||
313 | for event in sched.get_time_slot_array().get_events(slots, | ||
314 | schedule.TimeSlotArray.TASK_LIST, | ||
315 | schedule.EVENT_LIST): | ||
316 | events_to_render[event.get_layer()][event] = None | ||
317 | |||
318 | return events_to_render | ||
319 | |||
320 | def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): | ||
321 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
322 | x = self._get_time_xpos(time) | ||
323 | y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
324 | self.canvas.draw_suspend_triangle(x, y, height, selected) | ||
325 | |||
326 | def add_sel_suspend_triangle_at_time(self, time, task_no, cpu_no, event): | ||
327 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
328 | x = self._get_time_xpos(time) | ||
329 | y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
330 | |||
331 | self.canvas.add_sel_suspend_triangle(x, y, height, event) | ||
332 | |||
333 | def draw_resume_triangle_at_time(self, time, task_no, cpu_no, selected=False): | ||
334 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
335 | x = self._get_time_xpos(time) | ||
336 | y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
337 | |||
338 | self.canvas.draw_resume_triangle(x, y, height, selected) | ||
339 | |||
340 | def add_sel_resume_triangle_at_time(self, time, task_no, cpu_no, event): | ||
341 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
342 | x = self._get_time_xpos(time) | ||
343 | y = self._get_item_ypos(task_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
344 | |||
345 | self.canvas.add_sel_resume_triangle(x, y, height, event) | ||
346 | |||
347 | def draw_completion_marker_at_time(self, time, task_no, cpu_no, selected=False): | ||
348 | height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR | ||
349 | x = self._get_time_xpos(time) | ||
350 | y = self._get_item_ypos(task_no) + self._get_bar_height() - height | ||
351 | |||
352 | self.canvas.draw_completion_marker(x, y, height, selected) | ||
353 | |||
354 | def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event): | ||
355 | height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR | ||
356 | |||
357 | x = self._get_time_xpos(time) | ||
358 | y = self._get_item_ypos(task_no) + self._get_bar_height() - height | ||
359 | |||
360 | self.canvas.add_sel_completion_marker(x, y, height, event) | ||
361 | |||
362 | def draw_release_arrow_at_time(self, time, task_no, job_no=None, selected=False): | ||
363 | height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR | ||
364 | |||
365 | x = self._get_time_xpos(time) | ||
366 | y = self._get_item_ypos(task_no) + self._get_bar_height() - height | ||
367 | |||
368 | self.canvas.draw_release_arrow_big(x, y, height, selected) | ||
369 | |||
370 | def add_sel_release_arrow_at_time(self, time, task_no, event): | ||
371 | height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR | ||
372 | |||
373 | x = self._get_time_xpos(time) | ||
374 | y = self._get_item_ypos(task_no) + self._get_bar_height() - height | ||
375 | |||
376 | self.canvas.add_sel_release_arrow_big(x, y, height, event) | ||
377 | |||
378 | def draw_deadline_arrow_at_time(self, time, task_no, job_no=None, selected=False): | ||
379 | height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR | ||
380 | |||
381 | x = self._get_time_xpos(time) | ||
382 | y = self._get_item_ypos(task_no) | ||
383 | |||
384 | self.canvas.draw_deadline_arrow_big(x, y, height, selected) | ||
385 | |||
386 | def add_sel_deadline_arrow_at_time(self, time, task_no, event): | ||
387 | height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR | ||
388 | |||
389 | x = self._get_time_xpos(time) | ||
390 | y = self._get_item_ypos(task_no) | ||
391 | |||
392 | self.canvas.add_sel_deadline_arrow_big(x, y, height, event) | ||
393 | |||
394 | def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None, selected=False): | ||
395 | if start_time > end_time: | ||
396 | raise ValueError("Litmus is not a time machine") | ||
397 | |||
398 | x = self._get_time_xpos(start_time) | ||
399 | y = self._get_item_ypos(task_no) | ||
400 | width = self._get_bar_width(start_time, end_time) | ||
401 | height = self._get_bar_height() | ||
402 | |||
403 | self.canvas.draw_bar(x, y, width, height, cpu_no, clip_side, selected) | ||
404 | |||
405 | # if a job number is specified, we want to draw a superscript and subscript for the task and job number, respectively | ||
406 | if job_no is not None: | ||
407 | x += GraphFormat.BAR_LABEL_OFS | ||
408 | y += self.attrs.y_item_size * GraphFormat.BAR_SIZE_FACTOR / 2.0 | ||
409 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
410 | GraphFormat.DEF_FOPTS_BAR, GraphFormat.DEF_FOPTS_BAR_SSCRIPT, AlignMode.LEFT, AlignMode.CENTER) | ||
411 | |||
412 | def add_sel_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
413 | if start_time > end_time: | ||
414 | raise ValueError("Litmus is not a time machine") | ||
415 | |||
416 | x = self._get_time_xpos(start_time) | ||
417 | y = self._get_item_ypos(task_no) | ||
418 | width = self._get_bar_width(start_time, end_time) | ||
419 | height = self._get_bar_height() | ||
420 | |||
421 | self.canvas.add_sel_bar(x, y, width, height, event) | ||
422 | |||
423 | def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None, selected=False): | ||
424 | if start_time > end_time: | ||
425 | raise ValueError("Litmus is not a time machine") | ||
426 | |||
427 | x = self._get_time_xpos(start_time) | ||
428 | y = self._get_item_ypos(task_no) - self._get_mini_bar_ofs() | ||
429 | width = self._get_bar_width(start_time, end_time) | ||
430 | height = self._get_mini_bar_height() | ||
431 | |||
432 | self.canvas.draw_mini_bar(x, y, width, height, Canvas.NULL_PATTERN, clip_side, selected) | ||
433 | |||
434 | if job_no is not None: | ||
435 | x += GraphFormat.MINI_BAR_LABEL_OFS | ||
436 | y += self.attrs.y_item_size * GraphFormat.MINI_BAR_SIZE_FACTOR / 2.0 | ||
437 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
438 | GraphFormat.DEF_FOPTS_MINI_BAR, GraphFormat.DEF_FOPTS_MINI_BAR_SSCRIPT, AlignMode.LEFT, AlignMode.CENTER) | ||
439 | |||
440 | def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
441 | x = self._get_time_xpos(start_time) | ||
442 | y = self._get_item_ypos(task_no) - self._get_mini_bar_ofs() | ||
443 | width = self._get_bar_width(start_time, end_time) | ||
444 | height = self._get_mini_bar_height() | ||
445 | |||
446 | self.canvas.add_sel_mini_bar(x, y, width, height, event) | ||
447 | |||
448 | class CpuGraph(Graph): | ||
449 | def get_events_to_render(self, sched, regions, selectable=False): | ||
450 | BOTTOM_EVENTS = [schedule.ReleaseEvent, schedule.DeadlineEvent, schedule.InversionStartEvent, | ||
451 | schedule.InversionEndEvent, schedule.InversionDummy] | ||
452 | TOP_EVENTS = [schedule.SuspendEvent, schedule.ResumeEvent, schedule.CompleteEvent, | ||
453 | schedule.SwitchAwayEvent, schedule.SwitchToEvent, schedule.IsRunningDummy] | ||
454 | |||
455 | if not regions: | ||
456 | return {} | ||
457 | |||
458 | top_slots = {} | ||
459 | bottom_slots = {} | ||
460 | |||
461 | self.min_time, self.max_time, self.min_item, self.max_item = None, None, None, None | ||
462 | for region in regions: | ||
463 | x, y, width, height = region | ||
464 | start_time, end_time, start_item, end_item = self.get_offset_params(x, y, width, height) | ||
465 | self._recomp_min_max(start_time, end_time, start_item, end_item) | ||
466 | |||
467 | sched.get_time_slot_array().get_slots(top_slots, | ||
468 | start_time, end_time, start_item, end_item, | ||
469 | schedule.TimeSlotArray.CPU_LIST) | ||
470 | |||
471 | if end_item >= len(self.y_item_list): | ||
472 | # we are far down enough that we should render the releases and deadlines and inversions, | ||
473 | # which appear near the x-axis | ||
474 | sched.get_time_slot_array().get_slots(bottom_slots, | ||
475 | start_time, end_time, 0, sched.get_num_cpus(), | ||
476 | schedule.TimeSlotArray.CPU_LIST) | ||
477 | |||
478 | if not selectable: | ||
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): | ||
515 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
516 | x = self._get_time_xpos(time) | ||
517 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
518 | self.canvas.draw_suspend_triangle(x, y, height, selected) | ||
519 | |||
520 | def add_sel_suspend_triangle_at_time(self, time, task_no, cpu_no, event): | ||
521 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
522 | x = self._get_time_xpos(time) | ||
523 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
524 | |||
525 | self.canvas.add_sel_suspend_triangle(x, y, height, event) | ||
526 | |||
527 | def draw_resume_triangle_at_time(self, time, task_no, cpu_no, selected=False): | ||
528 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
529 | x = self._get_time_xpos(time) | ||
530 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
531 | |||
532 | self.canvas.draw_resume_triangle(x, y, height, selected) | ||
533 | |||
534 | def add_sel_resume_triangle_at_time(self, time, task_no, cpu_no, event): | ||
535 | height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR | ||
536 | x = self._get_time_xpos(time) | ||
537 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0 | ||
538 | |||
539 | self.canvas.add_sel_resume_triangle(x, y, height, event) | ||
540 | |||
541 | def draw_completion_marker_at_time(self, time, task_no, cpu_no, selected=False): | ||
542 | height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR | ||
543 | x = self._get_time_xpos(time) | ||
544 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() - height | ||
545 | |||
546 | self.canvas.draw_completion_marker(x, y, height, selected) | ||
547 | |||
548 | def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event): | ||
549 | height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR | ||
550 | |||
551 | x = self._get_time_xpos(time) | ||
552 | y = self._get_item_ypos(cpu_no) + self._get_bar_height() - height | ||
553 | |||
554 | self.canvas.add_sel_completion_marker(x, y, height, event) | ||
555 | |||
556 | def draw_release_arrow_at_time(self, time, task_no, job_no=None, selected=False): | ||
557 | if job_no is None and task_no is not None: | ||
558 | raise ValueError("Must specify a job number along with the task number") | ||
559 | |||
560 | height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR | ||
561 | |||
562 | x = self._get_time_xpos(time) | ||
563 | y = self.origin[1] - height | ||
564 | |||
565 | self.canvas.draw_release_arrow_small(x, y, height, selected) | ||
566 | |||
567 | if task_no is not None: | ||
568 | y -= GraphFormat.ARROW_LABEL_OFS | ||
569 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
570 | GraphFormat.DEF_FOPTS_ARROW, GraphFormat.DEF_FOPTS_ARROW_SSCRIPT, \ | ||
571 | AlignMode.CENTER, AlignMode.BOTTOM) | ||
572 | |||
573 | def add_sel_release_arrow_at_time(self, time, task_no, event): | ||
574 | height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR | ||
575 | |||
576 | x = self._get_time_xpos(time) | ||
577 | y = self.origin[1] - height | ||
578 | |||
579 | self.canvas.add_sel_release_arrow_small(x, y, height, event) | ||
580 | |||
581 | def draw_deadline_arrow_at_time(self, time, task_no, job_no=None, selected=False): | ||
582 | if job_no is None and task_no is not None: | ||
583 | raise ValueError("Must specify a job number along with the task number") | ||
584 | |||
585 | height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR | ||
586 | |||
587 | x = self._get_time_xpos(time) | ||
588 | y = self.origin[1] - height | ||
589 | |||
590 | self.canvas.draw_deadline_arrow_small(x, y, height, selected) | ||
591 | |||
592 | if task_no is not None: | ||
593 | y -= GraphFormat.ARROW_LABEL_OFS | ||
594 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
595 | GraphFormat.DEF_FOPTS_ARROW, GraphFormat.DEF_FOPTS_ARROW_SSCRIPT, \ | ||
596 | AlignMode.CENTER, AlignMode.BOTTOM) | ||
597 | |||
598 | def add_sel_deadline_arrow_at_time(self, time, task_no, event): | ||
599 | height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR | ||
600 | |||
601 | x = self._get_time_xpos(time) | ||
602 | y = self.origin[1] - height | ||
603 | |||
604 | self.canvas.add_sel_deadline_arrow_small(x, y, height, event) | ||
605 | |||
606 | def draw_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None, selected=False): | ||
607 | if start_time > end_time: | ||
608 | raise ValueError("Litmus is not a time machine") | ||
609 | |||
610 | x = self._get_time_xpos(start_time) | ||
611 | y = self._get_item_ypos(cpu_no) | ||
612 | width = self._get_bar_width(start_time, end_time) | ||
613 | height = self._get_bar_height() | ||
614 | |||
615 | self.canvas.draw_bar(x, y, width, height, task_no, clip_side, selected) | ||
616 | |||
617 | # if a job number is specified, we want to draw a superscript and subscript for the task and job number, respectively | ||
618 | if job_no is not None: | ||
619 | x += GraphFormat.BAR_LABEL_OFS | ||
620 | y += self.attrs.y_item_size * GraphFormat.BAR_SIZE_FACTOR / 2.0 | ||
621 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
622 | GraphFormat.DEF_FOPTS_BAR, GraphFormat.DEF_FOPTS_BAR_SSCRIPT, \ | ||
623 | AlignMode.LEFT, AlignMode.CENTER) | ||
624 | |||
625 | def add_sel_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
626 | x = self._get_time_xpos(start_time) | ||
627 | y = self._get_item_ypos(cpu_no) | ||
628 | width = self._get_bar_width(start_time, end_time) | ||
629 | height = self._get_bar_height() | ||
630 | |||
631 | self.canvas.add_sel_bar(x, y, width, height, event) | ||
632 | |||
633 | def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, clip_side=None, selected=False): | ||
634 | if start_time > end_time: | ||
635 | raise ValueError("Litmus is not a time machine") | ||
636 | |||
637 | x = self._get_time_xpos(start_time) | ||
638 | y = self._get_item_ypos(len(self.y_item_list)) | ||
639 | width = self._get_bar_width(start_time, end_time) | ||
640 | height = self._get_mini_bar_height() | ||
641 | |||
642 | self.canvas.draw_mini_bar(x, y, width, height, task_no, clip_side, selected) | ||
643 | |||
644 | if job_no is not None: | ||
645 | x += GraphFormat.MINI_BAR_LABEL_OFS | ||
646 | y += self.attrs.y_item_size * GraphFormat.MINI_BAR_SIZE_FACTOR / 2.0 | ||
647 | self.canvas.draw_label_with_sscripts('T', str(task_no), str(job_no), x, y, \ | ||
648 | GraphFormat.DEF_FOPTS_MINI_BAR, GraphFormat.DEF_FOPTS_MINI_BAR_SSCRIPT, AlignMode.LEFT, AlignMode.CENTER) | ||
649 | |||
650 | def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): | ||
651 | x = self._get_time_xpos(start_time) | ||
652 | y = self._get_item_ypos(len(self.y_item_list)) | ||
653 | width = self._get_bar_width(start_time, end_time) | ||
654 | height = self._get_mini_bar_height() | ||
655 | |||
656 | self.canvas.add_sel_mini_bar(x, y, width, height, event) | ||
657 | |||
diff --git a/unit_trace/viz/renderer.py b/unit_trace/viz/renderer.py index d94129c..4b231c4 100644 --- a/unit_trace/viz/renderer.py +++ b/unit_trace/viz/renderer.py | |||
@@ -1,6 +1,6 @@ | |||
1 | #!/usr/bin/python | 1 | #!/usr/bin/python |
2 | from schedule import * | 2 | from schedule import * |
3 | from draw import * | 3 | from graph import * |
4 | 4 | ||
5 | """The renderer, a glue object which converts a schedule to its representation | 5 | """The renderer, a glue object which converts a schedule to its representation |
6 | on a graph.""" | 6 | on a graph.""" |
diff --git a/unit_trace/viz/schedule.py b/unit_trace/viz/schedule.py index f134562..81269fa 100644 --- a/unit_trace/viz/schedule.py +++ b/unit_trace/viz/schedule.py | |||
@@ -5,7 +5,7 @@ the job releases and other events that have occurred for each task. This gives | |||
5 | a high-level representation of a schedule that can be converted to, say, a | 5 | a high-level representation of a schedule that can be converted to, say, a |
6 | graphic.""" | 6 | graphic.""" |
7 | 7 | ||
8 | from draw import * | 8 | from graph import * |
9 | import util | 9 | import util |
10 | 10 | ||
11 | import copy | 11 | import copy |
@@ -97,8 +97,16 @@ class TimeSlotArray(object): | |||
97 | 97 | ||
98 | self._put_event_in_slot(TimeSlotArray.TASK_LIST, task_no, dummy.__class__, 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) | 99 | self._put_event_in_slot(TimeSlotArray.CPU_LIST, cpu, dummy.__class__, slot, dummy) |
100 | 100 | ||
101 | def iter_over_period(self, start, end, start_no, end_no, list_type, event_types): | 101 | def get_events(self, slots, list_type, event_types): |
102 | for type in event_types: | ||
103 | for slot in slots: | ||
104 | for no in slots[slot]: | ||
105 | if slot in self.array[list_type][no][type]: | ||
106 | for event in self.array[list_type][no][type][slot]: | ||
107 | yield event | ||
108 | |||
109 | def get_slots(self, slots, start, end, start_no, end_no, list_type): | ||
102 | if self.array is None: | 110 | if self.array is None: |
103 | return # empty schedule | 111 | return # empty schedule |
104 | 112 | ||
@@ -106,19 +114,17 @@ class TimeSlotArray(object): | |||
106 | raise ValueError('Litmus is not a time machine') | 114 | raise ValueError('Litmus is not a time machine') |
107 | if start_no > end_no: | 115 | if start_no > end_no: |
108 | raise ValueError('start no should be less than end no') | 116 | raise ValueError('start no should be less than end no') |
109 | 117 | ||
110 | start_slot = self.get_time_slot(start) | 118 | start_slot = self.get_time_slot(start) |
111 | end_slot = self.get_time_slot(end) + 2 | 119 | end_slot = self.get_time_slot(end) + 1 |
112 | |||
113 | start_no = max(0, start_no) | 120 | start_no = max(0, start_no) |
114 | end_no = min(self.list_sizes[list_type] - 1, end_no) | 121 | end_no = min(self.list_sizes[list_type] - 1, end_no) |
115 | 122 | ||
116 | for no in range(start_no, end_no + 1): | 123 | for slot in xrange(start_slot, end_slot + 1): |
117 | for type in event_types: | 124 | if slot not in slots: |
118 | for slot in range(start_slot, end_slot): | 125 | slots[slot] = {} |
119 | if slot in self.array[list_type][no][type]: | 126 | for no in xrange(start_no, end_no + 1): |
120 | for event in self.array[list_type][no][type][slot]: | 127 | slots[slot][no] = None |
121 | yield event | ||
122 | 128 | ||
123 | class Schedule(object): | 129 | class Schedule(object): |
124 | """The total schedule (task system), consisting of a certain number of | 130 | """The total schedule (task system), consisting of a certain number of |
@@ -138,16 +144,26 @@ class Schedule(object): | |||
138 | def get_selected(self): | 144 | def get_selected(self): |
139 | return self.selected | 145 | return self.selected |
140 | 146 | ||
141 | def set_selected(self, new_selected): | 147 | def set_selected(self, selected): |
142 | for event in self.selected: | 148 | self.selected = selected |
143 | event.selected = False | 149 | |
144 | for event in new_selected: | 150 | def add_selected(self, selected): |
145 | event.selected = True | 151 | for layer in selected: |
146 | self.selected = new_selected | 152 | if layer not in self.selected: |
147 | 153 | self.selected[layer] = {} | |
148 | def get_selected(self): | 154 | for event in selected[layer]: |
149 | return copy.copy(self.selected) | 155 | if event not in self.selected: |
156 | self.selected[layer][event] = {} | ||
157 | for graph in selected[layer][event]: | ||
158 | self.selected[layer][event][graph] = selected[layer][event][graph] | ||
150 | 159 | ||
160 | def remove_selected(self, selected): | ||
161 | for layer in selected: | ||
162 | if layer in self.selected: | ||
163 | for event in selected[layer]: | ||
164 | if event in self.selected[layer]: | ||
165 | del self.selected[layer][event] | ||
166 | |||
151 | def set_time_params(self, time_per_maj=None): | 167 | def set_time_params(self, time_per_maj=None): |
152 | self.time_per_maj = time_per_maj | 168 | self.time_per_maj = time_per_maj |
153 | if self.time_per_maj is None: | 169 | if self.time_per_maj is None: |
@@ -212,7 +228,13 @@ class Schedule(object): | |||
212 | 228 | ||
213 | def get_num_cpus(self): | 229 | def get_num_cpus(self): |
214 | return self.num_cpus | 230 | return self.num_cpus |
215 | 231 | ||
232 | def deepcopy_selected(selected): | ||
233 | selected_copy = {} | ||
234 | for layer in selected: | ||
235 | selected_copy[layer] = copy.copy(selected[layer]) | ||
236 | return selected_copy | ||
237 | |||
216 | class Task(object): | 238 | class Task(object): |
217 | """Represents a task, including the set of jobs that were run under | 239 | """Represents a task, including the set of jobs that were run under |
218 | this task.""" | 240 | this task.""" |
@@ -313,7 +335,6 @@ class Event(DummyEvent): | |||
313 | def __init__(self, time, cpu): | 335 | def __init__(self, time, cpu): |
314 | super(Event, self).__init__(time, cpu) | 336 | super(Event, self).__init__(time, cpu) |
315 | self.erroneous = False | 337 | self.erroneous = False |
316 | self.selected = False | ||
317 | 338 | ||
318 | def __str__(self): | 339 | def __str__(self): |
319 | return '[Event]' | 340 | return '[Event]' |
@@ -332,7 +353,8 @@ class Event(DummyEvent): | |||
332 | 353 | ||
333 | def is_selected(self): | 354 | def is_selected(self): |
334 | """Returns whether the event has been selected by the user. (needed for rendering)""" | 355 | """Returns whether the event has been selected by the user. (needed for rendering)""" |
335 | return self.selected | 356 | selected = self.get_job().get_task().get_schedule().get_selected() |
357 | return self.get_layer() in selected and self in selected[self.get_layer()] | ||
336 | 358 | ||
337 | def scan(self, cur_cpu, switches): | 359 | def scan(self, cur_cpu, switches): |
338 | """Part of the procedure that walks through all the events and sets | 360 | """Part of the procedure that walks through all the events and sets |
@@ -480,7 +502,7 @@ class SwitchAwayEvent(Event): | |||
480 | 502 | ||
481 | def __str__(self): | 503 | def __str__(self): |
482 | if self.corresp_start_event is None: | 504 | if self.corresp_start_event is None: |
483 | return 'Switch Away (w/o Switch To)' + self._common_str() + 'TIME=' \ | 505 | return 'Switch Away (w/o Switch To)' + self._common_str() + ', TIME=' \ |
484 | + str(self.get_time()) | 506 | + str(self.get_time()) |
485 | return str(self.corresp_start_event) | 507 | return str(self.corresp_start_event) |
486 | 508 | ||
@@ -641,7 +663,7 @@ class InversionEndEvent(ErrorEvent): | |||
641 | 663 | ||
642 | if self.corresp_start_event is None: | 664 | if self.corresp_start_event is None: |
643 | self.erroneous = True | 665 | self.erroneous = True |
644 | print "inversion end was not matched by a corresponding inversion start" | 666 | #print "inversion end was not matched by a corresponding inversion start" |
645 | 667 | ||
646 | super(InversionEndEvent, self).scan(cur_cpu, switches) | 668 | super(InversionEndEvent, self).scan(cur_cpu, switches) |
647 | 669 | ||
diff --git a/unit_trace/viz/viewer.py b/unit_trace/viz/viewer.py index 909da76..9b8502a 100644 --- a/unit_trace/viz/viewer.py +++ b/unit_trace/viz/viewer.py | |||
@@ -17,21 +17,28 @@ class GraphContextMenu(gtk.Menu): | |||
17 | def __init__(self, selected): | 17 | def __init__(self, selected): |
18 | super(GraphContextMenu, self).__init__() | 18 | super(GraphContextMenu, self).__init__() |
19 | 19 | ||
20 | for event in selected: | 20 | if not selected: |
21 | string = str(event) | 21 | item = gtk.MenuItem("(No events selected)") |
22 | if len(string) > GraphContextMenu.MAX_STR_LEN - 3: | 22 | item.set_sensitive(False) |
23 | string = string[:GraphContextMenu.MAX_STR_LEN - 3] + '...' | ||
24 | item = gtk.MenuItem(string) | ||
25 | self.append(item) | 23 | self.append(item) |
26 | item.show() | 24 | item.show() |
27 | 25 | else: | |
26 | for layer in selected: | ||
27 | for event in selected[layer]: | ||
28 | string = str(event) | ||
29 | if len(string) > GraphContextMenu.MAX_STR_LEN - 3: | ||
30 | string = string[:GraphContextMenu.MAX_STR_LEN - 3] + '...' | ||
31 | item = gtk.MenuItem(string) | ||
32 | self.append(item) | ||
33 | item.show() | ||
34 | |||
28 | class GraphArea(gtk.DrawingArea): | 35 | class GraphArea(gtk.DrawingArea): |
29 | HORIZ_PAGE_SCROLL_FACTOR = 10.8 | 36 | HORIZ_PAGE_SCROLL_FACTOR = 10.8 |
30 | HORIZ_STEP_SCROLL_FACTOR = 0.8 | 37 | HORIZ_STEP_SCROLL_FACTOR = 0.8 |
31 | VERT_PAGE_SCROLL_FACTOR = 3.0 | 38 | VERT_PAGE_SCROLL_FACTOR = 3.0 |
32 | VERT_STEP_SCROLL_FACTOR = 0.5 | 39 | VERT_STEP_SCROLL_FACTOR = 0.5 |
33 | 40 | ||
34 | REFRESH_INFLATION_FACTOR = 20.0 | 41 | REFRESH_INFLATION_FACTOR = 4.0 |
35 | 42 | ||
36 | def __init__(self, renderer): | 43 | def __init__(self, renderer): |
37 | super(GraphArea, self).__init__() | 44 | super(GraphArea, self).__init__() |
@@ -42,6 +49,7 @@ class GraphArea(gtk.DrawingArea): | |||
42 | self.cur_y = 0 | 49 | self.cur_y = 0 |
43 | self.width = 0 | 50 | self.width = 0 |
44 | self.height = 0 | 51 | self.height = 0 |
52 | self.scale = 1.0 | ||
45 | 53 | ||
46 | self.set_set_scroll_adjustments_signal('set-scroll-adjustments') | 54 | self.set_set_scroll_adjustments_signal('set-scroll-adjustments') |
47 | 55 | ||
@@ -50,6 +58,7 @@ class GraphArea(gtk.DrawingArea): | |||
50 | 58 | ||
51 | self.band_rect = None | 59 | self.band_rect = None |
52 | self.ctrl_clicked = False | 60 | self.ctrl_clicked = False |
61 | self.last_selected = {} | ||
53 | self.dirtied_regions = [] | 62 | self.dirtied_regions = [] |
54 | 63 | ||
55 | self.connect('expose-event', self.expose) | 64 | self.connect('expose-event', self.expose) |
@@ -62,7 +71,7 @@ class GraphArea(gtk.DrawingArea): | |||
62 | def expose(self, widget, expose_event, data=None): | 71 | def expose(self, widget, expose_event, data=None): |
63 | ctx = widget.window.cairo_create() | 72 | ctx = widget.window.cairo_create() |
64 | graph = self.renderer.get_graph() | 73 | graph = self.renderer.get_graph() |
65 | graph.update_view(self.cur_x, self.cur_y, self.width, self.height, ctx) | 74 | graph.update_view(self.cur_x, self.cur_y, self.width, self.height, self.scale, ctx) |
66 | 75 | ||
67 | # We ourselves didn't update dirtied_regions, so this means that X or the | 76 | # We ourselves didn't update dirtied_regions, so this means that X or the |
68 | # window manager must have caused the expose event. So just update the | 77 | # window manager must have caused the expose event. So just update the |
@@ -91,7 +100,30 @@ class GraphArea(gtk.DrawingArea): | |||
91 | 100 | ||
92 | def get_graph(self): | 101 | def get_graph(self): |
93 | return self.renderer.get_graph() | 102 | return self.renderer.get_graph() |
103 | |||
104 | MIN_ZOOM_OUT = 0.25 | ||
105 | MAX_ZOOM_IN = 4.0 | ||
106 | ZOOM_INCR = 0.25 | ||
107 | |||
108 | def zoom_in(self): | ||
109 | scale = self.scale + GraphArea.ZOOM_INCR | ||
110 | if scale > GraphArea.MAX_ZOOM_IN: | ||
111 | scale = GraphArea.MAX_ZOOM_IN | ||
112 | self.set_scale(scale) | ||
113 | |||
114 | def zoom_out(self): | ||
115 | scale = self.scale - GraphArea.ZOOM_INCR | ||
116 | if scale < GraphArea.MIN_ZOOM_OUT: | ||
117 | scale = GraphArea.MIN_ZOOM_OUT | ||
118 | self.set_scale(scale) | ||
119 | |||
120 | def set_scale(self, scale): | ||
121 | if scale == self.scale: | ||
122 | return | ||
94 | 123 | ||
124 | self.scale = scale | ||
125 | self._dirty(0, 0, self.width, self.height) | ||
126 | |||
95 | def set_scroll_adjustments(self, widget, horizontal, vertical, data=None): | 127 | def set_scroll_adjustments(self, widget, horizontal, vertical, data=None): |
96 | graph = self.renderer.get_graph() | 128 | graph = self.renderer.get_graph() |
97 | width = graph.get_width() | 129 | width = graph.get_width() |
@@ -110,12 +142,14 @@ class GraphArea(gtk.DrawingArea): | |||
110 | self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width()) | 142 | self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width()) |
111 | self.cur_x = max(adjustment.value, 0.0) | 143 | self.cur_x = max(adjustment.value, 0.0) |
112 | 144 | ||
145 | self.renderer.get_graph().render_surface(self.renderer.get_schedule(), [(0, 0, self.width, self.height)], True) | ||
113 | self._dirty(0, 0, self.width, self.height) | 146 | self._dirty(0, 0, self.width, self.height) |
114 | 147 | ||
115 | def vertical_value_changed(self, adjustment): | 148 | def vertical_value_changed(self, adjustment): |
116 | self.cur_y = min(adjustment.value, self.renderer.get_graph().get_height()) | 149 | self.cur_y = min(adjustment.value, self.renderer.get_graph().get_height()) |
117 | self.cur_y = max(adjustment.value, 0.0) | 150 | self.cur_y = max(adjustment.value, 0.0) |
118 | 151 | ||
152 | self.renderer.get_graph().render_surface(self.renderer.get_schedule(), [(0, 0, self.width, self.height)], True) | ||
119 | self._dirty(0, 0, self.width, self.height) | 153 | self._dirty(0, 0, self.width, self.height) |
120 | 154 | ||
121 | def size_allocate(self, widget, allocation): | 155 | def size_allocate(self, widget, allocation): |
@@ -129,12 +163,38 @@ class GraphArea(gtk.DrawingArea): | |||
129 | height = graph.get_height() | 163 | height = graph.get_height() |
130 | 164 | ||
131 | if self.horizontal is not None: | 165 | if self.horizontal is not None: |
132 | self.horizontal.set_all(hvalue, 0.0, width, graph.get_attrs().maj_sep * GraphArea.HORIZ_STEP_SCROLL_FACTOR, | 166 | self.horizontal.set_all(hvalue, 0.0, width + self.width, |
167 | graph.get_attrs().maj_sep * GraphArea.HORIZ_STEP_SCROLL_FACTOR, | ||
133 | graph.get_attrs().maj_sep * GraphArea.HORIZ_PAGE_SCROLL_FACTOR, self.width) | 168 | graph.get_attrs().maj_sep * GraphArea.HORIZ_PAGE_SCROLL_FACTOR, self.width) |
134 | if self.vertical is not None: | 169 | if self.vertical is not None: |
135 | self.vertical.set_all(vvalue, 0.0, height, graph.get_attrs().y_item_size * GraphArea.VERT_STEP_SCROLL_FACTOR, | 170 | self.vertical.set_all(vvalue, 0.0, height + self.height, |
171 | graph.get_attrs().y_item_size * GraphArea.VERT_STEP_SCROLL_FACTOR, | ||
136 | graph.get_attrs().y_item_size * GraphArea.VERT_PAGE_SCROLL_FACTOR, self.height) | 172 | graph.get_attrs().y_item_size * GraphArea.VERT_PAGE_SCROLL_FACTOR, self.height) |
137 | 173 | ||
174 | def refresh_events(self, sender, new, old, replace): | ||
175 | """Even if the selected areas change on one graph, they change | ||
176 | everywhere, and different events might be in different regions on | ||
177 | different graphs. So when an event is selected on one graph its | ||
178 | region on a completely different graph needs to be updated. This | ||
179 | is why this method is here: given the graph that requested the | ||
180 | change, the old set of events selected, and the new set of events | ||
181 | selected, it updates the selected regions of this graph, and | ||
182 | refreshes the drawing of the graph that requested the change.""" | ||
183 | if self is not sender: | ||
184 | self.renderer.get_graph().render_events(new, True) | ||
185 | |||
186 | self._tag_events(new) | ||
187 | |||
188 | if self is sender: | ||
189 | self._copy_tags(old) | ||
190 | self._dirty_events(new) | ||
191 | self._dirty_events(old) | ||
192 | if replace: | ||
193 | self.renderer.get_schedule().set_selected(new) | ||
194 | else: | ||
195 | self.renderer.get_schedule().remove_selected(old) | ||
196 | self.renderer.get_schedule().add_selected(new) | ||
197 | |||
138 | def _find_max_layer(self, regions): | 198 | def _find_max_layer(self, regions): |
139 | max_layer = Canvas.BOTTOM_LAYER | 199 | max_layer = Canvas.BOTTOM_LAYER |
140 | for event in regions: | 200 | for event in regions: |
@@ -142,17 +202,46 @@ class GraphArea(gtk.DrawingArea): | |||
142 | max_layer = event.get_layer() | 202 | max_layer = event.get_layer() |
143 | return max_layer | 203 | return max_layer |
144 | 204 | ||
145 | def _update_event_changes(self, list1, list2): | 205 | def _dirty_events(self, events): |
146 | # if an event changed selected status, update the bounding area | 206 | # if an event changed selected status, update the bounding area |
147 | for event in list1: | 207 | for layer in events: |
148 | if event not in list2: | 208 | for event in events[layer]: |
149 | x, y, width, height = list1[event].get_dimensions() | 209 | x, y, width, height = events[layer][event][self].get_dimensions() |
150 | self._dirty_inflate(x - self.cur_x, y - self.cur_y, width, height, GraphFormat.BORDER_THICKNESS) | 210 | self._dirty_inflate((x - self.cur_x) * self.scale, |
151 | for event in list2: | 211 | (y - self.cur_y) * self.scale, |
152 | if event not in list1: | 212 | width * self.scale, |
153 | x, y, width, height = list2[event].get_dimensions() | 213 | height * self.scale, |
154 | self._dirty_inflate(x - self.cur_x, y - self.cur_y, width, height, GraphFormat.BORDER_THICKNESS) | 214 | GraphFormat.BORDER_THICKNESS * self.scale) |
155 | 215 | ||
216 | def _tag_events(self, selected): | ||
217 | """Some of the events in the collection of selected events might be new. | ||
218 | In this case, these events are not yet associated with the region on | ||
219 | the graph that they belong to. This method fixes this. | ||
220 | """ | ||
221 | graph = self.renderer.get_graph() | ||
222 | for layer in selected: | ||
223 | for event in selected[layer]: | ||
224 | # note that each graph has its own region associated | ||
225 | # with the event | ||
226 | selected[layer][event][self] = graph.get_sel_region(event) | ||
227 | |||
228 | def _copy_tags(self, selected): | ||
229 | """When we want to specify a collection of selected events to perform | ||
230 | an operation on, we usually do not know ahead of time what regions (in | ||
231 | which graphs) the events are associated with. But we have this information | ||
232 | stored in the collection of all selected events. This method just copies | ||
233 | this information over to the selected variable.""" | ||
234 | cur_selected = self.renderer.get_schedule().get_selected() | ||
235 | for layer in selected: | ||
236 | for event in selected[layer]: | ||
237 | selected[layer][event] = cur_selected[layer][event] | ||
238 | |||
239 | def _select_event(self, coll, event): | ||
240 | if event.get_layer() not in coll: | ||
241 | coll[event.get_layer()] = {} | ||
242 | if event not in coll[event.get_layer()]: | ||
243 | coll[event.get_layer()][event] = {} | ||
244 | |||
156 | def motion_notify(self, widget, motion_event, data=None): | 245 | def motion_notify(self, widget, motion_event, data=None): |
157 | msg = None | 246 | msg = None |
158 | 247 | ||
@@ -160,7 +249,9 @@ class GraphArea(gtk.DrawingArea): | |||
160 | 249 | ||
161 | graph.render_surface(self.renderer.get_schedule(), [(motion_event.x, motion_event.y, | 250 | graph.render_surface(self.renderer.get_schedule(), [(motion_event.x, motion_event.y, |
162 | 0, 0)], True) | 251 | 0, 0)], True) |
252 | |||
163 | just_selected = graph.get_selected_regions(motion_event.x, motion_event.y, 0, 0) | 253 | just_selected = graph.get_selected_regions(motion_event.x, motion_event.y, 0, 0) |
254 | was_selected = self.renderer.get_schedule().get_selected() | ||
164 | if not just_selected: | 255 | if not just_selected: |
165 | msg = '' | 256 | msg = '' |
166 | the_event = None | 257 | the_event = None |
@@ -177,30 +268,57 @@ class GraphArea(gtk.DrawingArea): | |||
177 | self.emit('update-event-description', the_event, msg) | 268 | self.emit('update-event-description', the_event, msg) |
178 | 269 | ||
179 | if self.band_rect is not None: | 270 | if self.band_rect is not None: |
180 | selected = {} | 271 | remove_selected = {} |
181 | was_selected = self.renderer.get_schedule().get_selected() | 272 | add_selected = {} |
182 | if self.ctrl_clicked: | ||
183 | selected = copy.copy(was_selected) | ||
184 | 273 | ||
185 | # dragging a rectangle | 274 | # dragging a rectangle |
186 | x = self.band_rect[0] | 275 | x = self.band_rect[0] |
187 | y = self.band_rect[1] | 276 | y = self.band_rect[1] |
188 | width = motion_event.x - self.band_rect[0] | 277 | width = motion_event.x - self.band_rect[0] |
189 | height = motion_event.y - self.band_rect[1] | 278 | height = motion_event.y - self.band_rect[1] |
279 | old_x, old_y, old_width, old_height = self.band_rect | ||
190 | 280 | ||
191 | x_p, y_p, width_p, height_p = self._positivify(x, y, width, height) | 281 | x_p, y_p, width_p, height_p = self._positivify(x, y, width, height) |
192 | graph.render_surface(self.renderer.get_schedule(), [(x_p, y_p, width_p, height_p)], True) | 282 | old_x_p, old_y_p, old_width_p, old_height_p = self._positivify(old_x, old_y, old_width, old_height) |
193 | selected.update(graph.get_selected_regions(x_p, y_p, width_p, height_p)) | 283 | x_p, y_p, width_p, height_p = int(x_p), int(y_p), int(width_p), int(height_p) |
194 | self.renderer.get_schedule().set_selected(selected) | 284 | old_x_p, old_y_p, old_width_p, old_height_p = int(old_x_p), int(old_y_p), int(old_width_p), int(old_height_p) |
195 | 285 | ||
196 | old_x, old_y, old_width, old_height = self.band_rect | 286 | new_reg = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(x_p, y_p, width_p, height_p)) |
197 | self.band_rect = (x, y, width, height) | 287 | old_reg = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(old_x_p, old_y_p, old_width_p, old_height_p)) |
288 | |||
289 | # To find the events that should be deselected and the new events that should be selected, compute | ||
290 | # the set differences between the old and new selection rectangles | ||
291 | |||
292 | remove_reg = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(old_x_p, old_y_p, old_width_p, old_height_p)) | ||
293 | remove_reg.subtract(new_reg) | ||
294 | dirty_list = [] | ||
295 | for rect in remove_reg.get_rectangles(): | ||
296 | dirty_list.append((rect.x, rect.y, rect.width, rect.height)) | ||
297 | graph.render_surface(self.renderer.get_schedule(), dirty_list, True) | ||
298 | for rect in dirty_list: | ||
299 | rx, ry, rwidth, rheight = rect | ||
300 | for event in graph.get_selected_regions(rx, ry, rwidth, rheight): | ||
301 | if event.get_layer() in was_selected and event in was_selected[event.get_layer()]: | ||
302 | self._select_event(remove_selected, event) | ||
303 | |||
304 | add_reg = gtk.gdk.region_rectangle(gtk.gdk.Rectangle(x_p, y_p, width_p, height_p)) | ||
305 | add_reg.subtract(old_reg) | ||
306 | dirty_list = [(x_p, y_p, width_p, 0), (x_p, y_p, 0, height_p), | ||
307 | (x_p, y_p + height_p, width_p, 0), (x_p + width_p, y_p, 0, height_p)] | ||
308 | for rect in add_reg.get_rectangles(): | ||
309 | dirty_list.append((rect.x, rect.y, rect.width, rect.height)) | ||
310 | graph.render_surface(self.renderer.get_schedule(), dirty_list, True) | ||
311 | for rect in dirty_list: | ||
312 | rx, ry, rwidth, rheight = rect | ||
313 | for event in graph.get_selected_regions(rx, ry, rwidth, rheight): | ||
314 | self._select_event(add_selected, event) | ||
315 | |||
316 | self.band_rect = x, y, width, height | ||
317 | self.emit('request-refresh-events', self, add_selected, remove_selected, False) | ||
198 | 318 | ||
199 | self._dirty_rect_border(old_x, old_y, old_width, old_height, GraphFormat.BAND_THICKNESS) | 319 | self._dirty_rect_border(old_x, old_y, old_width, old_height, GraphFormat.BAND_THICKNESS) |
200 | self._dirty_rect_border(x, y, width, height, GraphFormat.BAND_THICKNESS) | 320 | self._dirty_rect_border(x, y, width, height, GraphFormat.BAND_THICKNESS) |
201 | 321 | ||
202 | self._update_event_changes(was_selected, selected) | ||
203 | |||
204 | def button_press(self, widget, button_event, data=None): | 322 | def button_press(self, widget, button_event, data=None): |
205 | graph = self.renderer.get_graph() | 323 | graph = self.renderer.get_graph() |
206 | 324 | ||
@@ -215,34 +333,43 @@ class GraphArea(gtk.DrawingArea): | |||
215 | 333 | ||
216 | max_layer = self._find_max_layer(just_selected) | 334 | max_layer = self._find_max_layer(just_selected) |
217 | 335 | ||
218 | new_now_selected = None | ||
219 | was_selected = self.renderer.get_schedule().get_selected() | 336 | was_selected = self.renderer.get_schedule().get_selected() |
220 | if self.ctrl_clicked: | 337 | if not self.ctrl_clicked: |
221 | new_now_selected = copy.copy(was_selected) | ||
222 | else: | ||
223 | new_now_selected = {} | 338 | new_now_selected = {} |
224 | 339 | ||
225 | # only select those events which were in the top layer (it's | 340 | more_than_one = 0 |
226 | # not intuitive to click something and then have something | 341 | for layer in was_selected: |
227 | # below it get selected). Also, clicking something that | 342 | for event in was_selected[layer]: |
228 | # is selected deselects it, if it's the only thing selected | 343 | more_than_one += 1 |
229 | for event in just_selected: | 344 | if more_than_one > 1: |
230 | if event.get_layer() == max_layer: | 345 | break |
231 | val = True | 346 | |
232 | now_selected = self.renderer.get_schedule().get_selected() | 347 | # only select those events which were in the top layer (it's |
233 | if (len(now_selected) == 1 or self.ctrl_clicked) and event in now_selected: | 348 | # not intuitive to click something and then have something |
234 | val = not event.is_selected | 349 | # below it get selected). Also, clicking something that |
235 | if val: | 350 | # is selected deselects it, if it's the only thing selected |
236 | new_now_selected[event] = graph.get_sel_region(event) | 351 | for event in just_selected: |
237 | elif event in new_now_selected: | 352 | if event.get_layer() == max_layer: |
238 | del new_now_selected[event] | 353 | if not (more_than_one == 1 and event in was_selected): |
239 | break # only pick one event when just clicking | 354 | self._select_event(new_now_selected, event) |
240 | 355 | break # only pick one event when just clicking | |
241 | self.renderer.get_schedule().set_selected(new_now_selected) | ||
242 | #self.last_selected = new_now_selected | ||
243 | |||
244 | self._update_event_changes(new_now_selected, was_selected) | ||
245 | 356 | ||
357 | self.emit('request-refresh-events', self, new_now_selected, was_selected, True) | ||
358 | else: | ||
359 | remove_selected = {} | ||
360 | add_selected = {} | ||
361 | |||
362 | for event in just_selected: | ||
363 | layer = event.get_layer() | ||
364 | if layer == max_layer: | ||
365 | if layer in was_selected and event in was_selected[layer]: | ||
366 | self._select_event(remove_selected, event) | ||
367 | else: | ||
368 | self._select_event(add_selected, event) | ||
369 | break # again, only pick one event because we are just clicking | ||
370 | |||
371 | self.emit('request-refresh-events', self, add_selected, remove_selected, False) | ||
372 | |||
246 | if self.band_rect is None: | 373 | if self.band_rect is None: |
247 | self.band_rect = (button_event.x, button_event.y, 0, 0) | 374 | self.band_rect = (button_event.x, button_event.y, 0, 0) |
248 | 375 | ||
@@ -252,11 +379,10 @@ class GraphArea(gtk.DrawingArea): | |||
252 | 379 | ||
253 | def button_release(self, widget, button_event, data=None): | 380 | def button_release(self, widget, button_event, data=None): |
254 | self.ctrl_clicked = False | 381 | self.ctrl_clicked = False |
255 | #self.last_selected = copy.copy(self.renderer.get_schedule().get_selected()) | ||
256 | 382 | ||
257 | if button_event.button == 1: | 383 | if button_event.button == 1: |
258 | self._release_band() | 384 | self._release_band() |
259 | 385 | ||
260 | def _release_band(self): | 386 | def _release_band(self): |
261 | if self.band_rect is not None: | 387 | if self.band_rect is not None: |
262 | x, y, width, height = self.band_rect | 388 | x, y, width, height = self.band_rect |
@@ -306,20 +432,45 @@ class GraphWindow(gtk.ScrolledWindow): | |||
306 | def __init__(self, renderer): | 432 | def __init__(self, renderer): |
307 | super(GraphWindow, self).__init__(None, None) | 433 | super(GraphWindow, self).__init__(None, None) |
308 | 434 | ||
309 | self.add_events(gtk.gdk.KEY_PRESS_MASK) | 435 | self.add_events(gtk.gdk.KEY_PRESS_MASK | gtk.gdk.SCROLL_MASK) |
310 | 436 | ||
311 | self.ctr = 0 | 437 | self.ctr = 0 |
312 | self.connect('key-press-event', self.key_press) | 438 | self.connect('key-press-event', self.key_press) |
439 | self.connect('scroll-event', self.scroll) | ||
313 | 440 | ||
314 | self.garea = GraphArea(renderer) | 441 | self.garea = GraphArea(renderer) |
315 | self.add(self.garea) | 442 | self.add(self.garea) |
316 | self.garea.show() | 443 | self.garea.show() |
317 | 444 | ||
318 | def key_press(self, widget, key_event): | 445 | def key_press(self, widget, key_event): |
319 | hadj = self.get_hadjustment() | 446 | hadj = self.get_hadjustment() |
320 | vadj = self.get_vadjustment() | 447 | vadj = self.get_vadjustment() |
321 | if hadj is None or vadj is None: | 448 | if hadj is None or vadj is None: |
322 | return | 449 | return |
450 | |||
451 | ctrl_clicked = key_event.state & gtk.gdk.CONTROL_MASK | ||
452 | |||
453 | keystr = None | ||
454 | keymap = {gtk.keysyms.Up : 'up', gtk.keysyms.Down : 'down', | ||
455 | gtk.keysyms.Left : 'left', gtk.keysyms.Right : 'right'} | ||
456 | if key_event.keyval in keymap: | ||
457 | keystr = keymap[key_event.keyval] | ||
458 | else: | ||
459 | return True | ||
460 | |||
461 | if ctrl_clicked: | ||
462 | keystr = 'ctrl-' + keystr | ||
463 | |||
464 | if keystr is not None: | ||
465 | self._scroll_direction(keystr) | ||
466 | |||
467 | return True | ||
468 | |||
469 | def _scroll_direction(self, keystr): | ||
470 | hadj = self.get_hadjustment() | ||
471 | vadj = self.get_vadjustment() | ||
472 | if hadj is None or vadj is None: | ||
473 | return | ||
323 | 474 | ||
324 | hupper = hadj.get_upper() | 475 | hupper = hadj.get_upper() |
325 | hlower = hadj.get_lower() | 476 | hlower = hadj.get_lower() |
@@ -334,8 +485,6 @@ class GraphWindow(gtk.ScrolledWindow): | |||
334 | vsincr = vadj.get_step_increment() | 485 | vsincr = vadj.get_step_increment() |
335 | vpsize = vadj.get_page_size() | 486 | vpsize = vadj.get_page_size() |
336 | 487 | ||
337 | ctrl_clicked = key_event.state & gtk.gdk.CONTROL_MASK | ||
338 | |||
339 | adj_tuple = {'up' : (vadj, -vsincr, 0, vval, max), | 488 | adj_tuple = {'up' : (vadj, -vsincr, 0, vval, max), |
340 | 'ctrl-up' : (vadj, -vpincr, 0, vval, max), | 489 | 'ctrl-up' : (vadj, -vpincr, 0, vval, max), |
341 | 'down' : (vadj, vsincr, vupper - vpsize, vval, min), | 490 | 'down' : (vadj, vsincr, vupper - vpsize, vval, min), |
@@ -344,22 +493,24 @@ class GraphWindow(gtk.ScrolledWindow): | |||
344 | 'ctrl-left' : (hadj, -hpincr, 0, hval, max), | 493 | 'ctrl-left' : (hadj, -hpincr, 0, hval, max), |
345 | 'right' : (hadj, hsincr, hupper - hpsize, hval, min), | 494 | 'right' : (hadj, hsincr, hupper - hpsize, hval, min), |
346 | 'ctrl-right' : (hadj, hpincr, hupper - hpsize, hval, min)} | 495 | 'ctrl-right' : (hadj, hpincr, hupper - hpsize, hval, min)} |
347 | |||
348 | keystr = None | ||
349 | keymap = {gtk.keysyms.Up : 'up', gtk.keysyms.Down : 'down', | ||
350 | gtk.keysyms.Left : 'left', gtk.keysyms.Right : 'right'} | ||
351 | if key_event.keyval in keymap: | ||
352 | keystr = keymap[key_event.keyval] | ||
353 | |||
354 | if ctrl_clicked: | ||
355 | keystr = 'ctrl-' + keystr | ||
356 | |||
357 | if keystr is not None: | ||
358 | adj, inc, lim, val, extr = adj_tuple[keystr] | ||
359 | adj.set_value(extr(val + inc, lim)) | ||
360 | |||
361 | return True | ||
362 | 496 | ||
497 | adj, inc, lim, val, extr = adj_tuple[keystr] | ||
498 | adj.set_value(extr(val + inc, lim)) | ||
499 | |||
500 | def scroll(self, widget, scroll_event): | ||
501 | if scroll_event.state & gtk.gdk.CONTROL_MASK: | ||
502 | if scroll_event.direction == gtk.gdk.SCROLL_UP: | ||
503 | self.emit('request-zoom-in') | ||
504 | elif scroll_event.direction == gtk.gdk.SCROLL_DOWN: | ||
505 | self.emit('request-zoom-out') | ||
506 | else: | ||
507 | if scroll_event.direction == gtk.gdk.SCROLL_UP: | ||
508 | self._scroll_direction('up') | ||
509 | elif scroll_event.direction == gtk.gdk.SCROLL_DOWN: | ||
510 | self._scroll_direction('down') | ||
511 | |||
512 | return True | ||
513 | |||
363 | def get_graph_area(self): | 514 | def get_graph_area(self): |
364 | return self.garea | 515 | return self.garea |
365 | 516 | ||
@@ -370,38 +521,76 @@ class MainWindow(gtk.Window): | |||
370 | def __init__(self): | 521 | def __init__(self): |
371 | super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL) | 522 | super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL) |
372 | 523 | ||
524 | self.add_events(gtk.gdk.BUTTON_PRESS_MASK) | ||
525 | |||
373 | self.connect('delete_event', self.delete_event) | 526 | self.connect('delete_event', self.delete_event) |
374 | self.connect('destroy', self.die) | 527 | self.connect('destroy', self.die) |
375 | 528 | ||
376 | self.file_menu = gtk.Menu() | 529 | file_menu = gtk.Menu() |
377 | self.quit_item = gtk.MenuItem('_Quit', True) | 530 | view_menu = gtk.Menu() |
378 | self.quit_item.connect('activate', self.quit_item_activate) | 531 | |
379 | self.quit_item.show() | 532 | agr = gtk.AccelGroup() |
380 | self.file_menu.append(self.quit_item) | 533 | self.add_accel_group(agr) |
534 | |||
535 | quit_item = gtk.ImageMenuItem(gtk.STOCK_QUIT, agr) | ||
536 | key, mod = gtk.accelerator_parse('Q') | ||
537 | quit_item.add_accelerator('activate', agr, key, mod, | ||
538 | gtk.ACCEL_VISIBLE) | ||
539 | quit_item.connect('activate', self.quit_item_activate) | ||
540 | quit_item.show() | ||
541 | |||
542 | file_menu.append(quit_item) | ||
543 | |||
544 | file_item = gtk.MenuItem('_File', True) | ||
545 | file_item.set_submenu(file_menu) | ||
546 | file_item.show() | ||
547 | |||
548 | zoom_in_item = gtk.ImageMenuItem(gtk.STOCK_ZOOM_IN, agr) | ||
549 | key, mod = gtk.accelerator_parse('<Ctrl>plus') | ||
550 | zoom_in_item.add_accelerator('activate', agr, key, mod, | ||
551 | gtk.ACCEL_VISIBLE) | ||
552 | key, mod = gtk.accelerator_parse('<Ctrl>equal') | ||
553 | zoom_in_item.add_accelerator('activate', agr, key, mod, 0) | ||
381 | 554 | ||
382 | self.file_item = gtk.MenuItem('_File', True) | 555 | zoom_in_item.connect('activate', self.zoom_in_item_activate) |
383 | self.file_item.set_submenu(self.file_menu) | 556 | zoom_in_item.show() |
384 | self.file_item.show() | ||
385 | 557 | ||
386 | self.menu_bar = gtk.MenuBar() | 558 | zoom_out_item = gtk.ImageMenuItem(gtk.STOCK_ZOOM_OUT, agr) |
387 | self.menu_bar.append(self.file_item) | 559 | key, mod = gtk.accelerator_parse('<Ctrl>minus') |
560 | zoom_out_item.add_accelerator('activate', agr, key, mod, | ||
561 | gtk.ACCEL_VISIBLE) | ||
562 | key, mod = gtk.accelerator_parse('<Ctrl>underscore') | ||
563 | zoom_out_item.add_accelerator('activate', agr, key, mod, 0) | ||
388 | 564 | ||
389 | self.menu_bar.show() | 565 | zoom_out_item.connect('activate', self.zoom_out_item_activate) |
566 | zoom_out_item.show() | ||
390 | 567 | ||
568 | view_menu.append(zoom_in_item) | ||
569 | view_menu.append(zoom_out_item) | ||
570 | |||
571 | view_item = gtk.MenuItem('_View', True) | ||
572 | view_item.set_submenu(view_menu) | ||
573 | view_item.show() | ||
574 | |||
575 | menu_bar = gtk.MenuBar() | ||
576 | menu_bar.append(file_item) | ||
577 | menu_bar.append(view_item) | ||
578 | |||
579 | menu_bar.show() | ||
391 | self.vbox = gtk.VBox(False, 0) | 580 | self.vbox = gtk.VBox(False, 0) |
392 | 581 | ||
393 | self.notebook = gtk.Notebook() | 582 | self.notebook = gtk.Notebook() |
394 | 583 | ||
395 | self.notebook.last_page = -1 | 584 | self.notebook.last_page = -1 |
396 | self.notebook.connect('switch-page', self.switch_page) | 585 | self.notebook.connect('switch-page', self.switch_page) |
397 | 586 | ||
398 | self.notebook.show() | 587 | self.notebook.show() |
399 | 588 | ||
400 | self.desc_label = gtk.Label('') | 589 | self.desc_label = gtk.Label('') |
401 | self.desc_label.set_justify(gtk.JUSTIFY_LEFT) | 590 | self.desc_label.set_justify(gtk.JUSTIFY_LEFT) |
402 | self.desc_label.show() | 591 | self.desc_label.show() |
403 | 592 | ||
404 | self.vbox.pack_start(self.menu_bar, False, False, 0) | 593 | self.vbox.pack_start(menu_bar, False, False, 0) |
405 | self.vbox.pack_start(self.notebook, True, True, 0) | 594 | self.vbox.pack_start(self.notebook, True, True, 0) |
406 | self.vbox.pack_start(self.desc_label, False, False, 0) | 595 | self.vbox.pack_start(self.desc_label, False, False, 0) |
407 | self.vbox.show() | 596 | self.vbox.show() |
@@ -412,18 +601,23 @@ class MainWindow(gtk.Window): | |||
412 | 601 | ||
413 | self.show() | 602 | self.show() |
414 | 603 | ||
415 | def connect_widgets(self, garea): | 604 | def connect_widgets(self, gwindow): |
416 | garea.connect('update-event-description', self.update_event_description) | 605 | gwindow.get_graph_area().connect('update-event-description', self.update_event_description) |
417 | garea.connect('request-context-menu', self.request_context_menu) | 606 | gwindow.get_graph_area().connect('request-context-menu', self.request_context_menu) |
418 | 607 | gwindow.get_graph_area().connect('request-refresh-events', self.request_refresh_events) | |
608 | gwindow.connect('request-zoom-in', self.zoom_in_item_activate) | ||
609 | gwindow.connect('request-zoom-out', self.zoom_out_item_activate) | ||
610 | |||
419 | def set_renderers(self, renderers): | 611 | def set_renderers(self, renderers): |
420 | for i in range(0, self.notebook.get_n_pages()): | 612 | for i in range(0, self.notebook.get_n_pages()): |
421 | self.notebook.remove_page(0) | 613 | self.notebook.remove_page(0) |
422 | for title in renderers: | 614 | for title in renderers: |
423 | gwindow = GraphWindow(renderers[title]) | 615 | gwindow = GraphWindow(renderers[title]) |
424 | self.connect_widgets(gwindow.get_graph_area()) | 616 | self.connect_widgets(gwindow) |
425 | gwindow.show() | 617 | gwindow.show() |
426 | self.notebook.append_page(gwindow, gtk.Label(title)) | 618 | self.notebook.append_page(gwindow, gtk.Label(title)) |
619 | if self.notebook.get_n_pages() > 0: | ||
620 | self.notebook.get_nth_page(0).grab_focus() | ||
427 | 621 | ||
428 | def switch_page(self, widget, page, page_num): | 622 | def switch_page(self, widget, page, page_num): |
429 | if self.notebook.get_nth_page(self.notebook.last_page) is not None: | 623 | if self.notebook.get_nth_page(self.notebook.last_page) is not None: |
@@ -432,7 +626,7 @@ class MainWindow(gtk.Window): | |||
432 | new_ofs = self.notebook.get_nth_page(page_num).get_graph_area().get_graph().get_origin()[0] | 626 | new_ofs = self.notebook.get_nth_page(page_num).get_graph_area().get_graph().get_origin()[0] |
433 | new_value = old_value - old_ofs + new_ofs | 627 | new_value = old_value - old_ofs + new_ofs |
434 | self.notebook.get_nth_page(page_num).get_hadjustment().set_value(new_value) | 628 | self.notebook.get_nth_page(page_num).get_hadjustment().set_value(new_value) |
435 | 629 | ||
436 | self.notebook.last_page = page_num | 630 | self.notebook.last_page = page_num |
437 | 631 | ||
438 | def update_event_description(self, widget, event, msg): | 632 | def update_event_description(self, widget, event, msg): |
@@ -446,10 +640,27 @@ class MainWindow(gtk.Window): | |||
446 | 640 | ||
447 | menu = GraphContextMenu(selected) | 641 | menu = GraphContextMenu(selected) |
448 | menu.popup(None, None, None, button, time) | 642 | menu.popup(None, None, None, button, time) |
643 | |||
644 | def request_refresh_events(self, widget, sender, old, new, replace): | ||
645 | for i in range(0, self.notebook.get_n_pages()): | ||
646 | if self.notebook.get_nth_page(i).get_graph_area() is not sender: | ||
647 | self.notebook.get_nth_page(i).get_graph_area().refresh_events(sender, old, new, replace) | ||
648 | break | ||
649 | for i in range(0, self.notebook.get_n_pages()): | ||
650 | if self.notebook.get_nth_page(i).get_graph_area() is sender: | ||
651 | self.notebook.get_nth_page(i).get_graph_area().refresh_events(sender, old, new, replace) | ||
652 | |||
653 | def zoom_in_item_activate(self, widget): | ||
654 | for i in range(0, self.notebook.get_n_pages()): | ||
655 | self.notebook.get_nth_page(i).get_graph_area().zoom_in() | ||
656 | |||
657 | def zoom_out_item_activate(self, widget): | ||
658 | for i in range(0, self.notebook.get_n_pages()): | ||
659 | self.notebook.get_nth_page(i).get_graph_area().zoom_out() | ||
449 | 660 | ||
450 | def quit_item_activate(self, widget): | 661 | def quit_item_activate(self, widget): |
451 | self.destroy() | 662 | self.destroy() |
452 | 663 | ||
453 | def delete_event(self, widget, event, data=None): | 664 | def delete_event(self, widget, event, data=None): |
454 | return False | 665 | return False |
455 | 666 | ||
diff --git a/unit_trace/viz/visualizer.py b/unit_trace/viz/visualizer.py index d739736..5027e89 100755 --- a/unit_trace/viz/visualizer.py +++ b/unit_trace/viz/visualizer.py | |||
@@ -1,7 +1,5 @@ | |||
1 | #!/usr/bin/python | 1 | #!/usr/bin/python |
2 | 2 | ||
3 | """.""" | ||
4 | |||
5 | import viewer | 3 | import viewer |
6 | import convert | 4 | import convert |
7 | import renderer | 5 | import renderer |