diff options
| -rw-r--r-- | README | 106 | ||||
| -rw-r--r-- | convert.py | 2 | ||||
| -rw-r--r-- | gedf_test.py | 1 | ||||
| -rw-r--r-- | gedf_test.py~ | 1 | ||||
| -rwxr-xr-x | reader/sample_script.py | 8 | ||||
| -rw-r--r-- | reader/trace_reader.py | 32 | ||||
| -rwxr-xr-x | visualizer.py | 41 | ||||
| -rw-r--r-- | viz/__init__.py | 8 | ||||
| -rw-r--r-- | viz/draw.py | 138 | ||||
| -rw-r--r-- | viz/format.py | 2 | ||||
| -rw-r--r-- | viz/schedule.py | 210 | ||||
| -rw-r--r-- | viz/viewer.py | 253 |
12 files changed, 600 insertions, 202 deletions
| @@ -1 +1,105 @@ | |||
| 1 | See the LITMUS Wiki page for an explanation of this tool. | 1 | See the LITMUS Wiki page for a general explanation of this tool. |
| 2 | |||
| 3 | unit_trace consists of two modules and a core. The ``core'' is basically | ||
| 4 | a bunch of code, implemented as Python iterators, which converts the | ||
| 5 | raw trace data into a sequence of record objects, implemented in | ||
| 6 | Python. The modules are: | ||
| 7 | |||
| 8 | 1) A simple module that outputs the contents of each record to | ||
| 9 | stdout. This module, along with most of the core, can be found in the | ||
| 10 | reader/ directory. There is a sample script -- look at | ||
| 11 | sample_script.py in the reader/ directory (it's pretty | ||
| 12 | self-explanatory). Note that Mac is the one who coded most of the | ||
| 13 | this, though I can probably try to answer any questions about it since | ||
| 14 | I've had to go in there from time to time. | ||
| 15 | |||
| 16 | 2) The visualizer. Now, the GUI as it stands is very basic -- it's | ||
| 17 | basically just a shell for the core visualizer component. How to open | ||
| 18 | a file is obvious -- but note that you can open several files at a | ||
| 19 | time (since more often than not a trace consists of more than one | ||
| 20 | file, typically one for each CPU). | ||
| 21 | |||
| 22 | Most of the code for this is in the viz/ directory, but to run it, the | ||
| 23 | file you want to execute is visualizer.py (in the main directory). | ||
| 24 | |||
| 25 | A 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 | |||
| 55 | But 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 | |||
| 85 | How to install: | ||
| 86 | |||
| 87 | You should type | ||
| 88 | |||
| 89 | git clone -b wip-gary ssh://cvs.cs.unc.edu/cvs/proj/litmus/repo/unit-trace.git | ||
| 90 | |||
| 91 | to check out the repository. Note that you shouldn't check out the | ||
| 92 | master branch, as it's pretty outdated. It's all Python so far, so no | ||
| 93 | compiling or anything like that is necessary. | ||
| 94 | |||
| 95 | Requirements: | ||
| 96 | |||
| 97 | You're going to need Python 2.5 to run this. You'll also need to | ||
| 98 | install the pycairo and pygtk libraries. If anyone has questions about | ||
| 99 | how to do this or what these are, ask me. | ||
| 100 | |||
| 101 | Miscellanies: | ||
| 102 | |||
| 103 | Of course, let me know if you find any bugs (I'm sure there are | ||
| 104 | plenty, though, since this is fairly alpha software), if you're | ||
| 105 | unable to run it, or if you have any questions. \ No newline at end of file | ||
| @@ -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 | ||
| 85 | def _pid_to_task_name(pid): | 85 | def _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 | ||
| 12 | import copy | 11 | import 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 | ||
| 11 | import copy | 12 | import 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 |
| 16 | g6 = [ | 16 | g6 = [ |
| 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 | ||
| 28 | import struct | 28 | import struct |
| 29 | 29 | ||
| 30 | 30 | ############################################################################### | |
| 31 | # Class definitions | ||
| 32 | ############################################################################### | ||
| 33 | class 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): | |||
| 242 | def _get_type_name(type_num): | 254 | def _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 | ||
| 9 | import gtk | 9 | import gtk |
| 10 | 10 | ||
| 11 | path = 'sample_traces/' | 11 | TIME_PER_MAJ = 10000000 |
| 12 | MAX_NUM_SLOTS = 10000 | ||
| 12 | 13 | ||
| 13 | trace_list = [ | 14 | def request_renderer_change(widget, file_list, params): |
| 14 | path + 'st-g6-0.bin', | 15 | try: |
| 15 | path + 'st-g6-1.bin', | 16 | stream = reader.trace_reader.trace_reader(file_list) |
| 16 | path + 'st-g6-2.bin', | 17 | #stream = reader.sanitizer.sanitizer(stream) |
| 17 | path + '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 | ||
| 20 | if __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 | 35 | if __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 | |||
| 5 | import gtk | 5 | import gtk |
| 6 | 6 | ||
| 7 | gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | 7 | gobject.signal_new('set-scroll-adjustments', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, |
| 8 | None, (gtk.Adjustment, gtk.Adjustment)) | 8 | None, (gtk.Adjustment, gtk.Adjustment)) |
| 9 | gobject.signal_new('update-event-description', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | 9 | gobject.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)) |
| 11 | gobject.signal_new('request-context-menu', viewer.GraphArea, gobject.SIGNAL_RUN_FIRST, | ||
| 12 | None, (gtk.gdk.Event, gobject.TYPE_PYOBJECT)) | ||
| 13 | gobject.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 | ||
| 85 | class Pattern(object): | 85 | class 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 | ||
| 510 | class CairoCanvas(Canvas): | 527 | class 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 | ||
| 685 | class Graph(object): | 702 | class 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 | ||
| 921 | class TaskGraph(Graph): | 944 | class 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 | ||
| 1065 | class CpuGraph(Graph): | 1091 | class 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.""" | |||
| 8 | from draw import * | 8 | from draw import * |
| 9 | import util | 9 | import util |
| 10 | 10 | ||
| 11 | import copy | ||
| 12 | |||
| 13 | EVENT_LIST = None | ||
| 14 | |||
| 11 | class TimeSlotArray(object): | 15 | class 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 | ||
| 70 | class Schedule(object): | 91 | class 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 | ||
| 269 | class Event(DummyEvent): | 317 | class 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 | ||
| 339 | class ResumeEvent(Event): | 386 | class 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 | ||
| 361 | class CompleteEvent(Event): | 411 | class 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 | ||
| 381 | class SwitchAwayEvent(Event): | 432 | class 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 | ||
| 420 | class SwitchToEvent(Event): | 471 | class 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 | ||
| 457 | class ReleaseEvent(Event): | 510 | class 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 | ||
| 476 | class DeadlineEvent(Event): | 532 | class 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 | ||
| 495 | class InversionStartEvent(ErrorEvent): | 554 | class 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 | ||
| 525 | class InversionEndEvent(ErrorEvent): | 587 | class 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 | ||
| 557 | class InversionDummy(DummyEvent): | 619 | class 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 | ||
| 563 | class IsRunningDummy(DummyEvent): | 625 | class 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 | ||
| 569 | EVENT_LIST = {SuspendEvent : None, ResumeEvent : None, CompleteEvent : None, SwitchAwayEvent : None, | 631 | EVENT_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 | ||
| 5 | from schedule import * | 5 | from schedule import * |
| 6 | |||
| 6 | from renderer import * | 7 | from renderer import * |
| 7 | 8 | ||
| 8 | import pygtk | 9 | import pygtk |
| 9 | import gtk | 10 | import gtk |
| 10 | import gobject | 11 | import gobject |
| 12 | |||
| 13 | class 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 | |||
| 12 | class GraphArea(gtk.DrawingArea): | 27 | class 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 | |||
| 149 | class GraphWindow(gtk.ScrolledWindow): | 242 | class 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 | ||
| 160 | class MainWindow(object): | 253 | class 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 | ||
