summaryrefslogtreecommitdiffstats
path: root/unit_trace/viz
diff options
context:
space:
mode:
authorGary Bressler <garybressler@nc.rr.com>2010-04-06 12:45:04 -0400
committerGary Bressler <garybressler@nc.rr.com>2010-04-06 12:45:04 -0400
commitc7e3aaebdba7bf880534abd91a383b5543cf0be4 (patch)
tree048977efdaaa3d60e93c3d21ba29c46a0bfe71c3 /unit_trace/viz
parent7fdb4dbbbca577efbeec47cd1364eb319346a0cc (diff)
Making sure everything committed
Diffstat (limited to 'unit_trace/viz')
-rw-r--r--unit_trace/viz/__init__.py9
-rw-r--r--unit_trace/viz/canvas.py (renamed from unit_trace/viz/draw.py)788
-rw-r--r--unit_trace/viz/graph.py657
-rw-r--r--unit_trace/viz/renderer.py2
-rw-r--r--unit_trace/viz/schedule.py74
-rw-r--r--unit_trace/viz/viewer.py407
-rwxr-xr-xunit_trace/viz/visualizer.py2
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))
13gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 13gobject.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, 15gobject.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))
18gobject.signal_new('request-zoom-in', viewer.GraphWindow, gobject.SIGNAL_RUN_FIRST,
19 None, ())
20gobject.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
4this file is quite low-level, in that its objects are mostly restricted to
5dealing with drawing the components of a real-time graph given coordinates
6rather than having an abstract knowledge of the graph's measurements or
7any information about events."""
8
3import math 9import math
4import cairo 10import cairo
5import os 11import os
6import copy 12import copy
7 13
8import util 14import util
9import schedule
10from format import * 15from format import *
11 16
12def snap(pos): 17def 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
21class Surface(object): 26class 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
63class SVGSurface(Surface): 82class 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
741class 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
1003class 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
1168class 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 @@
1import util
2import schedule
3from format import *
4from canvas import *
5
6"""The higher-level components of the rendering engine. The graph classes deal with more abstract dimensions
7than the canvas classes (time and task/cpu number rather than plain coordinates). Also, graphs know how to
8update themselves, unlike the Canvas which can only overwrite itself."""
9
10class 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
290class 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
448class 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
2from schedule import * 2from schedule import *
3from draw import * 3from 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
6on a graph.""" 6on 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
5a high-level representation of a schedule that can be converted to, say, a 5a high-level representation of a schedule that can be converted to, say, a
6graphic.""" 6graphic."""
7 7
8from draw import * 8from graph import *
9import util 9import util
10 10
11import copy 11import 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
123class Schedule(object): 129class 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
232def deepcopy_selected(selected):
233 selected_copy = {}
234 for layer in selected:
235 selected_copy[layer] = copy.copy(selected[layer])
236 return selected_copy
237
216class Task(object): 238class 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
28class GraphArea(gtk.DrawingArea): 35class 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
5import viewer 3import viewer
6import convert 4import convert
7import renderer 5import renderer