summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGary Bressler <garybressler@nc.rr.com>2010-03-09 13:33:54 -0500
committerGary Bressler <garybressler@nc.rr.com>2010-03-09 13:33:54 -0500
commit883f9dfe38ab081025220aafbf47f722b540d003 (patch)
tree6ab638add46bfd8e622301c2d704b3d6dfdba9b5
parentb97f0447b302746ab054aba0fd7417224ba73d86 (diff)
Preliminary, fairly workable version of unit_trace, including the visualizer.
-rw-r--r--README106
-rw-r--r--convert.py2
-rw-r--r--gedf_test.py1
-rw-r--r--gedf_test.py~1
-rwxr-xr-xreader/sample_script.py8
-rw-r--r--reader/trace_reader.py32
-rwxr-xr-xvisualizer.py41
-rw-r--r--viz/__init__.py8
-rw-r--r--viz/draw.py138
-rw-r--r--viz/format.py2
-rw-r--r--viz/schedule.py210
-rw-r--r--viz/viewer.py253
12 files changed, 600 insertions, 202 deletions
diff --git a/README b/README
index b2da190..4c1b772 100644
--- a/README
+++ b/README
@@ -1 +1,105 @@
1See the LITMUS Wiki page for an explanation of this tool. 1See the LITMUS Wiki page for a general explanation of this tool.
2
3unit_trace consists of two modules and a core. The ``core'' is basically
4a bunch of code, implemented as Python iterators, which converts the
5raw trace data into a sequence of record objects, implemented in
6Python. The modules are:
7
81) A simple module that outputs the contents of each record to
9stdout. This module, along with most of the core, can be found in the
10reader/ directory. There is a sample script -- look at
11sample_script.py in the reader/ directory (it's pretty
12self-explanatory). Note that Mac is the one who coded most of the
13this, though I can probably try to answer any questions about it since
14I've had to go in there from time to time.
15
162) The visualizer. Now, the GUI as it stands is very basic -- it's
17basically just a shell for the core visualizer component. How to open
18a file is obvious -- but note that you can open several files at a
19time (since more often than not a trace consists of more than one
20file, typically one for each CPU).
21
22Most of the code for this is in the viz/ directory, but to run it, the
23file you want to execute is visualizer.py (in the main directory).
24
25A few notes on how to use the GUI:
26
27-- How to scroll is pretty obvious, though I still need to implement
28 keypresses (very trivial, but when making a GUI component from
29 scratch it always seems like there are a million little things that
30 you need to do :)
31
32-- You can view either by task or by CPU; click the tabs at the top.
33
34-- Mousing over the items (not the axes, though, since those are
35 pretty self-explanatory) gives you information about the item that
36 you moused over, displayed at the bottom.
37
38-- You can select items. You can click them individually, one at a
39 time, or you can drag or ctrl-click to select multiple.
40
41-- What you have selected is independent of what mode (task or CPU)
42 you are operating in. So if you are curious, say, when a certain
43 job is running compared to other jobs on the same CPU, you can
44 click a job in task mode and then switch to CPU mode, and it will
45 remain selected.
46
47-- Right-click to get a menu of all the items you have selected (in
48 the future this menu will be clickable, so that you can get the
49 information about an item in its own window).
50
51-- It is a bit laggy when lots of stuff is on the screen at once. This
52 should be fairly easy to optimize, if I have correctly identified
53 the problem, but it's not a huge issue (it's not _that_ slow).
54
55But wait, there's more:
56
57-- As of now unit-trace has no way to determine the algorithm that was
58 used on the trace you're loading. This is important since certain
59 sections of code work only with G-EDF in particular. The point of
60 having this special code is either to filter out bogus data or to
61 generate extra information about the schedule (e.g. priority
62 inversions). Of course, you can leave these extra steps out and
63 it will still work, but you might get extra ``bogus'' information
64 generated by the tracer or you might not get all the information
65 you want.
66
67-- To add or remove these extra steps, take a look at visualizer.py
68 and sample_script.py. You will see some code like this:
69
70 stream = reader.trace_reader.trace_reader(file_list)
71 #stream = reader.sanitizer.sanitizer(stream)
72 #stream = reader.gedf_test.gedf_test(stream)
73
74 Uncommenting those lines will run the extra steps in the pipeline.
75 The sanitizer filters out some bogus data (stuff like ``pid 0''),
76 but so far it's only been coded for a select number of traces.
77 gedf_test generates extra information about G-EDF schedules
78 (the traces that are named st-g?-?.bin). If you try to run
79 gedf_test on anything else, it will most likely fail.
80
81-- What traces are you going to use? Well, you will probably want to
82 use your own, but there are some samples you can try in the traces/
83 directory (a couple of them do give error messages, however).
84
85How to install:
86
87You should type
88
89git clone -b wip-gary ssh://cvs.cs.unc.edu/cvs/proj/litmus/repo/unit-trace.git
90
91to check out the repository. Note that you shouldn't check out the
92master branch, as it's pretty outdated. It's all Python so far, so no
93compiling or anything like that is necessary.
94
95Requirements:
96
97You're going to need Python 2.5 to run this. You'll also need to
98install the pycairo and pygtk libraries. If anyone has questions about
99how to do this or what these are, ask me.
100
101Miscellanies:
102
103Of course, let me know if you find any bugs (I'm sure there are
104plenty, though, since this is fairly alpha software), if you're
105unable to run it, or if you have any questions. \ No newline at end of file
diff --git a/convert.py b/convert.py
index 1db4ad0..7035c47 100644
--- a/convert.py
+++ b/convert.py
@@ -79,7 +79,7 @@ def convert_trace_to_schedule(stream):
79 } 79 }
80 80
81 actions[record.type_name]() 81 actions[record.type_name]()
82 82
83 return sched 83 return sched
84 84
85def _pid_to_task_name(pid): 85def _pid_to_task_name(pid):
diff --git a/gedf_test.py b/gedf_test.py
index 2381dad..8457901 100644
--- a/gedf_test.py
+++ b/gedf_test.py
@@ -7,7 +7,6 @@
7############################################################################### 7###############################################################################
8# Imports 8# Imports
9############################################################################### 9###############################################################################
10#test
11 10
12import copy 11import copy
13 12
diff --git a/gedf_test.py~ b/gedf_test.py~
index 8457901..2381dad 100644
--- a/gedf_test.py~
+++ b/gedf_test.py~
@@ -7,6 +7,7 @@
7############################################################################### 7###############################################################################
8# Imports 8# Imports
9############################################################################### 9###############################################################################
10#test
10 11
11import copy 12import copy
12 13
diff --git a/reader/sample_script.py b/reader/sample_script.py
index f7e9297..676cfac 100755
--- a/reader/sample_script.py
+++ b/reader/sample_script.py
@@ -14,10 +14,10 @@ import stdout_printer
14 14
15# Specify your trace files 15# Specify your trace files
16g6 = [ 16g6 = [
17'../sample_traces/st-g6-0.bin', 17'../traces/st-g6-0.bin',
18'../sample_traces/st-g6-1.bin', 18'../traces/st-g6-1.bin',
19'../sample_traces/st-g6-2.bin', 19'../traces/st-g6-2.bin',
20'../sample_traces/st-g6-3.bin', 20'../traces/st-g6-3.bin',
21] 21]
22 22
23# Here is an example of a custom filter function. 23# Here is an example of a custom filter function.
diff --git a/reader/trace_reader.py b/reader/trace_reader.py
index a4ff964..831a06e 100644
--- a/reader/trace_reader.py
+++ b/reader/trace_reader.py
@@ -27,7 +27,12 @@
27 27
28import struct 28import struct
29 29
30 30###############################################################################
31# Class definitions
32###############################################################################
33class InvalidRecordError(Exception):
34 pass
35
31############################################################################### 36###############################################################################
32# Public functions 37# Public functions
33############################################################################### 38###############################################################################
@@ -49,15 +54,19 @@ def trace_reader(files):
49 for file in files: 54 for file in files:
50 file_iter = _get_file_iter(file) 55 file_iter = _get_file_iter(file)
51 file_iters.append(file_iter) 56 file_iters.append(file_iter)
52 file_iter_buff.append([file_iter.next()]) 57 file_iter_buff.append([])
53 58
54 # We keep 100 records in each buffer and then keep the buffer sorted 59 # We keep 100 records in each buffer and then keep the buffer sorted
55 # This is because records may have been recorded slightly out of order 60 # This is because records may have been recorded slightly out of order
56 # This cannot guarantee records are produced in order, but it makes it 61 # This cannot guarantee records are produced in order, but it makes it
57 # overwhelmingly probably. 62 # overwhelmingly probably.
58 for x in range(0,len(file_iter_buff)): 63 for x in range(0,len(file_iter_buff)):
59 for y in range(0,100): 64 for y in range(0,100):
60 file_iter_buff[x].append(file_iters[x].next()) 65 try:
66 file_iter_buff[x].append(file_iters[x].next())
67 except StopIteration:
68 break
69
61 for x in range(0,len(file_iter_buff)): 70 for x in range(0,len(file_iter_buff)):
62 file_iter_buff[x] = sorted(file_iter_buff[x],key=lambda rec: rec.when) 71 file_iter_buff[x] = sorted(file_iter_buff[x],key=lambda rec: rec.when)
63 72
@@ -96,7 +105,7 @@ def trace_reader(files):
96 105
97 # Check for monotonically increasing time 106 # Check for monotonically increasing time
98 if last_time is not None and earliest.when < last_time: 107 if last_time is not None and earliest.when < last_time:
99 exit("FATAL ERROR: trace_reader.py: out-of-order record produced") 108 raise InvalidRecordError("out-of-order record produced")
100 else: 109 else:
101 last_time = earliest.when 110 last_time = earliest.when
102 111
@@ -112,10 +121,12 @@ def _get_file_iter(file):
112 f = open(file,'rb') 121 f = open(file,'rb')
113 while True: 122 while True:
114 data = f.read(RECORD_HEAD_SIZE) 123 data = f.read(RECORD_HEAD_SIZE)
124 if data == '':
125 break
115 try: 126 try:
116 type_num = struct.unpack_from('b',data)[0] 127 type_num = struct.unpack_from('b',data)[0]
117 except struct.error: 128 except struct.error:
118 break #We read to the end of the file 129 raise InvalidRecordError("Invalid record detected, stopping.")
119 type = _get_type(type_num) 130 type = _get_type(type_num)
120 try: 131 try:
121 values = struct.unpack_from(StHeader.format + 132 values = struct.unpack_from(StHeader.format +
@@ -123,8 +134,7 @@ def _get_file_iter(file):
123 record_dict = dict(zip(type.keys,values)) 134 record_dict = dict(zip(type.keys,values))
124 except struct.error: 135 except struct.error:
125 f.close() 136 f.close()
126 print "Invalid record detected, stopping." 137 raise InvalidRecordError("Invalid record detected, stopping.")
127 exit()
128 138
129 # Convert the record_dict into an object 139 # Convert the record_dict into an object
130 record = _dict2obj(record_dict) 140 record = _dict2obj(record_dict)
@@ -139,7 +149,7 @@ def _get_file_iter(file):
139 # If there is no timestamp, set the time to 0 149 # If there is no timestamp, set the time to 0
140 if 'when' not in record.__dict__.keys(): 150 if 'when' not in record.__dict__.keys():
141 record.when = 0 151 record.when = 0
142 152
143 yield record 153 yield record
144 154
145# Convert a dict into an object 155# Convert a dict into an object
@@ -235,6 +245,8 @@ def _get_type(type_num):
235 types = [None,StNameData,StParamData,StReleaseData,StAssignedData, 245 types = [None,StNameData,StParamData,StReleaseData,StAssignedData,
236 StSwitchToData,StSwitchAwayData,StCompletionData,StBlockData, 246 StSwitchToData,StSwitchAwayData,StCompletionData,StBlockData,
237 StResumeData,StSysReleaseData] 247 StResumeData,StSysReleaseData]
248 if type_num >= len(types) or type_num < 0:
249 raise InvalidRecordError("Invalid record detected, stopping.")
238 return types[type_num] 250 return types[type_num]
239 251
240# Return the type name, given the type_num (this is simply a convenience to 252# Return the type name, given the type_num (this is simply a convenience to
@@ -242,4 +254,6 @@ def _get_type(type_num):
242def _get_type_name(type_num): 254def _get_type_name(type_num):
243 type_names = [None,"name","params","release","assign","switch_to", 255 type_names = [None,"name","params","release","assign","switch_to",
244 "switch_away","completion","block","resume","sys_release"] 256 "switch_away","completion","block","resume","sys_release"]
257 if type_num >= len(type_names) or type_num < 0:
258 raise InvalidRecordError("Invalid record detected, stopping.")
245 return type_names[type_num] 259 return type_names[type_num]
diff --git a/visualizer.py b/visualizer.py
index 80c2af9..43d74a8 100755
--- a/visualizer.py
+++ b/visualizer.py
@@ -8,24 +8,31 @@ import viz
8 8
9import gtk 9import gtk
10 10
11path = 'sample_traces/' 11TIME_PER_MAJ = 10000000
12MAX_NUM_SLOTS = 10000
12 13
13trace_list = [ 14def request_renderer_change(widget, file_list, params):
14path + 'st-g6-0.bin', 15 try:
15path + 'st-g6-1.bin', 16 stream = reader.trace_reader.trace_reader(file_list)
16path + 'st-g6-2.bin', 17 #stream = reader.sanitizer.sanitizer(stream)
17path + 'st-g6-3.bin' 18 #stream = reader.gedf_test.gedf_test(stream)
18] 19 sched = convert.convert_trace_to_schedule(stream)
20 except reader.trace_reader.InvalidRecordError, e:
21 dialog = gtk.MessageDialog(widget, gtk.DIALOG_DESTROY_WITH_PARENT,
22 gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, str(e))
23 dialog.run()
24 dialog.destroy()
25 return
19 26
20if __name__ == '__main__': 27 sched.scan(TIME_PER_MAJ, MAX_NUM_SLOTS)
21 stream = reader.trace_reader.trace_reader(trace_list) 28
22 stream = reader.sanitizer.sanitizer(stream) 29 task_renderer = viz.renderer.Renderer(sched)
23 stream = reader.gedf_test.gedf_test(stream) 30 task_renderer.prepare_task_graph(attrs=viz.format.GraphFormat(time_per_maj=TIME_PER_MAJ))
24 sched = convert.convert_trace_to_schedule(stream) 31 cpu_renderer = viz.renderer.Renderer(sched)
25 sched.scan(10000000) 32 cpu_renderer.prepare_cpu_graph(attrs=viz.format.GraphFormat(time_per_maj=TIME_PER_MAJ))
26 renderer = viz.renderer.Renderer(sched) 33 widget.set_renderers({'Tasks' : task_renderer, 'CPUs' : cpu_renderer})
27 renderer.prepare_task_graph(attrs=viz.format.GraphFormat(time_per_maj=10000000)) 34
28 35if __name__ == '__main__':
29 viz.viewer.MainWindow(renderer) 36 window = viz.viewer.MainWindow(request_renderer_change)
30 gtk.main() 37 gtk.main()
31 38
diff --git a/viz/__init__.py b/viz/__init__.py
index ef409f6..0f9bd1c 100644
--- a/viz/__init__.py
+++ b/viz/__init__.py
@@ -5,6 +5,10 @@ import gobject
5import gtk 5import gtk
6 6
7gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 7gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
8 None, (gtk.Adjustment, gtk.Adjustment)) 8 None, (gtk.Adjustment, gtk.Adjustment))
9gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, 9gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
10 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)) 10 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
11gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST,
12 None, (gtk.gdk.Event, gobject.TYPE_PYOBJECT))
13gobject.signal_new('request-renderer-change', viewer.MainWindow, gobject.SIGNAL_RUN_FIRST,
14 None, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
diff --git a/viz/draw.py b/viz/draw.py
index c3ab756..db246ec 100644
--- a/viz/draw.py
+++ b/viz/draw.py
@@ -84,31 +84,40 @@ class ImageSurface(Surface):
84 84
85class Pattern(object): 85class Pattern(object):
86 DEF_STRIPE_SIZE = 10 86 DEF_STRIPE_SIZE = 10
87 MAX_FADE_WIDTH = 250
87 88
88 def __init__(self, color_list, stripe_size=DEF_STRIPE_SIZE): 89 def __init__(self, color_list, stripe_size=DEF_STRIPE_SIZE):
89 self.color_list = color_list 90 self.color_list = color_list
90 self.stripe_size = stripe_size 91 self.stripe_size = stripe_size
91 92
92 def render_on_canvas(self, canvas, x, y, width, height, fade=False): 93 def render_on_canvas(self, canvas, x, y, width, height, fade=False):
94 fade_span = min(width, Pattern.MAX_FADE_WIDTH)
95
93 if len(self.color_list) == 1: 96 if len(self.color_list) == 1:
94 if fade: 97 if fade:
95 canvas.fill_rect_fade(x, y, width, height, (1.0, 1.0, 1.0), \ 98 canvas.fill_rect_fade(x, y, fade_span, height, (1.0, 1.0, 1.0), \
96 self.color_list[0]) 99 self.color_list[0])
97 else: 100 else:
98 canvas.fill_rect(x, y, width, height, self.color_list[0]) 101 canvas.fill_rect(x, y, width, height, self.color_list[0])
99 102
103 if width > Pattern.MAX_FADE_WIDTH:
104 canvas.fill_rect(x + Pattern.MAX_FADE_WIDTH, y, width - Pattern.MAX_FADE_WIDTH,
105 height, self.color_list[0])
100 else: 106 else:
101 n = 0 107 n = 0
102 bottom = y + height 108 bottom = y + height
103 while y < bottom: 109 while y < bottom:
104 linear = cairo.LinearGradient(x, y, x + width, math.min(y + self.stripe_size, bottom))
105 i = n % len(self.color_list) 110 i = n % len(self.color_list)
106 if fade: 111 if fade:
107 canvas.fill_rect_fade(x, y, width, min(self.stripe_size, bottom - y), (1.0, 1.0, 1.0), \ 112 canvas.fill_rect_fade(x, y, fade_span, \
108 self.color_list[i]) 113 min(self.stripe_size, bottom - y), (1.0, 1.0, 1.0), self.color_list[i])
109 else: 114 else:
110 canvas.fill_rect(x, y, width, min(self.stripe_size, bottom - y), self.color_list[i]) 115 canvas.fill_rect(x, y, width, min(self.stripe_size, bottom - y), self.color_list[i])
111 116
117 if width > Pattern.MAX_FADE_WIDTH:
118 canvas.fill_rect(x + Pattern.MAX_FADE_WIDTH, y, width - Pattern.MAX_FADE_WIDTH,
119 min(self.stripe_size, bottom - y), self.color_list[i])
120
112 y += self.stripe_size 121 y += self.stripe_size
113 n += 1 122 n += 1
114 123
@@ -129,6 +138,8 @@ class Canvas(object):
129 138
130 LAYERS = (BOTTOM_LAYER, MIDDLE_LAYER, TOP_LAYER) 139 LAYERS = (BOTTOM_LAYER, MIDDLE_LAYER, TOP_LAYER)
131 140
141 NULL_PATTERN = -1
142
132 SQRT3 = math.sqrt(3.0) 143 SQRT3 = math.sqrt(3.0)
133 144
134 def __init__(self, width, height, item_clist, bar_plist, surface): 145 def __init__(self, width, height, item_clist, bar_plist, surface):
@@ -470,28 +481,32 @@ class Canvas(object):
470 def add_sel_resume_triangle(self, x, y, height, event): 481 def add_sel_resume_triangle(self, x, y, height, event):
471 self.add_sel_region(SelectableRegion(x - height / 2.0, y, height / 2.0, height, event)) 482 self.add_sel_region(SelectableRegion(x - height / 2.0, y, height / 2.0, height, event))
472 483
473 def clear_selectable_regions(self): 484 def clear_selectable_regions(self, real_x, real_y, width, height):
474 self.selectable_regions = {} 485 x = real_x + self.surface.virt_x
486 y = real_y + self.surface.virt_y
487 for event in self.selectable_regions.keys():
488 if self.selectable_regions[event].intersects(x, y, width, height):
489 del self.selectable_regions[event]
475 490
476 def add_sel_region(self, region): 491 def add_sel_region(self, region):
477 self.selectable_regions[region.get_event()] = region 492 self.selectable_regions[region.get_event()] = region
478 493
479 def get_selected_regions(self, real_x, real_y): 494 def get_selected_regions(self, real_x, real_y, width, height):
480 x = real_x + self.surface.virt_x 495 x = real_x + self.surface.virt_x
481 y = real_y + self.surface.virt_y 496 y = real_y + self.surface.virt_y
482 497
483 selected = {} 498 selected = {}
484 for event in self.selectable_regions: 499 for event in self.selectable_regions:
485 region = self.selectable_regions[event] 500 region = self.selectable_regions[event]
486 if region.contains(x, y): 501 if region.intersects(x, y, width, height):
487 selected[event] = region 502 selected[event] = region
488 503
489 return selected 504 return selected
490 505
491 def whiteout(self): 506 def whiteout(self, real_x, real_y, width, height):
492 """Overwrites the surface completely white, but technically doesn't delete anything""" 507 """Overwrites the surface completely white, but technically doesn't delete anything"""
493 self.fill_rect(self.surface.virt_x, self.surface.virt_y, self.surface.width, 508 self.fill_rect(self.surface.virt_x + real_x, self.surface.virt_y + real_y, width,
494 self.surface.height, (1.0, 1.0, 1.0)) 509 height, (1.0, 1.0, 1.0))
495 510
496 def get_item_color(self, n): 511 def get_item_color(self, n):
497 """Gets the nth color in the item color list, which are the colors used to draw the items 512 """Gets the nth color in the item color list, which are the colors used to draw the items
@@ -505,7 +520,9 @@ class Canvas(object):
505 fill in the bars. Note that there are conceptually infinitely 520 fill in the bars. Note that there are conceptually infinitely
506 many patterns because the patterns repeat -- that is, we just mod out by the size of the pattern 521 many patterns because the patterns repeat -- that is, we just mod out by the size of the pattern
507 list when indexing.""" 522 list when indexing."""
508 return self.bar_plist[n % len(self.bar_plist)] 523 if n < 0:
524 return self.bar_plist[-1]
525 return self.bar_plist[n % (len(self.bar_plist) - 1)]
509 526
510class CairoCanvas(Canvas): 527class CairoCanvas(Canvas):
511 """This is a basic class that stores and draws on a Cairo surface, 528 """This is a basic class that stores and draws on a Cairo surface,
@@ -679,8 +696,8 @@ class SelectableRegion(object):
679 def get_event(self): 696 def get_event(self):
680 return self.event 697 return self.event
681 698
682 def contains(self, x, y): 699 def intersects(self, x, y, width, height):
683 return self.x <= x <= self.x + self.width and self.y <= y <= self.y + self.height 700 return x <= self.x + self.width and x + width >= self.x and y <= self.y + self.height and y + height >= self.y
684 701
685class Graph(object): 702class Graph(object):
686 DEF_BAR_PLIST = [Pattern([(0.0, 0.9, 0.9)]), Pattern([(0.9, 0.3, 0.0)]), Pattern([(0.9, 0.7, 0.0)]), 703 DEF_BAR_PLIST = [Pattern([(0.0, 0.9, 0.9)]), Pattern([(0.9, 0.3, 0.0)]), Pattern([(0.9, 0.7, 0.0)]),
@@ -691,9 +708,15 @@ class Graph(object):
691 708
692 def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, attrs=GraphFormat(), 709 def __init__(self, CanvasType, surface, start_time, end_time, y_item_list, attrs=GraphFormat(),
693 item_clist=DEF_ITEM_CLIST, bar_plist=DEF_BAR_PLIST): 710 item_clist=DEF_ITEM_CLIST, bar_plist=DEF_BAR_PLIST):
711 # deal with possibly blank schedules
712 if start_time is None:
713 start_time = 0
714 if end_time is None:
715 end_time = 0
716
694 if start_time > end_time: 717 if start_time > end_time:
695 raise ValueError("Litmus is not a time machine") 718 raise ValueError("Litmus is not a time machine")
696 719
697 self.attrs = attrs 720 self.attrs = attrs
698 self.start_time = start_time 721 self.start_time = start_time
699 self.end_time = end_time 722 self.end_time = end_time
@@ -730,8 +753,8 @@ class Graph(object):
730 753
731 self.canvas = CanvasType(width, height, item_clist, bar_plist, surface) 754 self.canvas = CanvasType(width, height, item_clist, bar_plist, surface)
732 755
733 def get_selected_regions(self, real_x, real_y): 756 def get_selected_regions(self, real_x, real_y, width, height):
734 return self.canvas.get_selected_regions(real_x, real_y) 757 return self.canvas.get_selected_regions(real_x, real_y, width, height)
735 758
736 def get_width(self): 759 def get_width(self):
737 return self.width 760 return self.width
@@ -788,12 +811,12 @@ class Graph(object):
788 def ycoor_to_item_no(self, y): 811 def ycoor_to_item_no(self, y):
789 return int((y - self.origin[1] + self._get_y_axis_height()) // self.attrs.y_item_size) 812 return int((y - self.origin[1] + self._get_y_axis_height()) // self.attrs.y_item_size)
790 813
791 def get_offset_params(self): 814 def get_offset_params(self, real_x, real_y, width, height):
792 start_time = self.xcoor_to_time(self.canvas.surface.virt_x) 815 start_time = self.xcoor_to_time(self.canvas.surface.virt_x + real_x)
793 end_time = self.xcoor_to_time(self.canvas.surface.virt_x + self.canvas.surface.width) 816 end_time = self.xcoor_to_time(self.canvas.surface.virt_x + real_x + width)
794 817
795 start_item = self.ycoor_to_item_no(self.canvas.surface.virt_y) 818 start_item = self.ycoor_to_item_no(self.canvas.surface.virt_y + real_y)
796 end_item = 1 + self.ycoor_to_item_no(self.canvas.surface.virt_y + self.canvas.surface.height) 819 end_item = 1 + self.ycoor_to_item_no(self.canvas.surface.virt_y + real_y + height)
797 820
798 return (start_time, end_time, start_item, end_item) 821 return (start_time, end_time, start_item, end_item)
799 822
@@ -802,7 +825,7 @@ class Graph(object):
802 self.draw_x_axis_with_labels_at_time(start_time, end_time) 825 self.draw_x_axis_with_labels_at_time(start_time, end_time)
803 self.draw_y_axis_with_labels() 826 self.draw_y_axis_with_labels()
804 827
805 def render_surface(self, sched, list_type): 828 def render_surface(self, sched, real_x, real_y, width, height, selectable=False):
806 raise NotImplementedError 829 raise NotImplementedError
807 830
808 def render_all(self, schedule): 831 def render_all(self, schedule):
@@ -919,21 +942,24 @@ class Graph(object):
919 raise NotImplementedError 942 raise NotImplementedError
920 943
921class TaskGraph(Graph): 944class TaskGraph(Graph):
922 def render_surface(self, sched): 945 def render_surface(self, sched, real_x, real_y, width, height, selectable=False):
923 self.canvas.whiteout() 946 if not selectable:
924 self.canvas.clear_selectable_regions() 947 self.canvas.whiteout(real_x, real_y, width, height)
948 else:
949 self.canvas.clear_selectable_regions(real_x, real_y, width, height)
925 950
926 start_time, end_time, start_item, end_item = self.get_offset_params() 951 start_time, end_time, start_item, end_item = self.get_offset_params(real_x, real_y, width, height)
927 952
928 self.draw_skeleton(start_time, end_time, start_item, end_item) 953 if not selectable:
954 self.draw_skeleton(start_time, end_time, start_item, end_item)
929 955
930 for layer in Canvas.LAYERS: 956 for layer in Canvas.LAYERS:
931 prev_events = {} 957 prev_events = {}
932 for event in sched.get_time_slot_array().iter_over_period( 958 for event in sched.get_time_slot_array().iter_over_period(
933 start_time, end_time, start_item, end_item, 959 start_time, end_time, start_item, end_item,
934 schedule.TimeSlotArray.TASK_LIST, schedule.EVENT_LIST): 960 schedule.TimeSlotArray.TASK_LIST, schedule.EVENT_LIST):
935 event.render(self, layer, prev_events) 961 event.render(self, layer, prev_events, selectable)
936 962
937 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False): 963 def draw_suspend_triangle_at_time(self, time, task_no, cpu_no, selected=False):
938 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR 964 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR
939 x = self._get_time_xpos(time) 965 x = self._get_time_xpos(time)
@@ -1046,7 +1072,7 @@ class TaskGraph(Graph):
1046 width = self._get_bar_width(start_time, end_time) 1072 width = self._get_bar_width(start_time, end_time)
1047 height = self._get_mini_bar_height() 1073 height = self._get_mini_bar_height()
1048 1074
1049 self.canvas.draw_mini_bar(x, y, width, height, cpu_no, selected) 1075 self.canvas.draw_mini_bar(x, y, width, height, Canvas.NULL_PATTERN, selected)
1050 1076
1051 if job_no is not None: 1077 if job_no is not None:
1052 x += GraphFormat.MINI_BAR_LABEL_OFS 1078 x += GraphFormat.MINI_BAR_LABEL_OFS
@@ -1063,35 +1089,42 @@ class TaskGraph(Graph):
1063 self.canvas.add_sel_mini_bar(x, y, width, height, event) 1089 self.canvas.add_sel_mini_bar(x, y, width, height, event)
1064 1090
1065class CpuGraph(Graph): 1091class CpuGraph(Graph):
1066 def render_surface(self, sched): 1092 def render_surface(self, sched, real_x, real_y, width, height, selectable=False):
1067 self.canvas.whiteout() 1093 if not selectable:
1068 self.canvas.clear_selectable_regions() 1094 self.canvas.whiteout(real_x, real_y, width, height)
1095 else:
1096 self.canvas.clear_selectable_regions(real_x, real_y, width, height)
1069 1097
1070 start_time, end_time, start_item, end_item = self.get_offset_params() 1098 start_time, end_time, start_item, end_item = self.get_offset_params(real_x, real_y, width, height)
1071 1099
1072 self.draw_skeleton(start_time, end_time, start_item, end_item) 1100 if not selectable:
1101 self.draw_skeleton(start_time, end_time, start_item, end_item)
1073 1102
1074 event_list = dict(schedule.EVENT_LIST) 1103 event_list = dict(schedule.EVENT_LIST)
1075 1104
1076 del event_list[schedule.ReleaseEvent] 1105 bottom_events = [schedule.ReleaseEvent, schedule.DeadlineEvent, schedule.InversionStartEvent,
1077 del event_list[schedule.DeadlineEvent] 1106 schedule.InversionEndEvent, schedule.InversionDummy]
1107
1108 for event in bottom_events:
1109 del event_list[event]
1078 1110
1079 for layer in Canvas.LAYERS: 1111 for layer in Canvas.LAYERS:
1080 prev_events = {} 1112 prev_events = {}
1081 for event in sched.get_time_slot_array().iter_over_period( 1113 for event in sched.get_time_slot_array().iter_over_period(
1082 start_time, end_time, start_item, end_item, 1114 start_time, end_time, start_item, end_item,
1083 schedule.TimeSlotArray.CPU_LIST, schedule.EVENT_LIST): 1115 schedule.TimeSlotArray.CPU_LIST, schedule.EVENT_LIST):
1084 event.render(self, layer, prev_events) 1116 event.render(self, layer, prev_events, selectable)
1085 1117
1086 if end_item >= len(self.y_item_list): 1118 if end_item >= len(self.y_item_list):
1087 # we are far down enough that we should render the releases and deadlines 1119 # we are far down enough that we should render the releases and deadlines and inversions,
1120 # which appear near the x-axis
1088 for layer in Canvas.LAYERS: 1121 for layer in Canvas.LAYERS:
1089 prev_events = {} 1122 prev_events = {}
1090 for event in sched.get_time_slot_array().iter_over_period( 1123 for event in sched.get_time_slot_array().iter_over_period(
1091 start_time, end_time, start_item, end_item, 1124 start_time, end_time, 0, sched.get_num_cpus(),
1092 schedule.TimeSlotArray.CPU_LIST, 1125 schedule.TimeSlotArray.CPU_LIST,
1093 (schedule.ReleaseEvent, schedule.DeadlineEvent)): 1126 bottom_events):
1094 event.render(self, layer, prev_events) 1127 event.render(self, layer, prev_events, selectable)
1095 1128
1096 def render(self, schedule, start_time=None, end_time=None): 1129 def render(self, schedule, start_time=None, end_time=None):
1097 if end_time < start_time: 1130 if end_time < start_time:
@@ -1145,9 +1178,10 @@ class CpuGraph(Graph):
1145 self.canvas.draw_completion_marker(x, y, height, selected) 1178 self.canvas.draw_completion_marker(x, y, height, selected)
1146 1179
1147 def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event): 1180 def add_sel_completion_marker_at_time(self, time, task_no, cpu_no, event):
1148 height = self._get_bar_height() * GraphFormat.BLOCK_TRIANGLE_FACTOR 1181 height = self._get_bar_height() * GraphFormat.COMPLETION_MARKER_FACTOR
1182
1149 x = self._get_time_xpos(time) 1183 x = self._get_time_xpos(time)
1150 y = self._get_item_ypos(cpu_no) + self._get_bar_height() / 2.0 - height / 2.0 1184 y = self._get_item_ypos(cpu_no) + self._get_bar_height() - height
1151 1185
1152 self.canvas.add_sel_completion_marker(x, y, height, event) 1186 self.canvas.add_sel_completion_marker(x, y, height, event)
1153 1187
@@ -1169,7 +1203,7 @@ class CpuGraph(Graph):
1169 AlignMode.CENTER, AlignMode.BOTTOM) 1203 AlignMode.CENTER, AlignMode.BOTTOM)
1170 1204
1171 def add_sel_release_arrow_at_time(self, time, task_no, event): 1205 def add_sel_release_arrow_at_time(self, time, task_no, event):
1172 height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR 1206 height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR
1173 1207
1174 x = self._get_time_xpos(time) 1208 x = self._get_time_xpos(time)
1175 y = self.origin[1] - height 1209 y = self.origin[1] - height
@@ -1194,7 +1228,7 @@ class CpuGraph(Graph):
1194 AlignMode.CENTER, AlignMode.BOTTOM) 1228 AlignMode.CENTER, AlignMode.BOTTOM)
1195 1229
1196 def add_sel_deadline_arrow_at_time(self, time, task_no, event): 1230 def add_sel_deadline_arrow_at_time(self, time, task_no, event):
1197 height = self._get_bar_height() * GraphFormat.BIG_ARROW_FACTOR 1231 height = self._get_bar_height() * GraphFormat.SMALL_ARROW_FACTOR
1198 1232
1199 x = self._get_time_xpos(time) 1233 x = self._get_time_xpos(time)
1200 y = self.origin[1] - height 1234 y = self.origin[1] - height
@@ -1226,18 +1260,18 @@ class CpuGraph(Graph):
1226 width = self._get_bar_width(start_time, end_time) 1260 width = self._get_bar_width(start_time, end_time)
1227 height = self._get_bar_height() 1261 height = self._get_bar_height()
1228 1262
1229 self.canvas.add_sel_region(SelectableRegion(x, y, width, height, event)) 1263 self.canvas.add_sel_bar(x, y, width, height, event)
1230 1264
1231 def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, selected=False): 1265 def draw_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, job_no=None, selected=False):
1232 if start_time > end_time: 1266 if start_time > end_time:
1233 raise ValueError("Litmus is not a time machine") 1267 raise ValueError("Litmus is not a time machine")
1234 1268
1235 x = self._get_time_xpos(start_time) 1269 x = self._get_time_xpos(start_time)
1236 y = self._get_item_ypos(cpu_no) - self._get_mini_bar_ofs() 1270 y = self._get_item_ypos(len(self.y_item_list))
1237 width = self._get_bar_width(start_time, end_time) 1271 width = self._get_bar_width(start_time, end_time)
1238 height = self._get_mini_bar_height() 1272 height = self._get_mini_bar_height()
1239 1273
1240 self.canvas.draw_mini_bar(x, y, width, height, cpu_no, selected) 1274 self.canvas.draw_mini_bar(x, y, width, height, task_no, selected)
1241 1275
1242 if job_no is not None: 1276 if job_no is not None:
1243 x += GraphFormat.MINI_BAR_LABEL_OFS 1277 x += GraphFormat.MINI_BAR_LABEL_OFS
@@ -1247,8 +1281,8 @@ class CpuGraph(Graph):
1247 1281
1248 def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event): 1282 def add_sel_mini_bar_at_time(self, start_time, end_time, task_no, cpu_no, event):
1249 x = self._get_time_xpos(start_time) 1283 x = self._get_time_xpos(start_time)
1250 y = self._get_item_ypos(cpu_no) - self._get_mini_bar_ofs() 1284 y = self._get_item_ypos(len(self.y_item_list))
1251 width = self._get_bar_width(start_time, end_time) 1285 width = self._get_bar_width(start_time, end_time)
1252 height = self._get_mini_bar_height() 1286 height = self._get_mini_bar_height()
1253 1287
1254 self.canvas.add_sel_mini_bar(x, y, width, height, cpu_no, selected) 1288 self.canvas.add_sel_mini_bar(x, y, width, height, event)
diff --git a/viz/format.py b/viz/format.py
index fed39f0..6469467 100644
--- a/viz/format.py
+++ b/viz/format.py
@@ -23,7 +23,7 @@ class GraphFormat(object):
23 probably don't care about most of them anyway).""" 23 probably don't care about most of them anyway)."""
24 24
25 GRID_COLOR = (0.7, 0.7, 0.7) 25 GRID_COLOR = (0.7, 0.7, 0.7)
26 HIGHLIGHT_COLOR = (0.8, 0.0, 0.0) 26 HIGHLIGHT_COLOR = (0.85, 0.0, 0.0)
27 BORDER_COLOR = (0.0, 0.0, 0.0) 27 BORDER_COLOR = (0.0, 0.0, 0.0)
28 LITE_BORDER_COLOR = (0.4, 0.4, 0.4) 28 LITE_BORDER_COLOR = (0.4, 0.4, 0.4)
29 29
diff --git a/viz/schedule.py b/viz/schedule.py
index f842c8d..e525b17 100644
--- a/viz/schedule.py
+++ b/viz/schedule.py
@@ -8,6 +8,10 @@ graphic."""
8from draw import * 8from draw import *
9import util 9import util
10 10
11import copy
12
13EVENT_LIST = None
14
11class TimeSlotArray(object): 15class TimeSlotArray(object):
12 """Represents another way of organizing the events. This structure organizes events by 16 """Represents another way of organizing the events. This structure organizes events by
13 the (approximate) time at which they occur. Events that occur at approximately the same 17 the (approximate) time at which they occur. Events that occur at approximately the same
@@ -17,14 +21,28 @@ class TimeSlotArray(object):
17 TASK_LIST = 0 21 TASK_LIST = 0
18 CPU_LIST = 1 22 CPU_LIST = 1
19 23
20 def __init__(self, start, end, time_per_maj, num_tasks, num_cpus): 24 def __init__(self, start=None, end=None, time_per_maj=1, num_tasks=0, num_cpus=0):
25 if start is None or end is None:
26 self.array = None
27 return
28
21 self.start = start 29 self.start = start
22 self.end = end 30 self.end = end
23 self.time_per_maj = time_per_maj 31 self.time_per_maj = time_per_maj
24 self.list_sizes = { TimeSlotArray.TASK_LIST : num_tasks, TimeSlotArray.CPU_LIST : num_cpus } 32 self.list_sizes = { TimeSlotArray.TASK_LIST : num_tasks, TimeSlotArray.CPU_LIST : num_cpus }
25 self.array = [{TimeSlotArray.TASK_LIST : [{} for i in range(0, num_tasks)], \ 33 self.array = {}
26 TimeSlotArray.CPU_LIST : [{} for i in range (0, num_cpus)]} \ 34
27 for i in range(0, (end - start) // self.time_per_maj + 1)] 35 for type in self.list_sizes:
36 num = self.list_sizes[type]
37 self.array[type] = []
38 for i in range(0, int((end - start) // self.time_per_maj + 1)):
39 # for each slot in the array, we need a list of all events under this type
40 # (for example, a list of all events that occur in this time slot, indexed
41 # by task).
42 self.array[type].append([])
43 for j in range(0, num):
44 self.array[type][i].append(dict(zip(EVENT_LIST, \
45 [[] for j in range(0, len(EVENT_LIST))])))
28 46
29 def get_time_slot(self, time): 47 def get_time_slot(self, time):
30 return int((time - self.start) // self.time_per_maj) 48 return int((time - self.start) // self.time_per_maj)
@@ -34,29 +52,32 @@ class TimeSlotArray(object):
34 cpu = event.get_cpu() 52 cpu = event.get_cpu()
35 time_slot = self.get_time_slot(event.get_time()) 53 time_slot = self.get_time_slot(event.get_time())
36 54
37 self.array[time_slot][TimeSlotArray.TASK_LIST][task_no][event.__class__] = event 55 self.array[TimeSlotArray.TASK_LIST][time_slot][task_no][event.__class__].append(event)
38 self.array[time_slot][TimeSlotArray.CPU_LIST][cpu][event.__class__] = event 56 self.array[TimeSlotArray.CPU_LIST][time_slot][cpu][event.__class__].append(event)
39 57
40 span_events = { SwitchAwayEvent : IsRunningDummy, InversionEndEvent : InversionDummy} 58 span_events = { SwitchAwayEvent : IsRunningDummy, InversionEndEvent : InversionDummy}
41 59
42 for span_event in span_events: 60 for span_event in span_events:
43 if isinstance(event, span_event) and not event.is_erroneous(): 61 if isinstance(event, span_event) and event.corresp_start_event is not None:
44 start_slot = self.get_time_slot(event.corresp_start_event.get_time()) 62 start_slot = self.get_time_slot(event.corresp_start_event.get_time())
45 end_slot = self.get_time_slot(event.get_time()) 63 end_slot = time_slot
46 for slot in range(start_slot + 1, end_slot): 64 for slot in range(start_slot + 1, end_slot):
47 dummy = span_events[span_event](task_no, cpu) 65 dummy = span_events[span_event](task_no, cpu)
48 dummy.corresp_start_event = event.corresp_start_event 66 dummy.corresp_start_event = event.corresp_start_event
49 self.array[slot][TimeSlotArray.TASK_LIST][task_no][dummy.__class__] = dummy 67 self.array[TimeSlotArray.TASK_LIST][slot][task_no][dummy.__class__].append(dummy)
50 self.array[slot][TimeSlotArray.CPU_LIST][cpu][dummy.__class__] = dummy 68 self.array[TimeSlotArray.CPU_LIST][slot][cpu][dummy.__class__].append(dummy)
51 69
52 def iter_over_period(self, start, end, start_no, end_no, list_type, event_types): 70 def iter_over_period(self, start, end, start_no, end_no, list_type, event_types):
71 if self.array is None:
72 return # empty schedule
73
53 if start > end: 74 if start > end:
54 raise ValueError('Litmus is not a time machine') 75 raise ValueError('Litmus is not a time machine')
55 if start_no > end_no: 76 if start_no > end_no:
56 raise ValueError('start no should be less than end no') 77 raise ValueError('start no should be less than end no')
57 78
58 start_slot = max(0, self.get_time_slot(start)) 79 start_slot = max(0, self.get_time_slot(start))
59 end_slot = min(len(self.array), self.get_time_slot(end) + 2) 80 end_slot = min(len(self.array[list_type]), self.get_time_slot(end) + 2)
60 81
61 start_no = max(0, start_no) 82 start_no = max(0, start_no)
62 end_no = min(self.list_sizes[list_type] - 1, end_no) 83 end_no = min(self.list_sizes[list_type] - 1, end_no)
@@ -64,8 +85,8 @@ class TimeSlotArray(object):
64 for slot in range(start_slot, end_slot): 85 for slot in range(start_slot, end_slot):
65 for no in range(start_no, end_no + 1): 86 for no in range(start_no, end_no + 1):
66 for type in event_types: 87 for type in event_types:
67 if type in self.array[slot][list_type][no]: 88 for event in self.array[list_type][slot][no][type]:
68 yield self.array[slot][list_type][no][type] 89 yield event
69 90
70class Schedule(object): 91class Schedule(object):
71 """The total schedule (task system), consisting of a certain number of 92 """The total schedule (task system), consisting of a certain number of
@@ -75,36 +96,47 @@ class Schedule(object):
75 self.name = name 96 self.name = name
76 self.tasks = {} 97 self.tasks = {}
77 self.task_list = [] 98 self.task_list = []
99 self.selected = {}
78 self.time_slot_array = None 100 self.time_slot_array = None
79 self.cur_task_no = 0 101 self.cur_task_no = 0
80 self.num_cpus = num_cpus 102 self.num_cpus = num_cpus
81 for task in task_list: 103 for task in task_list:
82 self.add_task(task) 104 self.add_task(task)
83 105
84 def set_time_params(self, time_per_maj=None): 106 def get_selected(self):
85 if self.get_task_list() is None: 107 return self.selected
86 return (0, 0)
87 108
109 def set_selected(self, new_selected):
110 for event in self.selected:
111 event.selected = False
112 for event in new_selected:
113 event.selected = True
114 self.selected = new_selected
115
116 def get_selected(self):
117 return copy.copy(self.selected)
118
119 def set_time_params(self, time_per_maj=None, max_num_slots=None):
88 def find_extreme_time_sched(sched, cmp): 120 def find_extreme_time_sched(sched, cmp):
89 def find_extreme_time_task(task, cmp): 121 def find_extreme_time_task(task, cmp):
90 def find_extreme_time_job(job, cmp): 122 def find_extreme_time_job(job, cmp):
91 extreme_time = None 123 extreme_time = None
92 for time in job.get_events(): 124 for time in job.get_events():
93 if extreme_time is None or cmp(time, extreme_time) < 0: 125 if (extreme_time is None) or cmp(time, extreme_time) < 0:
94 extreme_time = time 126 extreme_time = time
95 return extreme_time 127 return extreme_time
96 128
97 extreme_time = None 129 extreme_time = None
98 for job_no in task.get_jobs(): 130 for job_no in task.get_jobs():
99 time = find_extreme_time_job(task.get_jobs()[job_no], cmp) 131 time = find_extreme_time_job(task.get_jobs()[job_no], cmp)
100 if time is not None and (extreme_time is None or cmp(time, extreme_time) < 0): 132 if (time is not None) and ((extreme_time is None) or cmp(time, extreme_time) < 0):
101 extreme_time = time 133 extreme_time = time
102 return extreme_time 134 return extreme_time
103 135
104 extreme_time = None 136 extreme_time = None
105 for task in sched.get_task_list(): 137 for task in sched.get_task_list():
106 time = find_extreme_time_task(task, cmp) 138 time = find_extreme_time_task(task, cmp)
107 if time is not None and (extreme_time is None or cmp(time, extreme_time) < 0): 139 if (time is not None) and ((extreme_time is None) or cmp(time, extreme_time) < 0):
108 extreme_time = time 140 extreme_time = time
109 141
110 return extreme_time 142 return extreme_time
@@ -129,20 +161,32 @@ class Schedule(object):
129 161
130 self.start = find_extreme_time_sched(self, earliest_cmp) 162 self.start = find_extreme_time_sched(self, earliest_cmp)
131 self.end = find_extreme_time_sched(self, latest_cmp) 163 self.end = find_extreme_time_sched(self, latest_cmp)
132 self.time_per_maj = time_per_maj 164 if self.start is None or self.end is None:
165 self.time_slot_array = TimeSlotArray()
166 return
167
168 min_time_per_maj = (self.end - self.start) * 1.0 / max_num_slots
169 if max_num_slots is not None and time_per_maj is not None:
170 if time_per_maj < min_time_per_maj:
171 self.time_per_maj = min_time_per_maj
172 else:
173 self.time_per_maj = time_per_maj
174 else:
175 self.time_per_maj = time_per_maj
133 self.time_slot_array = None 176 self.time_slot_array = None
134 if self.time_per_maj is not None: 177 if self.time_per_maj is not None:
135 self.time_slot_array = TimeSlotArray(self.start, self.end, time_per_maj, \ 178 self.time_slot_array = TimeSlotArray(self.start, self.end, self.time_per_maj, \
136 len(self.task_list), self.num_cpus) 179 len(self.task_list), self.num_cpus)
137 180
181
138 def get_time_slot_array(self): 182 def get_time_slot_array(self):
139 return self.time_slot_array 183 return self.time_slot_array
140 184
141 def get_time_bounds(self): 185 def get_time_bounds(self):
142 return (self.start, self.end) 186 return (self.start, self.end)
143 187
144 def scan(self, time_per_maj): 188 def scan(self, time_per_maj, max_num_slots):
145 self.set_time_params(time_per_maj) 189 self.set_time_params(time_per_maj, max_num_slots)
146 190
147 # we scan the graph task by task, and job by job 191 # we scan the graph task by task, and job by job
148 switches = {} 192 switches = {}
@@ -261,9 +305,13 @@ class DummyEvent(object):
261 def get_layer(self): 305 def get_layer(self):
262 return self.layer 306 return self.layer
263 307
264 def render(self, graph, layer, prev_events): 308 def render(self, graph, layer, prev_events, selectable=False):
265 """Method that the visualizer calls to tell the event to render itself 309 """Method that the visualizer calls to tell the event to render itself
266 Obviously only implemented by subclasses (actual event types)""" 310 Obviously only implemented by subclasses (actual event types)
311
312 ``Rendering'' can mean either actually drawing the event or just
313 adding it as a selectable region. This is controlled by the
314 ``selectable'' parameter"""
267 raise NotImplementdError 315 raise NotImplementdError
268 316
269class Event(DummyEvent): 317class Event(DummyEvent):
@@ -295,10 +343,6 @@ class Event(DummyEvent):
295 def is_selected(self): 343 def is_selected(self):
296 """Returns whether the event has been selected by the user. (needed for rendering)""" 344 """Returns whether the event has been selected by the user. (needed for rendering)"""
297 return self.selected 345 return self.selected
298
299 def set_selected(self, sel):
300 """Sets the event's state to selected."""
301 self.selected = sel
302 346
303 def scan(self, cur_cpu, switches): 347 def scan(self, cur_cpu, switches):
304 """Part of the procedure that walks through all the events and sets 348 """Part of the procedure that walks through all the events and sets
@@ -328,13 +372,16 @@ class SuspendEvent(Event):
328 print "suspending on a CPU different from the CPU we are on!" 372 print "suspending on a CPU different from the CPU we are on!"
329 super(SuspendEvent, self).scan(cur_cpu, switches) 373 super(SuspendEvent, self).scan(cur_cpu, switches)
330 374
331 def render(self, graph, layer, prev_events): 375 def render(self, graph, layer, prev_events, selectable=False):
332 if layer == self.layer: 376 if layer == self.layer:
333 prev_events[self] = None 377 prev_events[self] = None
334 graph.draw_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 378 if selectable:
335 self.get_cpu(), self.is_selected()) 379 graph.add_sel_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
336 graph.add_sel_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
337 self.get_cpu(), self) 380 self.get_cpu(), self)
381 else:
382 graph.draw_suspend_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
383 self.get_cpu(), self.is_selected())
384
338 385
339class ResumeEvent(Event): 386class ResumeEvent(Event):
340 def __init__(self, time, cpu): 387 def __init__(self, time, cpu):
@@ -350,13 +397,16 @@ class ResumeEvent(Event):
350 print "Resuming when currently scheduled on a CPU, but on a different CPU from the current CPU!" 397 print "Resuming when currently scheduled on a CPU, but on a different CPU from the current CPU!"
351 super(ResumeEvent, self).scan(cur_cpu, switches) 398 super(ResumeEvent, self).scan(cur_cpu, switches)
352 399
353 def render(self, graph, layer, prev_events): 400 def render(self, graph, layer, prev_events, selectable=False):
354 if layer == self.layer: 401 if layer == self.layer:
355 prev_events[self] = None 402 prev_events[self] = None
356 graph.draw_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 403 if selectable:
357 self.get_cpu(), self.is_selected()) 404 graph.add_sel_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
358 graph.add_sel_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
359 self.get_cpu(), self) 405 self.get_cpu(), self)
406 else:
407 graph.draw_resume_triangle_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
408 self.get_cpu(), self.is_selected())
409
360 410
361class CompleteEvent(Event): 411class CompleteEvent(Event):
362 def __init__(self, time, cpu): 412 def __init__(self, time, cpu):
@@ -369,14 +419,15 @@ class CompleteEvent(Event):
369 def scan(self, cur_cpu, switches): 419 def scan(self, cur_cpu, switches):
370 super(CompleteEvent, self).scan(cur_cpu, switches) 420 super(CompleteEvent, self).scan(cur_cpu, switches)
371 421
372 def render(self, graph, layer, prev_events): 422 def render(self, graph, layer, prev_events, selectable=False):
373 if layer == Canvas.TOP_LAYER: 423 if layer == Canvas.TOP_LAYER:
374 prev_events[self] = None 424 prev_events[self] = None
375 graph.draw_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 425 if selectable:
376 self.get_cpu(), self.is_selected()) 426 graph.add_sel_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
377 graph.add_sel_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
378 self.get_cpu(), self) 427 self.get_cpu(), self)
379 428 else:
429 graph.draw_completion_marker_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
430 self.get_cpu(), self.is_selected())
380 431
381class SwitchAwayEvent(Event): 432class SwitchAwayEvent(Event):
382 def __init__(self, time, cpu): 433 def __init__(self, time, cpu):
@@ -412,10 +463,10 @@ class SwitchAwayEvent(Event):
412 463
413 super(SwitchAwayEvent, self).scan(cur_cpu, switches) 464 super(SwitchAwayEvent, self).scan(cur_cpu, switches)
414 465
415 def render(self, graph, layer, prev_events): 466 def render(self, graph, layer, prev_events, selectable=False):
416 if self.corresp_start_event is None or self.corresp_start_event in prev_events: 467 if self.corresp_start_event is None or self.corresp_start_event in prev_events:
417 return # erroneous switch away or already rendered 468 return # erroneous switch away or already rendered
418 self.corresp_start_event.render(graph, layer, prev_events) 469 self.corresp_start_event.render(graph, layer, prev_events, selectable)
419 470
420class SwitchToEvent(Event): 471class SwitchToEvent(Event):
421 def __init__(self, time, cpu): 472 def __init__(self, time, cpu):
@@ -442,17 +493,19 @@ class SwitchToEvent(Event):
442 493
443 super(SwitchToEvent, self).scan(cur_cpu, switches) 494 super(SwitchToEvent, self).scan(cur_cpu, switches)
444 495
445 def render(self, graph, layer, prev_events): 496 def render(self, graph, layer, prev_events, selectable=False):
446 if self.is_erroneous(): 497 if self.corresp_end_event is None:
447 return # erroneous switch to 498 return # fatally erroneous switch to
448 if layer == Canvas.BOTTOM_LAYER: 499 if layer == Canvas.BOTTOM_LAYER:
449 prev_events[self] = None 500 prev_events[self] = None
450 cpu = self.get_cpu() 501 cpu = self.get_cpu()
451 task_no = self.get_job().get_task().get_task_no() 502 task_no = self.get_job().get_task().get_task_no()
452 graph.draw_bar_at_time(self.get_time(), self.corresp_end_event.get_time(), 503 if selectable:
453 task_no, cpu, self.get_job().get_job_no(), self.is_selected()) 504 graph.add_sel_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
454 graph.add_sel_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
455 task_no, cpu, self) 505 task_no, cpu, self)
506 else:
507 graph.draw_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
508 task_no, cpu, self.get_job().get_job_no(), self.is_selected())
456 509
457class ReleaseEvent(Event): 510class ReleaseEvent(Event):
458 def __init__(self, time, cpu): 511 def __init__(self, time, cpu):
@@ -465,13 +518,16 @@ class ReleaseEvent(Event):
465 def scan(self, cur_cpu, switches): 518 def scan(self, cur_cpu, switches):
466 super(ReleaseEvent, self).scan(cur_cpu, switches) 519 super(ReleaseEvent, self).scan(cur_cpu, switches)
467 520
468 def render(self, graph, layer, prev_events): 521 def render(self, graph, layer, prev_events, selectable=False):
469 prev_events[self] = None 522 prev_events[self] = None
470 if layer == Canvas.TOP_LAYER: 523 if layer == Canvas.TOP_LAYER:
471 graph.draw_release_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 524 if selectable:
525 graph.add_sel_release_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
526 self)
527 else:
528 graph.draw_release_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
472 self.get_job().get_job_no(), self.is_selected()) 529 self.get_job().get_job_no(), self.is_selected())
473 graph.add_sel_release_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 530
474 self)
475 531
476class DeadlineEvent(Event): 532class DeadlineEvent(Event):
477 def __init__(self, time, cpu): 533 def __init__(self, time, cpu):
@@ -484,13 +540,16 @@ class DeadlineEvent(Event):
484 def scan(self, cur_cpu, switches): 540 def scan(self, cur_cpu, switches):
485 super(DeadlineEvent, self).scan(cur_cpu, switches) 541 super(DeadlineEvent, self).scan(cur_cpu, switches)
486 542
487 def render(self, graph, layer, prev_events): 543 def render(self, graph, layer, prev_events, selectable=False):
488 prev_events[self] = None 544 prev_events[self] = None
489 if layer == Canvas.TOP_LAYER: 545 if layer == Canvas.TOP_LAYER:
490 graph.draw_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(), 546 if selectable:
491 self.get_job().get_job_no(), self.is_selected()) 547 graph.add_sel_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
492 graph.add_sel_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
493 self) 548 self)
549 else:
550 graph.draw_deadline_arrow_at_time(self.get_time(), self.get_job().get_task().get_task_no(),
551 self.get_job().get_job_no(), self.is_selected())
552
494 553
495class InversionStartEvent(ErrorEvent): 554class InversionStartEvent(ErrorEvent):
496 def __init__(self, time): 555 def __init__(self, time):
@@ -512,15 +571,18 @@ class InversionStartEvent(ErrorEvent):
512 # the corresp_end_event should already be set 571 # the corresp_end_event should already be set
513 super(InversionStartEvent, self).scan(cur_cpu, switches) 572 super(InversionStartEvent, self).scan(cur_cpu, switches)
514 573
515 def render(self, graph, layer, prev_events): 574 def render(self, graph, layer, prev_events, selectable=False):
516 if layer == Canvas.BOTTOM_LAYER: 575 if layer == Canvas.BOTTOM_LAYER:
517 prev_events[self] = None 576 prev_events[self] = None
518 cpu = self.get_cpu() 577 cpu = self.get_cpu()
519 task_no = self.get_job().get_task().get_task_no() 578 task_no = self.get_job().get_task().get_task_no()
520 graph.draw_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(), 579 if selectable:
521 task_no, cpu, self.get_job().get_job_no(), self.is_selected()) 580 graph.add_sel_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
522 graph.add_sel_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
523 task_no, cpu, self) 581 task_no, cpu, self)
582 else:
583 graph.draw_mini_bar_at_time(self.get_time(), self.corresp_end_event.get_time(),
584 task_no, cpu, self.get_job().get_job_no(), self.is_selected())
585
524 586
525class InversionEndEvent(ErrorEvent): 587class InversionEndEvent(ErrorEvent):
526 def __init__(self, time): 588 def __init__(self, time):
@@ -549,23 +611,25 @@ class InversionEndEvent(ErrorEvent):
549 611
550 super(InversionEndEvent, self).scan(cur_cpu, switches) 612 super(InversionEndEvent, self).scan(cur_cpu, switches)
551 613
552 def render(self, graph, layer, prev_events): 614 def render(self, graph, layer, prev_events, selectable=False):
553 if self.corresp_start_event is None or self.corresp_start_event in prev_events: 615 if self.corresp_start_event is None or self.corresp_start_event in prev_events:
554 return # erroneous inversion end or already rendered 616 return # erroneous inversion end or already rendered
555 self.corresp_start_event.render(graph, layer, prev_events) 617 self.corresp_start_event.render(graph, layer, prev_events, selectable)
556 618
557class InversionDummy(DummyEvent): 619class InversionDummy(DummyEvent):
558 def render(self, graph, layer, prev_events): 620 def render(self, graph, layer, prev_events, selectable=False):
559 if self.corresp_start_event in prev_events: 621 if self.corresp_start_event in prev_events:
560 return # we have already been rendered 622 return # we have already been rendered
561 self.corresp_start_event.render(graph, layer, prev_events) 623 self.corresp_start_event.render(graph, layer, prev_events, selectable)
562 624
563class IsRunningDummy(DummyEvent): 625class IsRunningDummy(DummyEvent):
564 def render(self, graph, layer, prev_events): 626 def render(self, graph, layer, prev_events, selectable=False):
565 if self.corresp_start_event in prev_events: 627 if self.corresp_start_event in prev_events:
566 return # we have already been rendered 628 return # we have already been rendered
567 self.corresp_start_event.render(graph, layer, prev_events) 629 self.corresp_start_event.render(graph, layer, prev_events, selectable)
568 630
569EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None, SwitchAwayEvent : None, 631EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None,
570 SwitchToEvent : None, ReleaseEvent : None, DeadlineEvent : None, IsRunningDummy : None, 632 SwitchAwayEvent : None, SwitchToEvent : None, ReleaseEvent : None,
571 InversionStartEvent : None, InversionEndEvent : None, InversionDummy : None} 633 DeadlineEvent : None, IsRunningDummy : None,
634 InversionStartEvent : None, InversionEndEvent : None,
635 InversionDummy : None}
diff --git a/viz/viewer.py b/viz/viewer.py
index a695473..a7871a7 100644
--- a/viz/viewer.py
+++ b/viz/viewer.py
@@ -3,20 +3,36 @@
3"""GUI stuff.""" 3"""GUI stuff."""
4 4
5from schedule import * 5from schedule import *
6
6from renderer import * 7from renderer import *
7 8
8import pygtk 9import pygtk
9import gtk 10import gtk
10import gobject 11import gobject
12
13class GraphContextMenu(gtk.Menu):
14 MAX_STR_LEN = 80
11 15
16 def __init__(self, selected):
17 super(GraphContextMenu, self).__init__()
18
19 for event in selected:
20 string = str(event)
21 if len(string) > GraphContextMenu.MAX_STR_LEN - 3:
22 string = string[:GraphContextMenu.MAX_STR_LEN - 3] + '...'
23 item = gtk.MenuItem(string)
24 self.append(item)
25 item.show()
26
12class GraphArea(gtk.DrawingArea): 27class GraphArea(gtk.DrawingArea):
13 DAREA_WIDTH_REQ = 500
14 DAREA_HEIGHT_REQ = 300
15 HORIZ_PAGE_SCROLL_FACTOR = 4.8 28 HORIZ_PAGE_SCROLL_FACTOR = 4.8
16 HORIZ_STEP_SCROLL_FACTOR = 0.8 29 HORIZ_STEP_SCROLL_FACTOR = 0.8
17 VERT_PAGE_SCROLL_FACTOR = 3.0 30 VERT_PAGE_SCROLL_FACTOR = 3.0
18 VERT_STEP_SCROLL_FACTOR = 0.5 31 VERT_STEP_SCROLL_FACTOR = 0.5
19 32
33 SELECT_THICKNESS = 1.5
34 SELECT_COLOR = (0.85, 0.0, 0.0)
35
20 def __init__(self, renderer): 36 def __init__(self, renderer):
21 super(GraphArea, self).__init__() 37 super(GraphArea, self).__init__()
22 38
@@ -27,25 +43,38 @@ class GraphArea(gtk.DrawingArea):
27 self.width = 0 43 self.width = 0
28 self.height = 0 44 self.height = 0
29 45
30 self.now_selected = []
31
32 self.set_set_scroll_adjustments_signal('set-scroll-adjustments') 46 self.set_set_scroll_adjustments_signal('set-scroll-adjustments')
33 47
34 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)
50
51 self.left_button_start_coor = None
52 self.select_rect = None
53 self.ctrl_clicked = False
35 54
36 self.connect('expose-event', self.expose) 55 self.connect('expose-event', self.expose)
37 self.connect('size-allocate', self.size_allocate) 56 self.connect('size-allocate', self.size_allocate)
38 self.connect('set-scroll-adjustments', self.set_scroll_adjustments) 57 self.connect('set-scroll-adjustments', self.set_scroll_adjustments)
39 self.connect('button-press-event', self.button_press) 58 self.connect('button-press-event', self.button_press)
59 self.connect('button-release-event', self.button_release)
40 self.connect('motion-notify-event', self.motion_notify) 60 self.connect('motion-notify-event', self.motion_notify)
41 61
42 self.set_size_request(GraphArea.DAREA_WIDTH_REQ, GraphArea.DAREA_HEIGHT_REQ)
43
44 def expose(self, widget, event, data=None): 62 def expose(self, widget, event, data=None):
45 ctx = widget.window.cairo_create() 63 ctx = widget.window.cairo_create()
46 graph = self.renderer.get_graph() 64 graph = self.renderer.get_graph()
47 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)
48 graph.render_surface(self.renderer.get_schedule()) 66 graph.render_surface(self.renderer.get_schedule(), 0, 0, self.width, self.height)
67
68 # render dragging rectangle, if there is one
69 if self.select_rect is not None:
70 x, y, width, height = self.select_rect
71 thickness = GraphArea.SELECT_THICKNESS
72 color = GraphArea.SELECT_COLOR
73
74 ctx.rectangle(x, y, width, height)
75 ctx.set_line_width(thickness)
76 ctx.set_source_rgb(color[0], color[1], color[2])
77 ctx.stroke()
49 78
50 def set_scroll_adjustments(self, widget, horizontal, vertical, data=None): 79 def set_scroll_adjustments(self, widget, horizontal, vertical, data=None):
51 graph = self.renderer.get_graph() 80 graph = self.renderer.get_graph()
@@ -55,9 +84,11 @@ class GraphArea(gtk.DrawingArea):
55 self.horizontal = horizontal 84 self.horizontal = horizontal
56 self.vertical = vertical 85 self.vertical = vertical
57 self.config_scrollbars(self.cur_x, self.cur_y) 86 self.config_scrollbars(self.cur_x, self.cur_y)
58 87
59 self.horizontal.connect('value-changed', self.horizontal_value_changed) 88 if self.horizontal is not None:
60 self.vertical.connect('value-changed', self.vertical_value_changed) 89 self.horizontal.connect('value-changed', self.horizontal_value_changed)
90 if self.vertical is not None:
91 self.vertical.connect('value-changed', self.vertical_value_changed)
61 92
62 def horizontal_value_changed(self, adjustment): 93 def horizontal_value_changed(self, adjustment):
63 self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width()) 94 self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width())
@@ -85,7 +116,7 @@ class GraphArea(gtk.DrawingArea):
85 116
86 if self.horizontal is not None: 117 if self.horizontal is not None:
87 self.horizontal.set_all(hvalue, 0.0, width, graph.get_attrs().maj_sep * GraphArea.HORIZ_STEP_SCROLL_FACTOR, 118 self.horizontal.set_all(hvalue, 0.0, width, graph.get_attrs().maj_sep * GraphArea.HORIZ_STEP_SCROLL_FACTOR,
88 graph.get_attrs().maj_sep * GraphArea.HORIZ_PAGE_SCROLL_FACTOR, self.width) 119 graph.get_attrs().maj_sep * GraphArea.HORIZ_PAGE_SCROLL_FACTOR, self.width)
89 if self.vertical is not None: 120 if self.vertical is not None:
90 self.vertical.set_all(vvalue, 0.0, height, graph.get_attrs().y_item_size * GraphArea.VERT_STEP_SCROLL_FACTOR, 121 self.vertical.set_all(vvalue, 0.0, height, graph.get_attrs().y_item_size * GraphArea.VERT_STEP_SCROLL_FACTOR,
91 graph.get_attrs().y_item_size * GraphArea.VERT_PAGE_SCROLL_FACTOR, self.height) 122 graph.get_attrs().y_item_size * GraphArea.VERT_PAGE_SCROLL_FACTOR, self.height)
@@ -101,7 +132,9 @@ class GraphArea(gtk.DrawingArea):
101 msg = None 132 msg = None
102 133
103 graph = self.renderer.get_graph() 134 graph = self.renderer.get_graph()
104 just_selected = graph.get_selected_regions(motion_event.x, motion_event.y) 135
136 graph.render_surface(self.renderer.get_schedule(), motion_event.x, motion_event.y, 0, 0, True)
137 just_selected = graph.get_selected_regions(motion_event.x, motion_event.y, 0, 0)
105 if not just_selected: 138 if not just_selected:
106 msg = '' 139 msg = ''
107 the_event = None 140 the_event = None
@@ -116,36 +149,96 @@ class GraphArea(gtk.DrawingArea):
116 msg = str(the_event) 149 msg = str(the_event)
117 150
118 self.emit('update-event-description', the_event, msg) 151 self.emit('update-event-description', the_event, msg)
152
153 if self.left_button_start_coor is not None:
154 selected = {}
155 if self.ctrl_clicked:
156 selected = dict(self.last_selected)
157
158 # dragging a rectangle
159 x = min(self.left_button_start_coor[0], motion_event.x)
160 y = min(self.left_button_start_coor[1], motion_event.y)
161 width = abs(self.left_button_start_coor[0] - motion_event.x)
162 height = abs(self.left_button_start_coor[1] - motion_event.y)
163
164 graph.render_surface(self.renderer.get_schedule(), x, y, width, height, True)
165 selected.update(graph.get_selected_regions(x, y, width, height))
166 self.renderer.get_schedule().set_selected(selected)
167
168 self.select_rect = (x, y, width, height)
119 169
170 self._dirty()
171
120 def button_press(self, widget, button_event, data=None): 172 def button_press(self, widget, button_event, data=None):
121 graph = self.renderer.get_graph() 173 graph = self.renderer.get_graph()
122 174
175 self.ctrl_clicked = button_event.state & gtk.gdk.CONTROL_MASK
176
123 if button_event.button == 1: 177 if button_event.button == 1:
124 just_selected = graph.get_selected_regions(button_event.x, button_event.y) 178 self.left_button_start_coor = (button_event.x, button_event.y)
179 graph.render_surface(self.renderer.get_schedule(), \
180 button_event.x, button_event.y, 0, 0, True)
181
182 just_selected = graph.get_selected_regions(button_event.x, button_event.y, 0, 0)
125 183
126 max_layer = self._find_max_layer(just_selected) 184 max_layer = self._find_max_layer(just_selected)
127 185
186 new_now_selected = None
187 if self.ctrl_clicked:
188 new_now_selected = self.renderer.get_schedule().get_selected()
189 else:
190 new_now_selected = {}
191
128 # only select those events which were in the top layer (it's 192 # only select those events which were in the top layer (it's
129 # not intuitive to click something and then have something 193 # not intuitive to click something and then have something
130 # below it get selected). Also, clicking something that 194 # below it get selected). Also, clicking something that
131 # is selected deselects it 195 # is selected deselects it, if it's the only thing selected
132 new_now_selected = {}
133 for event in just_selected: 196 for event in just_selected:
134 if event.get_layer() == max_layer: 197 if event.get_layer() == max_layer:
135 if not event.is_selected(): 198 val = True
199 now_selected = self.renderer.get_schedule().get_selected()
200 if (len(now_selected) == 1 or self.ctrl_clicked) and event in now_selected:
201 val = not event.is_selected
202 if val:
136 new_now_selected[event] = None 203 new_now_selected[event] = None
137 event.set_selected(not event.is_selected()) 204 elif event in new_now_selected:
138 break 205 del new_now_selected[event]
206 break # only pick one event when just clicking
139 207
140 for event in self.now_selected: 208 self.renderer.get_schedule().set_selected(new_now_selected)
141 if event not in new_now_selected: 209 self.last_selected = dict(new_now_selected)
142 event.set_selected(False)
143
144 self.now_selected = new_now_selected
145 210
146 rect = gtk.gdk.Rectangle(0, 0, self.width, self.height) 211 self._dirty()
147 self.window.invalidate_rect(rect, True) 212 elif button_event.button == 3:
148 213 self._release_band()
214 self.emit('request-context-menu', button_event, self.renderer.get_schedule().get_selected())
215
216 def button_release(self, widget, button_event, data=None):
217 self.ctrl_clicked = False
218 self.last_selected = dict(self.renderer.get_schedule().get_selected())
219
220 if button_event.button == 1:
221 self._release_band()
222
223 def _release_band(self):
224 old_select_rect = self.select_rect
225 self.left_button_start_coor = None
226 self.select_rect = None
227 if old_select_rect is not None:
228 self._dirty()
229
230 def _dirty(self, x=None, y=None, width=None, height=None):
231 if x is None:
232 x = 0
233 if y is None:
234 y = 0
235 if width is None:
236 width = self.width
237 if height is None:
238 height = self.height
239 rect = gtk.gdk.Rectangle(x, y, width, height)
240 self.window.invalidate_rect(rect, True)
241
149class GraphWindow(gtk.ScrolledWindow): 242class GraphWindow(gtk.ScrolledWindow):
150 def __init__(self, renderer): 243 def __init__(self, renderer):
151 super(GraphWindow, self).__init__(None, None) 244 super(GraphWindow, self).__init__(None, None)
@@ -157,37 +250,115 @@ class GraphWindow(gtk.ScrolledWindow):
157 def get_graph_area(self): 250 def get_graph_area(self):
158 return self.garea 251 return self.garea
159 252
160class MainWindow(object): 253class MainWindow(gtk.Window):
161 def __init__(self, renderer): 254 WINDOW_WIDTH_REQ = 500
162 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) 255 WINDOW_HEIGHT_REQ = 300
256
257 def __init__(self, request_renderer_change):
258 super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL)
259
260 self.connect('delete_event', self.delete_event)
261 self.connect('destroy', self.die)
262
263 self.connect('request-renderer-change', request_renderer_change)
264
265 self.file_menu = gtk.Menu()
266 self.open_item = gtk.MenuItem('_Open', True)
267 self.open_item.connect('activate', self.open_item_activate)
268 self.file_menu.append(self.open_item)
269 self.open_item.show()
270
271 self.quit_item = gtk.MenuItem('_Quit', True)
272 self.quit_item.connect('activate', self.quit_item_activate)
273 self.quit_item.show()
274 self.file_menu.append(self.quit_item)
163 275
164 self.window.connect('delete_event', self.delete_event) 276 self.file_item = gtk.MenuItem('_File', True)
165 self.window.connect('destroy', self.destroy) 277 self.file_item.set_submenu(self.file_menu)
278 self.file_item.show()
279
280 self.menu_bar = gtk.MenuBar()
281 self.menu_bar.append(self.file_item)
282
283 self.menu_bar.show()
166 284
167 self.vbox = gtk.VBox(False, 0) 285 self.vbox = gtk.VBox(False, 0)
168 286
169 self.gwindow = GraphWindow(renderer) 287 self.notebook = gtk.Notebook()
170 self.gwindow.get_graph_area().connect('update-event-description', 288
171 self.update_event_description) 289 self.notebook.last_page = -1
172 self.gwindow.show() 290 self.notebook.connect('switch-page', self.switch_page)
291
292 self.notebook.show()
173 293
174 self.desc_label = gtk.Label('') 294 self.desc_label = gtk.Label('')
175 self.desc_label.set_justify(gtk.JUSTIFY_LEFT) 295 self.desc_label.set_justify(gtk.JUSTIFY_LEFT)
176 self.desc_label.show() 296 self.desc_label.show()
177 297
178 self.vbox.pack_start(self.gwindow, True, True, 0) 298 self.vbox.pack_start(self.menu_bar, False, False, 0)
299 self.vbox.pack_start(self.notebook, True, True, 0)
179 self.vbox.pack_start(self.desc_label, False, False, 0) 300 self.vbox.pack_start(self.desc_label, False, False, 0)
180 self.vbox.show() 301 self.vbox.show()
181 302
182 self.window.add(self.vbox) 303 self.add(self.vbox)
183 self.window.show() 304
184 305 self.set_size_request(MainWindow.WINDOW_WIDTH_REQ, MainWindow.WINDOW_HEIGHT_REQ)
306
307 self.show()
308
309 def connect_widgets(self, garea):
310 garea.connect('update-event-description', self.update_event_description)
311 garea.connect('request-context-menu', self.request_context_menu)
312
313 def set_renderers(self, renderers):
314 for i in range(0, self.notebook.get_n_pages()):
315 self.notebook.remove_page(0)
316 for title in renderers:
317 gwindow = GraphWindow(renderers[title])
318 self.connect_widgets(gwindow.get_graph_area())
319 gwindow.show()
320 self.notebook.append_page(gwindow, gtk.Label(title))
321
322 def switch_page(self, widget, page, page_num):
323 if self.notebook.last_page >= 0:
324 hvalue = self.notebook.get_nth_page(self.notebook.last_page).get_hadjustment().get_value()
325 self.notebook.get_nth_page(page_num).get_hadjustment().set_value(hvalue)
326
327 self.notebook.last_page = page_num
328
185 def update_event_description(self, widget, event, msg): 329 def update_event_description(self, widget, event, msg):
186 self.desc_label.set_text(msg) 330 self.desc_label.set_text(msg)
187 331
332 def request_context_menu(self, widget, gdk_event, selected):
333 button = 0
334 if hasattr(gdk_event, 'button'):
335 button = gdk_event.button
336 time = gdk_event.time
337
338 menu = GraphContextMenu(selected)
339 menu.popup(None, None, None, button, time)
340
341 def open_item_activate(self, widget):
342 dialog = gtk.FileChooserDialog('Open File', self,
343 gtk.FILE_CHOOSER_ACTION_OPEN,
344 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
345 gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT))
346 dialog.set_select_multiple(True)
347
348 if dialog.run() == gtk.RESPONSE_ACCEPT:
349 filenames = dialog.get_filenames()
350 dialog.destroy()
351 self.emit('request-renderer-change', filenames, None)
352 else:
353 dialog.destroy()
354
355
356 def quit_item_activate(self, widget):
357 self.destroy()
358
188 def delete_event(self, widget, event, data=None): 359 def delete_event(self, widget, event, data=None):
189 return False 360 return False
190 361
191 def destroy(self, widget, data=None): 362 def die(self, widget, data=None):
192 gtk.main_quit() 363 gtk.main_quit()
193 364