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