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