summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGary Bressler <garybressler@nc.rr.com>2010-03-19 12:55:12 -0400
committerGary Bressler <garybressler@nc.rr.com>2010-03-19 12:55:12 -0400
commitee7ae82ae6df1378203957b7fa77066153e13c36 (patch)
treeceb3b420a5ce80b07e9a23bbef0ec9639dbb384f
parente6dbe1605d8ae52ee65a08e929ed1810bd07d45c (diff)
Fixed some graphical glitches, along with streamlining the access to the visualizer.
-rwxr-xr-xunit-trace6
-rwxr-xr-xunit_trace/visualizer.py35
-rw-r--r--unit_trace/viz/__init__.py5
-rw-r--r--unit_trace/viz/draw.py223
-rw-r--r--unit_trace/viz/format.py3
-rw-r--r--unit_trace/viz/schedule.py89
-rw-r--r--unit_trace/viz/viewer.py182
7 files changed, 338 insertions, 205 deletions
diff --git a/unit-trace b/unit-trace
index 93ab169..5328b99 100755
--- a/unit-trace
+++ b/unit-trace
@@ -17,7 +17,7 @@ from unit_trace import sanitizer
17from unit_trace import gedf_test 17from unit_trace import gedf_test
18from unit_trace import stats 18from unit_trace import stats
19from unit_trace import stdout_printer 19from unit_trace import stdout_printer
20from unit_trace import visualizer 20from unit_trace import viz
21from unit_trace import progress 21from unit_trace import progress
22from unit_trace import skipper 22from unit_trace import skipper
23from unit_trace import maxer 23from unit_trace import maxer
@@ -107,8 +107,8 @@ if options.stdout is True and options.visualize is True:
107 import itertools 107 import itertools
108 stream1, stream2 = itertools.tee(stream,2) 108 stream1, stream2 = itertools.tee(stream,2)
109 stdout_printer.stdout_printer(stream1) 109 stdout_printer.stdout_printer(stream1)
110 visualizer.visualizer(stream2) 110 viz.visualizer.visualizer(stream2)
111elif options.stdout is True: 111elif options.stdout is True:
112 stdout_printer.stdout_printer(stream) 112 stdout_printer.stdout_printer(stream)
113elif options.visualize is True: 113elif options.visualize is True:
114 visualizer.visualizer(stream) 114 viz.visualizer.visualizer(stream)
diff --git a/unit_trace/visualizer.py b/unit_trace/visualizer.py
deleted file mode 100755
index af2dc6a..0000000
--- a/unit_trace/visualizer.py
+++ /dev/null
@@ -1,35 +0,0 @@
1#!/usr/bin/python
2
3"""Runs the visualizer."""
4
5import viz
6import gtk
7import trace_reader
8
9TIME_PER_MAJ = 10000000
10
11def request_renderer_change(widget, file_list, params):
12 try:
13 stream = trace_reader.trace_reader(file_list)
14 #stream = reader.sanitizer.sanitizer(stream)
15 #stream = reader.gedf_test.gedf_test(stream)
16 sched = viz.convert.convert_trace_to_schedule(stream)
17 except trace_reader.InvalidRecordError, e:
18 dialog = gtk.MessageDialog(widget, gtk.DIALOG_DESTROY_WITH_PARENT,
19 gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, str(e))
20 dialog.run()
21 dialog.destroy()
22 return
23
24 sched.scan(TIME_PER_MAJ)
25
26 task_renderer = viz.renderer.Renderer(sched)
27 task_renderer.prepare_task_graph(attrs=viz.format.GraphFormat(time_per_maj=TIME_PER_MAJ))
28 cpu_renderer = viz.renderer.Renderer(sched)
29 cpu_renderer.prepare_cpu_graph(attrs=viz.format.GraphFormat(time_per_maj=TIME_PER_MAJ))
30 widget.set_renderers({'Tasks' : task_renderer, 'CPUs' : cpu_renderer})
31
32if __name__ == '__main__':
33 window = viz.viewer.MainWindow(request_renderer_change)
34 gtk.main()
35
diff --git a/unit_trace/viz/__init__.py b/unit_trace/viz/__init__.py
index ab141e1..11505b9 100644
--- a/unit_trace/viz/__init__.py
+++ b/unit_trace/viz/__init__.py
@@ -1,3 +1,4 @@
1import visualizer
1import viewer 2import viewer
2import renderer 3import renderer
3import format 4import format
@@ -11,5 +12,5 @@ gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_
11 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) 12 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
12gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 13gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
13 None, (gtk.gdk.Event, gobject.TYPE_PYOBJECT)) 14 None, (gtk.gdk.Event, gobject.TYPE_PYOBJECT))
14gobject.signal_new('request-renderer-change', viewer.MainWindow, gobject.SIGNAL_RUN_FIRST, 15#gobject.signal_new('request-renderer-change', viewer.MainWindow, gobject.SIGNAL_RUN_FIRST,
15 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) 16# None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
diff --git a/unit_trace/viz/draw.py b/unit_trace/viz/draw.py
index c30ffe7..e28ed24 100644
--- a/unit_trace/viz/draw.py
+++ b/unit_trace/viz/draw.py
@@ -3,6 +3,7 @@
3import math 3import math
4import cairo 4import cairo
5import os 5import os
6import copy
6 7
7import util 8import util
8import schedule 9import schedule
@@ -15,7 +16,7 @@ def snap(pos):
15 line of width 1 on integer coordinates, it will come out blurry unless we shift it, 16 line of width 1 on integer coordinates, it will come out blurry unless we shift it,
16 since the line will get distributed over two pixels. We actually apply this to all 17 since the line will get distributed over two pixels. We actually apply this to all
17 coordinates to make sure everything is aligned.""" 18 coordinates to make sure everything is aligned."""
18 return pos - 0.5 19 return pos
19 20
20class Surface(object): 21class Surface(object):
21 def __init__(self, fname='temp', ctx=None): 22 def __init__(self, fname='temp', ctx=None):
@@ -166,39 +167,40 @@ class Canvas(object):
166 def scaled(self, *coors): 167 def scaled(self, *coors):
167 return [coor * self.scale for coor in coors] 168 return [coor * self.scale for coor in coors]
168 169
169 def draw_rect(self, x, y, width, height, color, thickness): 170 def draw_rect(self, x, y, width, height, color, thickness, snap=True):
170 """Draws a rectangle somewhere (border only).""" 171 """Draws a rectangle somewhere (border only)."""
171 raise NotImplementedError 172 raise NotImplementedError
172 173
173 def fill_rect(self, x, y, width, height, color): 174 def fill_rect(self, x, y, width, height, color, snap=True):
174 """Draws a filled rectangle somewhere. ``color'' is a 3-tuple.""" 175 """Draws a filled rectangle somewhere. ``color'' is a 3-tuple."""
175 raise NotImplementedError 176 raise NotImplementedError
176 177
177 def fill_rect_fade(self, x, y, width, height, lcolor, rcolor): 178 def fill_rect_fade(self, x, y, width, height, lcolor, rcolor, snap=True):
178 """Draws a rectangle somewhere, filled in with the fade.""" 179 """Draws a rectangle somewhere, filled in with the fade."""
179 raise NotImplementedError 180 raise NotImplementedError
180 181
181 def draw_line(self, p0, p1, color, thickness): 182 def draw_line(self, p0, p1, color, thickness, snap=True):
182 """Draws a line from p0 to p1 with a certain color and thickness.""" 183 """Draws a line from p0 to p1 with a certain color and thickness."""
183 raise NotImplementedError 184 raise NotImplementedError
184 185
185 def draw_polyline(self, coor_list, color, thickness): 186 def draw_polyline(self, coor_list, color, thickness, snap=True):
186 """Draws a polyline, where coor_list = [(x_0, y_0), (x_1, y_1), ... (x_m, y_m)] 187 """Draws a polyline, where coor_list = [(x_0, y_0), (x_1, y_1), ... (x_m, y_m)]
187 specifies a polyline from (x_0, y_0) to (x_1, y_1), etc.""" 188 specifies a polyline from (x_0, y_0) to (x_1, y_1), etc."""
188 raise NotImplementedError 189 raise NotImplementedError
189 190
190 def fill_polyline(self, coor_list, color, thickness): 191 def fill_polyline(self, coor_list, color, thickness, snap=True):
191 """Draws a polyline (probably a polygon) and fills it.""" 192 """Draws a polyline (probably a polygon) and fills it."""
192 raise NotImplementedError 193 raise NotImplementedError
193 194
194 def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT, valign=AlignMode.BOTTOM): 195 def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL,
196 halign=AlignMode.LEFT, valign=AlignMode.BOTTOM, snap=True):
195 """Draws text at a position with a certain alignment.""" 197 """Draws text at a position with a certain alignment."""
196 raise NotImplementedError 198 raise NotImplementedError
197 199
198 def draw_label_with_sscripts(self, text, supscript, subscript, x, y, \ 200 def draw_label_with_sscripts(self, text, supscript, subscript, x, y, \
199 textfopts=GraphFormat.DEF_FOPTS_LABEL, 201 textfopts=GraphFormat.DEF_FOPTS_LABEL,
200 sscriptfopts=GraphFormat.DEF_FOPTS_LABEL_SSCRIPT, \ 202 sscriptfopts=GraphFormat.DEF_FOPTS_LABEL_SSCRIPT, \
201 halign=AlignMode.LEFT, valign=AlignMode.BOTTOM): 203 halign=AlignMode.LEFT, valign=AlignMode.BOTTOM, snap=True):
202 """Draws text at a position with a certain alignment, along with optionally a superscript and 204 """Draws text at a position with a certain alignment, along with optionally a superscript and
203 subscript (which are None if either is not used.)""" 205 subscript (which are None if either is not used.)"""
204 raise NotImplementedError 206 raise NotImplementedError
@@ -288,7 +290,7 @@ class Canvas(object):
288 ``start_tick'' and ``end_tick'' give the major ticks to start and end at for drawing vertical lines. 290 ``start_tick'' and ``end_tick'' give the major ticks to start and end at for drawing vertical lines.
289 ``start_item'' and ``end_item'' give the item boundaries to start and end drawing horizontal lines.""" 291 ``start_item'' and ``end_item'' give the item boundaries to start and end drawing horizontal lines."""
290 if start_tick > end_tick or start_item > end_item: 292 if start_tick > end_tick or start_item > end_item:
291 raise ValueError('start must be less than end') 293 return
292 294
293 line_width = (end_tick - start_tick) * maj_sep 295 line_width = (end_tick - start_tick) * maj_sep
294 line_height = (end_item - start_item) * item_size 296 line_height = (end_item - start_item) * item_size
@@ -490,7 +492,10 @@ class Canvas(object):
490 492
491 def add_sel_region(self, region): 493 def add_sel_region(self, region):
492 self.selectable_regions[region.get_event()] = region 494 self.selectable_regions[region.get_event()] = region
493 495
496 def get_sel_region(self, event):
497 return self.selectable_regions[event]
498
494 def get_selected_regions(self, real_x, real_y, width, height): 499 def get_selected_regions(self, real_x, real_y, width, height):
495 x = real_x + self.surface.virt_x 500 x = real_x + self.surface.virt_x
496 y = real_y + self.surface.virt_y 501 y = real_y + self.surface.virt_y
@@ -506,7 +511,7 @@ class Canvas(object):
506 def whiteout(self, real_x, real_y, width, height): 511 def whiteout(self, real_x, real_y, width, height):
507 """Overwrites the surface completely white, but technically doesn't delete anything""" 512 """Overwrites the surface completely white, but technically doesn't delete anything"""
508 self.fill_rect(self.surface.virt_x + real_x, self.surface.virt_y + real_y, width, 513 self.fill_rect(self.surface.virt_x + real_x, self.surface.virt_y + real_y, width,
509 height, (1.0, 1.0, 1.0)) 514 height, (1.0, 1.0, 1.0), False)
510 515
511 def get_item_color(self, n): 516 def get_item_color(self, n):
512 """Gets the nth color in the item color list, which are the colors used to draw the items 517 """Gets the nth color in the item color list, which are the colors used to draw the items
@@ -552,65 +557,84 @@ class CairoCanvas(Canvas):
552 """Gets the Surface that we are drawing on in its current state.""" 557 """Gets the Surface that we are drawing on in its current state."""
553 return self.surface 558 return self.surface
554 559
555 def _rect_common(self, x, y, width, height, color, thickness): 560 def _rect_common(self, x, y, width, height, color, thickness, do_snap=True):
556 x, y, width, height = self.scaled(x, y, width, height) 561 x, y, width, height = self.scaled(x, y, width, height)
557 x, y = self.surface.get_real_coor(x, y) 562 x, y = self.surface.get_real_coor(x, y)
558 self.surface.ctx.rectangle(snap(x), snap(y), width, height) 563 if do_snap:
564 self.surface.ctx.rectangle(snap(x), snap(y), width, height)
565 else:
566 self.surface.ctx.rectangle(x, y, width, height)
567
559 self.surface.ctx.set_line_width(thickness * self.scale) 568 self.surface.ctx.set_line_width(thickness * self.scale)
560 self.surface.ctx.set_source_rgb(color[0], color[1], color[2]) 569 self.surface.ctx.set_source_rgb(color[0], color[1], color[2])
561 570
562 def draw_rect(self, x, y, width, height, color, thickness): 571 def draw_rect(self, x, y, width, height, color, thickness, do_snap=True):
563 self._rect_common(x, y, width, height, color, thickness) 572 self._rect_common(x, y, width, height, color, thickness, do_snap)
564 self.surface.ctx.stroke() 573 self.surface.ctx.stroke()
565 574
566 def fill_rect(self, x, y, width, height, color): 575 def fill_rect(self, x, y, width, height, color, do_snap=True):
567 self._rect_common(x, y, width, height, color, 1) 576 self._rect_common(x, y, width, height, color, 1, do_snap)
568 self.surface.ctx.fill() 577 self.surface.ctx.fill()
569 578
570 def fill_rect_fade(self, x, y, width, height, lcolor, rcolor): 579 def fill_rect_fade(self, x, y, width, height, lcolor, rcolor, do_snap=True):
571 """Draws a rectangle somewhere, filled in with the fade.""" 580 """Draws a rectangle somewhere, filled in with the fade."""
572 x, y, width, height = self.scaled(x, y, width, height) 581 x, y, width, height = self.scaled(x, y, width, height)
573 x, y = self.surface.get_real_coor(x, y) 582 x, y = self.surface.get_real_coor(x, y)
574 583
575 linear = cairo.LinearGradient(snap(x), snap(y), \ 584 if do_snap:
585 linear = cairo.LinearGradient(snap(x), snap(y), \
576 snap(x + width), snap(y + height)) 586 snap(x + width), snap(y + height))
587 else:
588 linear = cairo.LinearGradient(x, y, \
589 x + width, y + height)
577 linear.add_color_stop_rgb(0.0, lcolor[0], lcolor[1], lcolor[2]) 590 linear.add_color_stop_rgb(0.0, lcolor[0], lcolor[1], lcolor[2])
578 linear.add_color_stop_rgb(1.0, rcolor[0], rcolor[1], rcolor[2]) 591 linear.add_color_stop_rgb(1.0, rcolor[0], rcolor[1], rcolor[2])
579 self.surface.ctx.set_source(linear) 592 self.surface.ctx.set_source(linear)
580 self.surface.ctx.rectangle(snap(x), snap(y), width, height) 593 if do_snap:
594 self.surface.ctx.rectangle(snap(x), snap(y), width, height)
595 else:
596 self.surface.ctx.rectangle(snap(x), snap(y), width, height)
581 self.surface.ctx.fill() 597 self.surface.ctx.fill()
582 598
583 def draw_line(self, p0, p1, color, thickness): 599 def draw_line(self, p0, p1, color, thickness, do_snap=True):
584 """Draws a line from p0 to p1 with a certain color and thickness.""" 600 """Draws a line from p0 to p1 with a certain color and thickness."""
585 p0 = self.scaled(p0[0], p0[1]) 601 p0 = self.scaled(p0[0], p0[1])
586 p0 = self.surface.get_real_coor(p0[0], p0[1]) 602 p0 = self.surface.get_real_coor(p0[0], p0[1])
587 p1 = self.scaled(p1[0], p1[1]) 603 p1 = self.scaled(p1[0], p1[1])
588 p1 = self.surface.get_real_coor(p1[0], p1[1]) 604 p1 = self.surface.get_real_coor(p1[0], p1[1])
605 if do_snap:
606 p0 = (snap(p0[0]), snap(p0[1]))
607 p1 = (snap(p1[0]), snap(p1[1]))
608
589 self.surface.ctx.move_to(p0[0], p0[1]) 609 self.surface.ctx.move_to(p0[0], p0[1])
590 self.surface.ctx.line_to(p1[0], p1[1]) 610 self.surface.ctx.line_to(p1[0], p1[1])
591 self.surface.ctx.set_source_rgb(color[0], color[1], color[2]) 611 self.surface.ctx.set_source_rgb(color[0], color[1], color[2])
592 self.surface.ctx.set_line_width(thickness * self.scale) 612 self.surface.ctx.set_line_width(thickness * self.scale)
593 self.surface.ctx.stroke() 613 self.surface.ctx.stroke()
594 614
595 def _polyline_common(self, coor_list, color, thickness): 615 def _polyline_common(self, coor_list, color, thickness, do_snap=True):
596 real_coor_list = [self.surface.get_real_coor(coor[0], coor[1]) for coor in coor_list] 616 real_coor_list = [self.surface.get_real_coor(coor[0], coor[1]) for coor in coor_list]
597 self.surface.ctx.move_to(real_coor_list[0][0], real_coor_list[0][1]) 617 self.surface.ctx.move_to(real_coor_list[0][0], real_coor_list[0][1])
618 if do_snap:
619 for i in range(0, len(real_coor_list)):
620 real_coor_list[i] = (snap(real_coor_list[i][0]), snap(real_coor_list[i][1]))
621
598 for coor in real_coor_list[1:]: 622 for coor in real_coor_list[1:]:
599 self.surface.ctx.line_to(coor[0], coor[1]) 623 self.surface.ctx.line_to(coor[0], coor[1])
600 624
601 self.surface.ctx.set_line_width(thickness) 625 self.surface.ctx.set_line_width(thickness)
602 self.surface.ctx.set_source_rgb(color[0], color[1], color[2]) 626 self.surface.ctx.set_source_rgb(color[0], color[1], color[2])
603 627
604 def draw_polyline(self, coor_list, color, thickness): 628 def draw_polyline(self, coor_list, color, thickness, do_snap=True):
605 self._polyline_common(coor_list, color, thickness) 629 self._polyline_common(coor_list, color, thickness, do_snap)
606 self.surface.ctx.stroke() 630 self.surface.ctx.stroke()
607 631
608 def fill_polyline(self, coor_list, color, thickness): 632 def fill_polyline(self, coor_list, color, thickness, do_snap=True):
609 self._polyline_common(coor_list, color, thickness) 633 self._polyline_common(coor_list, color, thickness, do_snap)
610 self.surface.ctx.fill() 634 self.surface.ctx.fill()
611 635
612 def _draw_label_common(self, text, x, y, fopts, x_bearing_factor, \ 636 def _draw_label_common(self, text, x, y, fopts, x_bearing_factor, \
613 f_descent_factor, width_factor, f_height_factor): 637 f_descent_factor, width_factor, f_height_factor, do_snap=True):
614 """Helper function for drawing a label with some alignment. Instead of taking in an alignment, 638 """Helper function for drawing a label with some alignment. Instead of taking in an alignment,
615 it takes in the scale factor for the font extent parameters, which give the raw data of how much to adjust 639 it takes in the scale factor for the font extent parameters, which give the raw data of how much to adjust
616 the x and y parameters. Only should be used internally.""" 640 the x and y parameters. Only should be used internally."""
@@ -633,11 +657,14 @@ class CairoCanvas(Canvas):
633 657
634 self.surface.ctx.set_source_rgb(fopts.color[0], fopts.color[1], fopts.color[2]) 658 self.surface.ctx.set_source_rgb(fopts.color[0], fopts.color[1], fopts.color[2])
635 659
636 self.surface.ctx.move_to(snap(actual_x), snap(actual_y)) 660 if do_snap:
637 661 self.surface.ctx.move_to(snap(actual_x), snap(actual_y))
662 else:
663 self.surface.ctx.move_to(actual_x, actual_y)
664
638 self.surface.ctx.show_text(text) 665 self.surface.ctx.show_text(text)
639 666
640 def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT, valign=AlignMode.BOTTOM): 667 def draw_label(self, text, x, y, fopts=GraphFormat.DEF_FOPTS_LABEL, halign=AlignMode.LEFT, valign=AlignMode.BOTTOM, do_snap=True):
641 """Draws a label with the given parameters, with the given horizontal and vertical justification. One can override 668 """Draws a label with the given parameters, with the given horizontal and vertical justification. One can override
642 the color from ``fopts'' by passing something in to ``pattern'', which overrides the color with an arbitrary 669 the color from ``fopts'' by passing something in to ``pattern'', which overrides the color with an arbitrary
643 pattern.""" 670 pattern."""
@@ -653,11 +680,11 @@ class CairoCanvas(Canvas):
653 f_descent_factor, f_height_factor = valign_factors[valign] 680 f_descent_factor, f_height_factor = valign_factors[valign]
654 681
655 self._draw_label_common(text, x, y, fopts, x_bearing_factor, \ 682 self._draw_label_common(text, x, y, fopts, x_bearing_factor, \
656 f_descent_factor, width_factor, f_height_factor) 683 f_descent_factor, width_factor, f_height_factor, do_snap)
657 684
658 def draw_label_with_sscripts(self, text, supscript, subscript, x, y, \ 685 def draw_label_with_sscripts(self, text, supscript, subscript, x, y, \
659 textfopts=GraphFormat.DEF_FOPTS_LABEL, sscriptfopts=GraphFormat.DEF_FOPTS_LABEL_SSCRIPT, \ 686 textfopts=GraphFormat.DEF_FOPTS_LABEL, sscriptfopts=GraphFormat.DEF_FOPTS_LABEL_SSCRIPT, \
660 halign=AlignMode.LEFT, valign=AlignMode.BOTTOM): 687 halign=AlignMode.LEFT, valign=AlignMode.BOTTOM, do_snap=True):
661 """Draws a label, but also optionally allows a superscript and subscript to be rendered.""" 688 """Draws a label, but also optionally allows a superscript and subscript to be rendered."""
662 self.draw_label(text, x, y, textfopts, halign, valign) 689 self.draw_label(text, x, y, textfopts, halign, valign)
663 690
@@ -672,14 +699,14 @@ class CairoCanvas(Canvas):
672 xtmp = x + x_advance 699 xtmp = x + x_advance
673 ytmp = y 700 ytmp = y
674 ytmp = y - f_height / 4.0 701 ytmp = y - f_height / 4.0
675 self.draw_label(supscript, xtmp, ytmp, sscriptfopts, halign, valign) 702 self.draw_label(supscript, xtmp, ytmp, sscriptfopts, halign, valign, do_snap)
676 if subscript is not None: 703 if subscript is not None:
677 f_height = fe[2] 704 f_height = fe[2]
678 x_advance = te[4] 705 x_advance = te[4]
679 xtmp = x + x_advance 706 xtmp = x + x_advance
680 ytmp = y 707 ytmp = y
681 ytmp = y + f_height / 4.0 708 ytmp = y + f_height / 4.0
682 self.draw_label(subscript, xtmp, ytmp, sscriptfopts, halign, valign) 709 self.draw_label(subscript, xtmp, ytmp, sscriptfopts, halign, valign, do_snap)
683 710
684# represents a selectable region of the graph 711# represents a selectable region of the graph
685class SelectableRegion(object): 712class SelectableRegion(object):
@@ -769,11 +796,27 @@ class Graph(object):
769 def get_attrs(self): 796 def get_attrs(self):
770 return self.attrs 797 return self.attrs
771 798
799 def add_sel_region(self, region):
800 self.canvas.add_sel_region(region)
801
802 def get_sel_region(self, event):
803 return self.canvas.get_sel_region(event)
804
772 def update_view(self, x, y, width, height, ctx): 805 def update_view(self, x, y, width, height, ctx):
773 """Proxy into the surface's pan.""" 806 """Proxy into the surface's pan."""
774 self.canvas.surface.pan(x, y, width, height) 807 self.canvas.surface.pan(x, y, width, height)
775 self.canvas.surface.change_ctx(ctx) 808 self.canvas.surface.change_ctx(ctx)
776 809
810 def _recomp_min_max(self, start_time, end_time, start_item, end_item):
811 if self.min_time is None or start_time < self.min_time:
812 self.min_time = start_time
813 if self.max_time is None or end_time > self.max_time:
814 self.max_time = end_time
815 if self.min_item is None or start_item < self.min_item:
816 self.min_item = start_item
817 if self.max_item is None or end_item > self.max_item:
818 self.max_item = end_item
819
777 def _get_time_xpos(self, time): 820 def _get_time_xpos(self, time):
778 """get x so that x is at instant ``time'' on the graph""" 821 """get x so that x is at instant ``time'' on the graph"""
779 return self.origin[0] + GraphFormat.X_AXIS_MEASURE_OFS + 1.0 * (time - self.start_time) / self.attrs.time_per_maj * self.attrs.maj_sep 822 return self.origin[0] + GraphFormat.X_AXIS_MEASURE_OFS + 1.0 * (time - self.start_time) / self.attrs.time_per_maj * self.attrs.maj_sep
@@ -820,7 +863,7 @@ class Graph(object):
820 end_time = self.xcoor_to_time(self.canvas.surface.virt_x + real_x + width) 863 end_time = self.xcoor_to_time(self.canvas.surface.virt_x + real_x + width)
821 864
822 start_item = self.ycoor_to_item_no(self.canvas.surface.virt_y + real_y) 865 start_item = self.ycoor_to_item_no(self.canvas.surface.virt_y + real_y)
823 end_item = 1 + self.ycoor_to_item_no(self.canvas.surface.virt_y + real_y + height) 866 end_item = 2 + self.ycoor_to_item_no(self.canvas.surface.virt_y + real_y + height)
824 867
825 return (start_time, end_time, start_item, end_item) 868 return (start_time, end_time, start_item, end_item)
826 869
@@ -829,7 +872,7 @@ class Graph(object):
829 self.draw_x_axis_with_labels_at_time(start_time, end_time) 872 self.draw_x_axis_with_labels_at_time(start_time, end_time)
830 self.draw_y_axis_with_labels() 873 self.draw_y_axis_with_labels()
831 874
832 def render_surface(self, sched, real_x, real_y, width, height, selectable=False): 875 def render_surface(self, sched, regions, selectable=False):
833 raise NotImplementedError 876 raise NotImplementedError
834 877
835 def render_all(self, schedule): 878 def render_all(self, schedule):
@@ -946,22 +989,40 @@ class Graph(object):
946 raise NotImplementedError 989 raise NotImplementedError
947 990
948class TaskGraph(Graph): 991class TaskGraph(Graph):
949 def render_surface(self, sched, real_x, real_y, width, height, selectable=False): 992 def render_surface(self, sched, regions, selectable=False):
950 if not selectable: 993 events_to_render = {}
951 self.canvas.whiteout(real_x, real_y, width, height) 994 for layer in Canvas.LAYERS:
952 else: 995 events_to_render[layer] = {}
953 self.canvas.clear_selectable_regions(real_x, real_y, width, height) 996
997 for region in regions:
998 x, y, width, height = region
999 if not selectable:
1000 self.canvas.whiteout(x, y, width, height)
1001 else:
1002 self.canvas.clear_selectable_regions(x, y, width, height)
954 1003
955 start_time, end_time, start_item, end_item = self.get_offset_params(real_x, real_y, width, height) 1004 self.min_time, self.max_time, self.min_item, self.max_item = None, None, None, None
1005 for region in regions:
1006 x, y, width, height = region
1007 start_time, end_time, start_item, end_item = self.get_offset_params(x, y, width, height)
1008 self._recomp_min_max(start_time, end_time, start_item, end_item)
1009
1010 for event in sched.get_time_slot_array().iter_over_period(
1011 start_time, end_time, start_item, end_item,
1012 schedule.TimeSlotArray.TASK_LIST, schedule.EVENT_LIST):
1013 events_to_render[event.get_layer()][event] = None
956 1014
957 if not selectable: 1015 if not selectable:
958 self.draw_skeleton(start_time, end_time, start_item, end_item) 1016 self.draw_skeleton(self.min_time, self.max_time,
1017 self.min_item, self.max_item)
959 1018
1019 #if not selectable:
1020 # for layer in events_to_render:
1021 # print 'task render on layer', layer, ':', [str(e) for e in events_to_render[layer].keys()]
1022
960 for layer in Canvas.LAYERS: 1023 for layer in Canvas.LAYERS:
961 prev_events = {} 1024 prev_events = {}
962 for event in sched.get_time_slot_array().iter_over_period( 1025 for event in events_to_render[layer]:
963 start_time, end_time, start_item, end_item,
964 schedule.TimeSlotArray.TASK_LIST, schedule.EVENT_LIST):
965 event.render(self, layer, prev_events, selectable) 1026 event.render(self, layer, prev_events, selectable)
966 1027
967 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): 1028 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
@@ -1093,42 +1154,54 @@ class TaskGraph(Graph):
1093 self.canvas.add_sel_mini_bar(x, y, width, height, event) 1154 self.canvas.add_sel_mini_bar(x, y, width, height, event)
1094 1155
1095class CpuGraph(Graph): 1156class CpuGraph(Graph):
1096 def render_surface(self, sched, real_x, real_y, width, height, selectable=False): 1157 def render_surface(self, sched, regions, selectable=False):
1097 if not selectable: 1158 BOTTOM_EVENTS = [schedule.ReleaseEvent, schedule.DeadlineEvent, schedule.InversionStartEvent,
1098 self.canvas.whiteout(real_x, real_y, width, height) 1159 schedule.InversionEndEvent, schedule.InversionDummy]
1099 else: 1160 TOP_EVENTS = [schedule.SuspendEvent, schedule.ResumeEvent, schedule.CompleteEvent,
1100 self.canvas.clear_selectable_regions(real_x, real_y, width, height) 1161 schedule.SwitchAwayEvent, schedule.SwitchToEvent, schedule.IsRunningDummy]
1101 1162
1102 start_time, end_time, start_item, end_item = self.get_offset_params(real_x, real_y, width, height) 1163 events_to_render = {}
1103 1164 for layer in Canvas.LAYERS:
1104 if not selectable: 1165 events_to_render[layer] = {}
1105 self.draw_skeleton(start_time, end_time, start_item, end_item) 1166
1106 1167 self.min_time, self.max_time, self.min_item, self.max_item = None, None, None, None
1107 event_list = dict(schedule.EVENT_LIST) 1168 for region in regions:
1169 x, y, width, height = region
1170 if not selectable:
1171 #self.canvas.whiteout(x, y, width, height)
1172 self.canvas.whiteout(0, 0, self.canvas.surface.width, self.canvas.surface.height)
1173 else:
1174 self.canvas.clear_selectable_regions(x, y, width, height)
1108 1175
1109 bottom_events = [schedule.ReleaseEvent, schedule.DeadlineEvent, schedule.InversionStartEvent, 1176 for region in regions:
1110 schedule.InversionEndEvent, schedule.InversionDummy] 1177 x, y, width, height = region
1178 start_time, end_time, start_item, end_item = self.get_offset_params(x, y, width, height)
1179 self._recomp_min_max(start_time, end_time, start_item, end_item)
1111 1180
1112 for event in bottom_events:
1113 del event_list[event]
1114
1115 for layer in Canvas.LAYERS:
1116 prev_events = {}
1117 for event in sched.get_time_slot_array().iter_over_period( 1181 for event in sched.get_time_slot_array().iter_over_period(
1118 start_time, end_time, start_item, end_item, 1182 start_time, end_time, start_item, end_item,
1119 schedule.TimeSlotArray.CPU_LIST, schedule.EVENT_LIST): 1183 schedule.TimeSlotArray.CPU_LIST,
1120 event.render(self, layer, prev_events, selectable) 1184 TOP_EVENTS):
1185 events_to_render[event.get_layer()][event] = None
1121 1186
1122 if end_item >= len(self.y_item_list): 1187 if end_item >= len(self.y_item_list):
1123 # we are far down enough that we should render the releases and deadlines and inversions, 1188 # we are far down enough that we should render the releases and deadlines and inversions,
1124 # which appear near the x-axis 1189 # which appear near the x-axis
1125 for layer in Canvas.LAYERS: 1190 x, y, width, height = region
1126 prev_events = {}
1127 for event in sched.get_time_slot_array().iter_over_period( 1191 for event in sched.get_time_slot_array().iter_over_period(
1128 start_time, end_time, 0, sched.get_num_cpus(), 1192 start_time, end_time, 0, sched.get_num_cpus(),
1129 schedule.TimeSlotArray.CPU_LIST, 1193 schedule.TimeSlotArray.CPU_LIST,
1130 bottom_events): 1194 BOTTOM_EVENTS):
1131 event.render(self, layer, prev_events, selectable) 1195 events_to_render[event.get_layer()][event] = None
1196
1197 if not selectable:
1198 self.draw_skeleton(self.min_time, self.max_time,
1199 self.min_item, self.max_item)
1200
1201 for layer in Canvas.LAYERS:
1202 prev_events = {}
1203 for event in events_to_render[layer]:
1204 event.render(self, layer, prev_events, selectable)
1132 1205
1133 def render(self, schedule, start_time=None, end_time=None): 1206 def render(self, schedule, start_time=None, end_time=None):
1134 if end_time < start_time: 1207 if end_time < start_time:
diff --git a/unit_trace/viz/format.py b/unit_trace/viz/format.py
index 6469467..33e0b03 100644
--- a/unit_trace/viz/format.py
+++ b/unit_trace/viz/format.py
@@ -31,6 +31,9 @@ class GraphFormat(object):
31 GRID_THICKNESS = 1 31 GRID_THICKNESS = 1
32 AXIS_THICKNESS = 1 32 AXIS_THICKNESS = 1
33 33
34 BAND_THICKNESS = 1.5
35 BAND_COLOR = (0.85, 0.0, 0.0)
36
34 X_AXIS_MEASURE_OFS = 30 37 X_AXIS_MEASURE_OFS = 30
35 X_AXIS_LABEL_GAP = 10 38 X_AXIS_LABEL_GAP = 10
36 Y_AXIS_ITEM_GAP = 10 39 Y_AXIS_ITEM_GAP = 10
diff --git a/unit_trace/viz/schedule.py b/unit_trace/viz/schedule.py
index ce062a3..4277f1a 100644
--- a/unit_trace/viz/schedule.py
+++ b/unit_trace/viz/schedule.py
@@ -65,6 +65,7 @@ class TimeSlotArray(object):
65 for slot in range(start_slot + 1, end_slot): 65 for slot in range(start_slot + 1, end_slot):
66 dummy = span_events[span_event](task_no, cpu) 66 dummy = span_events[span_event](task_no, cpu)
67 dummy.corresp_start_event = event.corresp_start_event 67 dummy.corresp_start_event = event.corresp_start_event
68 dummy.corresp_end_event = event
68 69
69 self._put_event_in_slot(TimeSlotArray.TASK_LIST, task_no, dummy.__class__, slot, dummy) 70 self._put_event_in_slot(TimeSlotArray.TASK_LIST, task_no, dummy.__class__, slot, dummy)
70 self._put_event_in_slot(TimeSlotArray.CPU_LIST, cpu, dummy.__class__, slot, dummy) 71 self._put_event_in_slot(TimeSlotArray.CPU_LIST, cpu, dummy.__class__, slot, dummy)
@@ -151,7 +152,7 @@ class Schedule(object):
151 for event_time in sorted(job.get_events().keys()): 152 for event_time in sorted(job.get_events().keys()):
152 # could have multiple events at the same time (unlikely but possible) 153 # could have multiple events at the same time (unlikely but possible)
153 for event in job.get_events()[event_time]: 154 for event in job.get_events()[event_time]:
154 print 'task, job, event:', task.name, job.job_no, event.__class__.__name__ 155 #print 'task, job, event:', task.name, job.job_no, event.__class__.__name__
155 event.scan(cur_cpu, switches) 156 event.scan(cur_cpu, switches)
156 157
157 def add_task(self, task): 158 def add_task(self, task):
@@ -395,7 +396,7 @@ class SwitchAwayEvent(Event):
395 def __str__(self): 396 def __str__(self):
396 if self.corresp_start_event is None: 397 if self.corresp_start_event is None:
397 return 'Switch Away (w/o Switch To)' + self._common_str() + 'TIME=' \ 398 return 'Switch Away (w/o Switch To)' + self._common_str() + 'TIME=' \
398 + self.get_time() 399 + str(self.get_time())
399 return str(self.corresp_start_event) 400 return str(self.corresp_start_event)
400 401
401 def scan(self, cur_cpu, switches): 402 def scan(self, cur_cpu, switches):
@@ -422,9 +423,25 @@ class SwitchAwayEvent(Event):
422 super(SwitchAwayEvent, self).scan(cur_cpu, switches) 423 super(SwitchAwayEvent, self).scan(cur_cpu, switches)
423 424
424 def render(self, graph, layer, prev_events, selectable=False): 425 def render(self, graph, layer, prev_events, selectable=False):
425 if self.corresp_start_event is None or self.corresp_start_event in prev_events: 426 if self.corresp_start_event is None:
426 return # erroneous switch away or already rendered 427 # We never found a corresponding start event. In that case, we can assume it lies
427 self.corresp_start_event.render(graph, layer, prev_events, selectable) 428 # in some part of the trace that was never read in. So draw a bar starting from
429 # the very beginning.
430 if layer == self.layer:
431 prev_events[self] = None
432 cpu = self.get_cpu()
433 task_no = self.get_job().get_task().get_task_no()
434 if selectable:
435 start = self.get_job().get_task().get_schedule().start
436 graph.add_sel_bar_at_time(start, self.get_time(),
437 task_no, cpu, self)
438 else:
439 graph.draw_bar_at_time(start, self.get_time(),
440 task_no, cpu, self.get_job().get_job_no(), self.is_selected())
441 else:
442 if self.corresp_start_event in prev_events:
443 return # already rendered the bar
444 self.corresp_start_event.render(graph, layer, prev_events, selectable)
428 445
429class SwitchToEvent(Event): 446class SwitchToEvent(Event):
430 def __init__(self, time, cpu): 447 def __init__(self, time, cpu):
@@ -434,7 +451,7 @@ class SwitchToEvent(Event):
434 def __str__(self): 451 def __str__(self):
435 if self.corresp_end_event is None: 452 if self.corresp_end_event is None:
436 return 'Switch To (w/o Switch Away)' + self._common_str() + ', TIME=' \ 453 return 'Switch To (w/o Switch Away)' + self._common_str() + ', TIME=' \
437 + self.get_time() 454 + str(self.get_time())
438 return 'Scheduled' + self._common_str() + ', START=' \ 455 return 'Scheduled' + self._common_str() + ', START=' \
439 + util.format_float(self.get_time(), Event.NUM_DEC_PLACES) \ 456 + util.format_float(self.get_time(), Event.NUM_DEC_PLACES) \
440 + ', END=' + util.format_float(self.corresp_end_event.get_time(), Event.NUM_DEC_PLACES) 457 + ', END=' + util.format_float(self.corresp_end_event.get_time(), Event.NUM_DEC_PLACES)
@@ -454,15 +471,21 @@ class SwitchToEvent(Event):
454 def render(self, graph, layer, prev_events, selectable=False): 471 def render(self, graph, layer, prev_events, selectable=False):
455 if self.corresp_end_event is None: 472 if self.corresp_end_event is None:
456 return # fatally erroneous switch to 473 return # fatally erroneous switch to
457 if layer == Canvas.BOTTOM_LAYER: 474 if layer == self.layer:
475 end_time = None
476 if self.corresp_end_event is None:
477 end_time = self.get_job().get_task().get_schedule().end
478 else:
479 end_time = self.corresp_end_event.get_time()
480
458 prev_events[self] = None 481 prev_events[self] = None
459 cpu = self.get_cpu() 482 cpu = self.get_cpu()
460 task_no = self.get_job().get_task().get_task_no() 483 task_no = self.get_job().get_task().get_task_no()
461 if selectable: 484 if selectable:
462 graph.add_sel_bar_at_time(self.get_time(), self.corresp_end_event.get_time(), 485 graph.add_sel_bar_at_time(self.get_time(), end_time,
463 task_no, cpu, self) 486 task_no, cpu, self)
464 else: 487 else:
465 graph.draw_bar_at_time(self.get_time(), self.corresp_end_event.get_time(), 488 graph.draw_bar_at_time(self.get_time(), end_time,
466 task_no, cpu, self.get_job().get_job_no(), self.is_selected()) 489 task_no, cpu, self.get_job().get_job_no(), self.is_selected())
467 490
468class ReleaseEvent(Event): 491class ReleaseEvent(Event):
@@ -530,15 +553,21 @@ class InversionStartEvent(ErrorEvent):
530 super(InversionStartEvent, self).scan(cur_cpu, switches) 553 super(InversionStartEvent, self).scan(cur_cpu, switches)
531 554
532 def render(self, graph, layer, prev_events, selectable=False): 555 def render(self, graph, layer, prev_events, selectable=False):
533 if layer == Canvas.BOTTOM_LAYER: 556 end_time = None
557 if self.corresp_end_event is None:
558 end_time = self.get_job().get_task().get_schedule().end
559 else:
560 end_time = self.corresp_end_event.get_time()
561
562 if layer == self.layer:
534 prev_events[self] = None 563 prev_events[self] = None
535 cpu = self.get_cpu() 564 cpu = self.get_cpu()
536 task_no = self.get_job().get_task().get_task_no() 565 task_no = self.get_job().get_task().get_task_no()
537 if selectable: 566 if selectable:
538 graph.add_sel_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(), 567 graph.add_sel_mini_bar_at_time(self.get_time(), end_time,
539 task_no, cpu, self) 568 task_no, cpu, self)
540 else: 569 else:
541 graph.draw_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(), 570 graph.draw_mini_bar_at_time(self.get_time(), end_time,
542 task_no, cpu, self.get_job().get_job_no(), self.is_selected()) 571 task_no, cpu, self.get_job().get_job_no(), self.is_selected())
543 572
544 573
@@ -570,19 +599,45 @@ class InversionEndEvent(ErrorEvent):
570 super(InversionEndEvent, self).scan(cur_cpu, switches) 599 super(InversionEndEvent, self).scan(cur_cpu, switches)
571 600
572 def render(self, graph, layer, prev_events, selectable=False): 601 def render(self, graph, layer, prev_events, selectable=False):
573 if self.corresp_start_event is None or self.corresp_start_event in prev_events: 602 if self.corresp_start_event is None:
574 return # erroneous inversion end or already rendered 603 # We never found a corresponding start event. In that case, we can assume it lies
575 self.corresp_start_event.render(graph, layer, prev_events, selectable) 604 # in some part of the trace that was never read in. So draw a bar starting from
605 # the very beginning.
606 if layer == self.layer:
607 prev_events[self] = None
608 cpu = self.get_cpu()
609 task_no = self.get_job().get_task().get_task_no()
610 if selectable:
611 start = self.get_job().get_task().get_schedule().start
612 graph.add_sel_mini_bar_at_time(start, self.get_time(),
613 task_no, cpu, self)
614 else:
615 graph.draw_mini_bar_at_time(start, self.get_time(),
616 task_no, cpu, self.get_job().get_job_no(), self.is_selected())
617 else:
618 if self.corresp_start_event in prev_events:
619 return # already rendered the bar
620 self.corresp_start_event.render(graph, layer, prev_events, selectable)
576 621
577class InversionDummy(DummyEvent): 622class InversionDummy(DummyEvent):
623 def __init__(self, time, cpu):
624 super(InversionDummy, self).__init__(time, Event.NO_CPU)
625 self.layer = Canvas.BOTTOM_LAYER
626
578 def render(self, graph, layer, prev_events, selectable=False): 627 def render(self, graph, layer, prev_events, selectable=False):
579 if self.corresp_start_event in prev_events: 628 if self.corresp_start_event is not None and self.corresp_start_event in prev_events:
580 return # we have already been rendered 629 return # we have already been rendered
630 if self.corresp_end_event is not None and self.corresp_end_event in prev_events:
631 return
581 self.corresp_start_event.render(graph, layer, prev_events, selectable) 632 self.corresp_start_event.render(graph, layer, prev_events, selectable)
582 633
583class IsRunningDummy(DummyEvent): 634class IsRunningDummy(DummyEvent):
635 def __init__(self, time, cpu):
636 super(IsRunningDummy, self).__init__(time, Event.NO_CPU)
637 self.layer = Canvas.BOTTOM_LAYER
638
584 def render(self, graph, layer, prev_events, selectable=False): 639 def render(self, graph, layer, prev_events, selectable=False):
585 if self.corresp_start_event in prev_events: 640 if self.corresp_start_event is None or self.corresp_start_event in prev_events:
586 return # we have already been rendered 641 return # we have already been rendered
587 self.corresp_start_event.render(graph, layer, prev_events, selectable) 642 self.corresp_start_event.render(graph, layer, prev_events, selectable)
588 643
diff --git a/unit_trace/viz/viewer.py b/unit_trace/viz/viewer.py
index 120c7e7..236a467 100644
--- a/unit_trace/viz/viewer.py
+++ b/unit_trace/viz/viewer.py
@@ -9,6 +9,7 @@ from renderer import *
9import pygtk 9import pygtk
10import gtk 10import gtk
11import gobject 11import gobject
12import copy
12 13
13class GraphContextMenu(gtk.Menu): 14class GraphContextMenu(gtk.Menu):
14 MAX_STR_LEN = 80 15 MAX_STR_LEN = 80
@@ -30,8 +31,7 @@ class GraphArea(gtk.DrawingArea):
30 VERT_PAGE_SCROLL_FACTOR = 3.0 31 VERT_PAGE_SCROLL_FACTOR = 3.0
31 VERT_STEP_SCROLL_FACTOR = 0.5 32 VERT_STEP_SCROLL_FACTOR = 0.5
32 33
33 SELECT_THICKNESS = 1.5 34 REFRESH_INFLATION_FACTOR = 20.0
34 SELECT_COLOR = (0.85, 0.0, 0.0)
35 35
36 def __init__(self, renderer): 36 def __init__(self, renderer):
37 super(GraphArea, self).__init__() 37 super(GraphArea, self).__init__()
@@ -46,11 +46,11 @@ class GraphArea(gtk.DrawingArea):
46 self.set_set_scroll_adjustments_signal('set-scroll-adjustments') 46 self.set_set_scroll_adjustments_signal('set-scroll-adjustments')
47 47
48 self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK | 48 self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK |
49 gtk.gdk.BUTTON_RELEASE_MASK) 49 gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.EXPOSURE_MASK)
50 50
51 self.left_button_start_coor = None 51 self.band_rect = None
52 self.select_rect = None
53 self.ctrl_clicked = False 52 self.ctrl_clicked = False
53 self.dirtied_regions = []
54 54
55 self.connect('expose-event', self.expose) 55 self.connect('expose-event', self.expose)
56 self.connect('size-allocate', self.size_allocate) 56 self.connect('size-allocate', self.size_allocate)
@@ -59,23 +59,34 @@ class GraphArea(gtk.DrawingArea):
59 self.connect('button-release-event', self.button_release) 59 self.connect('button-release-event', self.button_release)
60 self.connect('motion-notify-event', self.motion_notify) 60 self.connect('motion-notify-event', self.motion_notify)
61 61
62 def expose(self, widget, event, data=None): 62 def expose(self, widget, expose_event, data=None):
63 ctx = widget.window.cairo_create() 63 ctx = widget.window.cairo_create()
64 graph = self.renderer.get_graph() 64 graph = self.renderer.get_graph()
65 graph.update_view(self.cur_x, self.cur_y, self.width, self.height, ctx) 65 graph.update_view(self.cur_x, self.cur_y, self.width, self.height, ctx)
66 graph.render_surface(self.renderer.get_schedule(), 0, 0, self.width, self.height)
67 66
68 # render dragging rectangle, if there is one 67 # We ourselves didn't update dirtied_regions, so this means that X or the
69 if self.select_rect is not None: 68 # window manager must have caused the expose event. So just update the
70 x, y, width, height = self.select_rect 69 # expose_event's bounding area.
71 thickness = GraphArea.SELECT_THICKNESS 70 if not self.dirtied_regions or expose_event.send_event:
72 color = GraphArea.SELECT_COLOR 71 print 'forced expose'
72 self.dirtied_regions = [(expose_event.area.x, expose_event.area.y,
73 expose_event.area.width, expose_event.area.height)]
74
75 graph.render_surface(self.renderer.get_schedule(), self.dirtied_regions)
76
77 # render dragging band rectangle, if there is one
78 if self.band_rect is not None:
79 x, y, width, height = self.band_rect
80 thickness = GraphFormat.BAND_THICKNESS
81 color = GraphFormat.BAND_COLOR
73 82
74 ctx.rectangle(x, y, width, height) 83 ctx.rectangle(x, y, width, height)
75 ctx.set_line_width(thickness) 84 ctx.set_line_width(thickness)
76 ctx.set_source_rgb(color[0], color[1], color[2]) 85 ctx.set_source_rgb(color[0], color[1], color[2])
77 ctx.stroke() 86 ctx.stroke()
78 87
88 self.dirtied_regions = []
89
79 def get_renderer(self): 90 def get_renderer(self):
80 return self.renderer 91 return self.renderer
81 92
@@ -100,15 +111,13 @@ class GraphArea(gtk.DrawingArea):
100 self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width()) 111 self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width())
101 self.cur_x = max(adjustment.value, 0.0) 112 self.cur_x = max(adjustment.value, 0.0)
102 113
103 rect = gtk.gdk.Rectangle(0, 0, self.width, self.height) 114 self._dirty(0, 0, self.width, self.height)
104 self.window.invalidate_rect(rect, True)
105 115
106 def vertical_value_changed(self, adjustment): 116 def vertical_value_changed(self, adjustment):
107 self.cur_y = min(adjustment.value, self.renderer.get_graph().get_height()) 117 self.cur_y = min(adjustment.value, self.renderer.get_graph().get_height())
108 self.cur_y = max(adjustment.value, 0.0) 118 self.cur_y = max(adjustment.value, 0.0)
109 119
110 rect = gtk.gdk.Rectangle(0, 0, self.width, self.height) 120 self._dirty(0, 0, self.width, self.height)
111 self.window.invalidate_rect(rect, True)
112 121
113 def size_allocate(self, widget, allocation): 122 def size_allocate(self, widget, allocation):
114 self.width = allocation.width 123 self.width = allocation.width
@@ -133,13 +142,25 @@ class GraphArea(gtk.DrawingArea):
133 if event.get_layer() > max_layer: 142 if event.get_layer() > max_layer:
134 max_layer = event.get_layer() 143 max_layer = event.get_layer()
135 return max_layer 144 return max_layer
136 145
146 def _update_event_changes(self, list1, list2):
147 # if an event changed selected status, update the bounding area
148 for event in list1:
149 if event not in list2:
150 x, y, width, height = list1[event].get_dimensions()
151 self._dirty_inflate(x - self.cur_x, y - self.cur_y, width, height, GraphFormat.BORDER_THICKNESS)
152 for event in list2:
153 if event not in list1:
154 x, y, width, height = list2[event].get_dimensions()
155 self._dirty_inflate(x - self.cur_x, y - self.cur_y, width, height, GraphFormat.BORDER_THICKNESS)
156
137 def motion_notify(self, widget, motion_event, data=None): 157 def motion_notify(self, widget, motion_event, data=None):
138 msg = None 158 msg = None
139 159
140 graph = self.renderer.get_graph() 160 graph = self.renderer.get_graph()
141 161
142 graph.render_surface(self.renderer.get_schedule(), motion_event.x, motion_event.y, 0, 0, True) 162 graph.render_surface(self.renderer.get_schedule(), [(motion_event.x, motion_event.y,
163 0, 0)], True)
143 just_selected = graph.get_selected_regions(motion_event.x, motion_event.y, 0, 0) 164 just_selected = graph.get_selected_regions(motion_event.x, motion_event.y, 0, 0)
144 if not just_selected: 165 if not just_selected:
145 msg = '' 166 msg = ''
@@ -156,25 +177,31 @@ class GraphArea(gtk.DrawingArea):
156 177
157 self.emit('update-event-description', the_event, msg) 178 self.emit('update-event-description', the_event, msg)
158 179
159 if self.left_button_start_coor is not None: 180 if self.band_rect is not None:
160 selected = {} 181 selected = {}
182 was_selected = self.renderer.get_schedule().get_selected()
161 if self.ctrl_clicked: 183 if self.ctrl_clicked:
162 selected = dict(self.last_selected) 184 selected = copy.copy(was_selected)
163 185
164 # dragging a rectangle 186 # dragging a rectangle
165 x = min(self.left_button_start_coor[0], motion_event.x) 187 x = self.band_rect[0]
166 y = min(self.left_button_start_coor[1], motion_event.y) 188 y = self.band_rect[1]
167 width = abs(self.left_button_start_coor[0] - motion_event.x) 189 width = motion_event.x - self.band_rect[0]
168 height = abs(self.left_button_start_coor[1] - motion_event.y) 190 height = motion_event.y - self.band_rect[1]
169 191
170 graph.render_surface(self.renderer.get_schedule(), x, y, width, height, True) 192 x_p, y_p, width_p, height_p = self._positivify(x, y, width, height)
171 selected.update(graph.get_selected_regions(x, y, width, height)) 193 graph.render_surface(self.renderer.get_schedule(), [(x_p, y_p, width_p, height_p)], True)
194 selected.update(graph.get_selected_regions(x_p, y_p, width_p, height_p))
172 self.renderer.get_schedule().set_selected(selected) 195 self.renderer.get_schedule().set_selected(selected)
173 196
174 self.select_rect = (x, y, width, height) 197 old_x, old_y, old_width, old_height = self.band_rect
198 self.band_rect = (x, y, width, height)
175 199
176 self._dirty() 200 self._dirty_rect_border(old_x, old_y, old_width, old_height, GraphFormat.BAND_THICKNESS)
201 self._dirty_rect_border(x, y, width, height, GraphFormat.BAND_THICKNESS)
177 202
203 self._update_event_changes(was_selected, selected)
204
178 def button_press(self, widget, button_event, data=None): 205 def button_press(self, widget, button_event, data=None):
179 graph = self.renderer.get_graph() 206 graph = self.renderer.get_graph()
180 207
@@ -183,15 +210,16 @@ class GraphArea(gtk.DrawingArea):
183 if button_event.button == 1: 210 if button_event.button == 1:
184 self.left_button_start_coor = (button_event.x, button_event.y) 211 self.left_button_start_coor = (button_event.x, button_event.y)
185 graph.render_surface(self.renderer.get_schedule(), \ 212 graph.render_surface(self.renderer.get_schedule(), \
186 button_event.x, button_event.y, 0, 0, True) 213 [(button_event.x, button_event.y, 0, 0)], True)
187 214
188 just_selected = graph.get_selected_regions(button_event.x, button_event.y, 0, 0) 215 just_selected = graph.get_selected_regions(button_event.x, button_event.y, 0, 0)
189 216
190 max_layer = self._find_max_layer(just_selected) 217 max_layer = self._find_max_layer(just_selected)
191 218
192 new_now_selected = None 219 new_now_selected = None
220 was_selected = self.renderer.get_schedule().get_selected()
193 if self.ctrl_clicked: 221 if self.ctrl_clicked:
194 new_now_selected = self.renderer.get_schedule().get_selected() 222 new_now_selected = copy.copy(was_selected)
195 else: 223 else:
196 new_now_selected = {} 224 new_now_selected = {}
197 225
@@ -206,45 +234,75 @@ class GraphArea(gtk.DrawingArea):
206 if (len(now_selected) == 1 or self.ctrl_clicked) and event in now_selected: 234 if (len(now_selected) == 1 or self.ctrl_clicked) and event in now_selected:
207 val = not event.is_selected 235 val = not event.is_selected
208 if val: 236 if val:
209 new_now_selected[event] = None 237 new_now_selected[event] = graph.get_sel_region(event)
210 elif event in new_now_selected: 238 elif event in new_now_selected:
211 del new_now_selected[event] 239 del new_now_selected[event]
212 break # only pick one event when just clicking 240 break # only pick one event when just clicking
213 241
214 self.renderer.get_schedule().set_selected(new_now_selected) 242 self.renderer.get_schedule().set_selected(new_now_selected)
215 self.last_selected = dict(new_now_selected) 243 #self.last_selected = new_now_selected
216 244
217 self._dirty() 245 self._update_event_changes(new_now_selected, was_selected)
246
247 if self.band_rect is None:
248 self.band_rect = (button_event.x, button_event.y, 0, 0)
249
218 elif button_event.button == 3: 250 elif button_event.button == 3:
219 self._release_band() 251 self._release_band()
220 self.emit('request-context-menu', button_event, self.renderer.get_schedule().get_selected()) 252 self.emit('request-context-menu', button_event, self.renderer.get_schedule().get_selected())
221 253
222 def button_release(self, widget, button_event, data=None): 254 def button_release(self, widget, button_event, data=None):
223 self.ctrl_clicked = False 255 self.ctrl_clicked = False
224 self.last_selected = dict(self.renderer.get_schedule().get_selected()) 256 #self.last_selected = copy.copy(self.renderer.get_schedule().get_selected())
225 257
226 if button_event.button == 1: 258 if button_event.button == 1:
227 self._release_band() 259 self._release_band()
228 260
229 def _release_band(self): 261 def _release_band(self):
230 old_select_rect = self.select_rect 262 if self.band_rect is not None:
231 self.left_button_start_coor = None 263 x, y, width, height = self.band_rect
232 self.select_rect = None 264 self._dirty_rect_border(x, y, width, height, GraphFormat.BAND_THICKNESS)
233 if old_select_rect is not None: 265 self.band_rect = None
234 self._dirty()
235 266
236 def _dirty(self, x=None, y=None, width=None, height=None): 267 def _dirty(self, x, y, width, height):
237 if x is None: 268 x = max(int(math.floor(x)), 0)
238 x = 0 269 y = max(int(math.floor(y)), 0)
239 if y is None: 270 width = min(int(math.ceil(width)), self.width)
240 y = 0 271 height = min(int(math.ceil(height)), self.height)
241 if width is None: 272
242 width = self.width 273 self.dirtied_regions.append((x, y, width, height))
243 if height is None: 274
244 height = self.height
245 rect = gtk.gdk.Rectangle(x, y, width, height) 275 rect = gtk.gdk.Rectangle(x, y, width, height)
246 self.window.invalidate_rect(rect, True) 276 self.window.invalidate_rect(rect, True)
247 277
278 def _dirty_inflate(self, x, y, width, height, thickness):
279 t = thickness * GraphArea.REFRESH_INFLATION_FACTOR
280 x -= t / 2.0
281 y -= t / 2.0
282 width += t
283 height += t
284 self._dirty(x, y, width, height)
285
286 def _dirty_rect_border(self, x, y, width, height, thickness):
287 # support rectangles with negative width and height (i.e. -width = width, but going leftwards
288 # instead of rightwards)
289 x, y, width, height = self._positivify(x, y, width, height)
290
291 self._dirty_inflate(x, y, width, 0, thickness)
292 self._dirty_inflate(x, y, 0, height, thickness)
293 self._dirty_inflate(x, y + height, width, 0, thickness)
294 self._dirty_inflate(x + width, y, 0, height, thickness)
295
296 def _positivify(self, x, y, width, height):
297 if width < 0:
298 x += width
299 width = -width
300 if height < 0:
301 y += height
302 height = -height
303
304 return x, y, width, height
305
248class GraphWindow(gtk.ScrolledWindow): 306class GraphWindow(gtk.ScrolledWindow):
249 def __init__(self, renderer): 307 def __init__(self, renderer):
250 super(GraphWindow, self).__init__(None, None) 308 super(GraphWindow, self).__init__(None, None)
@@ -310,20 +368,13 @@ class MainWindow(gtk.Window):
310 WINDOW_WIDTH_REQ = 500 368 WINDOW_WIDTH_REQ = 500
311 WINDOW_HEIGHT_REQ = 300 369 WINDOW_HEIGHT_REQ = 300
312 370
313 def __init__(self, request_renderer_change): 371 def __init__(self):
314 super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL) 372 super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL)
315 373
316 self.connect('delete_event', self.delete_event) 374 self.connect('delete_event', self.delete_event)
317 self.connect('destroy', self.die) 375 self.connect('destroy', self.die)
318
319 self.connect('request-renderer-change', request_renderer_change)
320 376
321 self.file_menu = gtk.Menu() 377 self.file_menu = gtk.Menu()
322 self.open_item = gtk.MenuItem('_Open', True)
323 self.open_item.connect('activate', self.open_item_activate)
324 self.file_menu.append(self.open_item)
325 self.open_item.show()
326
327 self.quit_item = gtk.MenuItem('_Quit', True) 378 self.quit_item = gtk.MenuItem('_Quit', True)
328 self.quit_item.connect('activate', self.quit_item_activate) 379 self.quit_item.connect('activate', self.quit_item_activate)
329 self.quit_item.show() 380 self.quit_item.show()
@@ -397,21 +448,6 @@ class MainWindow(gtk.Window):
397 menu = GraphContextMenu(selected) 448 menu = GraphContextMenu(selected)
398 menu.popup(None, None, None, button, time) 449 menu.popup(None, None, None, button, time)
399 450
400 def open_item_activate(self, widget):
401 dialog = gtk.FileChooserDialog('Open File', self,
402 gtk.FILE_CHOOSER_ACTION_OPEN,
403 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
404 gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT))
405 dialog.set_select_multiple(True)
406
407 if dialog.run() == gtk.RESPONSE_ACCEPT:
408 filenames = dialog.get_filenames()
409 dialog.destroy()
410 self.emit('request-renderer-change', filenames, None)
411 else:
412 dialog.destroy()
413
414
415 def quit_item_activate(self, widget): 451 def quit_item_activate(self, widget):
416 self.destroy() 452 self.destroy()
417 453