diff options
author | Gary Bressler <garybressler@nc.rr.com> | 2010-03-19 12:55:12 -0400 |
---|---|---|
committer | Gary Bressler <garybressler@nc.rr.com> | 2010-03-19 12:55:12 -0400 |
commit | ee7ae82ae6df1378203957b7fa77066153e13c36 (patch) | |
tree | ceb3b420a5ce80b07e9a23bbef0ec9639dbb384f /unit_trace/viz/viewer.py | |
parent | e6dbe1605d8ae52ee65a08e929ed1810bd07d45c (diff) |
Fixed some graphical glitches, along with streamlining the access to the visualizer.
Diffstat (limited to 'unit_trace/viz/viewer.py')
-rw-r--r-- | unit_trace/viz/viewer.py | 182 |
1 files changed, 109 insertions, 73 deletions
diff --git a/unit_trace/viz/viewer.py b/unit_trace/viz/viewer.py index 120c7e7..236a467 100644 --- a/unit_trace/viz/viewer.py +++ b/unit_trace/viz/viewer.py | |||
@@ -9,6 +9,7 @@ from renderer import * | |||
9 | import pygtk | 9 | import pygtk |
10 | import gtk | 10 | import gtk |
11 | import gobject | 11 | import gobject |
12 | import copy | ||
12 | 13 | ||
13 | class GraphContextMenu(gtk.Menu): | 14 | class GraphContextMenu(gtk.Menu): |
14 | MAX_STR_LEN = 80 | 15 | MAX_STR_LEN = 80 |
@@ -30,8 +31,7 @@ class GraphArea(gtk.DrawingArea): | |||
30 | VERT_PAGE_SCROLL_FACTOR = 3.0 | 31 | VERT_PAGE_SCROLL_FACTOR = 3.0 |
31 | VERT_STEP_SCROLL_FACTOR = 0.5 | 32 | VERT_STEP_SCROLL_FACTOR = 0.5 |
32 | 33 | ||
33 | SELECT_THICKNESS = 1.5 | 34 | REFRESH_INFLATION_FACTOR = 20.0 |
34 | SELECT_COLOR = (0.85, 0.0, 0.0) | ||
35 | 35 | ||
36 | def __init__(self, renderer): | 36 | def __init__(self, renderer): |
37 | super(GraphArea, self).__init__() | 37 | super(GraphArea, self).__init__() |
@@ -46,11 +46,11 @@ class GraphArea(gtk.DrawingArea): | |||
46 | self.set_set_scroll_adjustments_signal('set-scroll-adjustments') | 46 | self.set_set_scroll_adjustments_signal('set-scroll-adjustments') |
47 | 47 | ||
48 | self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK | | 48 | self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK | |
49 | gtk.gdk.BUTTON_RELEASE_MASK) | 49 | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.EXPOSURE_MASK) |
50 | 50 | ||
51 | self.left_button_start_coor = None | 51 | self.band_rect = None |
52 | self.select_rect = None | ||
53 | self.ctrl_clicked = False | 52 | self.ctrl_clicked = False |
53 | self.dirtied_regions = [] | ||
54 | 54 | ||
55 | self.connect('expose-event', self.expose) | 55 | self.connect('expose-event', self.expose) |
56 | self.connect('size-allocate', self.size_allocate) | 56 | self.connect('size-allocate', self.size_allocate) |
@@ -59,23 +59,34 @@ class GraphArea(gtk.DrawingArea): | |||
59 | self.connect('button-release-event', self.button_release) | 59 | self.connect('button-release-event', self.button_release) |
60 | self.connect('motion-notify-event', self.motion_notify) | 60 | self.connect('motion-notify-event', self.motion_notify) |
61 | 61 | ||
62 | def expose(self, widget, event, data=None): | 62 | def expose(self, widget, expose_event, data=None): |
63 | ctx = widget.window.cairo_create() | 63 | ctx = widget.window.cairo_create() |
64 | graph = self.renderer.get_graph() | 64 | graph = self.renderer.get_graph() |
65 | graph.update_view(self.cur_x, self.cur_y, self.width, self.height, ctx) | 65 | graph.update_view(self.cur_x, self.cur_y, self.width, self.height, ctx) |
66 | graph.render_surface(self.renderer.get_schedule(), 0, 0, self.width, self.height) | ||
67 | 66 | ||
68 | # render dragging rectangle, if there is one | 67 | # We ourselves didn't update dirtied_regions, so this means that X or the |
69 | if self.select_rect is not None: | 68 | # window manager must have caused the expose event. So just update the |
70 | x, y, width, height = self.select_rect | 69 | # expose_event's bounding area. |
71 | thickness = GraphArea.SELECT_THICKNESS | 70 | if not self.dirtied_regions or expose_event.send_event: |
72 | color = GraphArea.SELECT_COLOR | 71 | print 'forced expose' |
72 | self.dirtied_regions = [(expose_event.area.x, expose_event.area.y, | ||
73 | expose_event.area.width, expose_event.area.height)] | ||
74 | |||
75 | graph.render_surface(self.renderer.get_schedule(), self.dirtied_regions) | ||
76 | |||
77 | # render dragging band rectangle, if there is one | ||
78 | if self.band_rect is not None: | ||
79 | x, y, width, height = self.band_rect | ||
80 | thickness = GraphFormat.BAND_THICKNESS | ||
81 | color = GraphFormat.BAND_COLOR | ||
73 | 82 | ||
74 | ctx.rectangle(x, y, width, height) | 83 | ctx.rectangle(x, y, width, height) |
75 | ctx.set_line_width(thickness) | 84 | ctx.set_line_width(thickness) |
76 | ctx.set_source_rgb(color[0], color[1], color[2]) | 85 | ctx.set_source_rgb(color[0], color[1], color[2]) |
77 | ctx.stroke() | 86 | ctx.stroke() |
78 | 87 | ||
88 | self.dirtied_regions = [] | ||
89 | |||
79 | def get_renderer(self): | 90 | def get_renderer(self): |
80 | return self.renderer | 91 | return self.renderer |
81 | 92 | ||
@@ -100,15 +111,13 @@ class GraphArea(gtk.DrawingArea): | |||
100 | self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width()) | 111 | self.cur_x = min(adjustment.value, self.renderer.get_graph().get_width()) |
101 | self.cur_x = max(adjustment.value, 0.0) | 112 | self.cur_x = max(adjustment.value, 0.0) |
102 | 113 | ||
103 | rect = gtk.gdk.Rectangle(0, 0, self.width, self.height) | 114 | self._dirty(0, 0, self.width, self.height) |
104 | self.window.invalidate_rect(rect, True) | ||
105 | 115 | ||
106 | def vertical_value_changed(self, adjustment): | 116 | def vertical_value_changed(self, adjustment): |
107 | self.cur_y = min(adjustment.value, self.renderer.get_graph().get_height()) | 117 | self.cur_y = min(adjustment.value, self.renderer.get_graph().get_height()) |
108 | self.cur_y = max(adjustment.value, 0.0) | 118 | self.cur_y = max(adjustment.value, 0.0) |
109 | 119 | ||
110 | rect = gtk.gdk.Rectangle(0, 0, self.width, self.height) | 120 | self._dirty(0, 0, self.width, self.height) |
111 | self.window.invalidate_rect(rect, True) | ||
112 | 121 | ||
113 | def size_allocate(self, widget, allocation): | 122 | def size_allocate(self, widget, allocation): |
114 | self.width = allocation.width | 123 | self.width = allocation.width |
@@ -133,13 +142,25 @@ class GraphArea(gtk.DrawingArea): | |||
133 | if event.get_layer() > max_layer: | 142 | if event.get_layer() > max_layer: |
134 | max_layer = event.get_layer() | 143 | max_layer = event.get_layer() |
135 | return max_layer | 144 | return max_layer |
136 | 145 | ||
146 | def _update_event_changes(self, list1, list2): | ||
147 | # if an event changed selected status, update the bounding area | ||
148 | for event in list1: | ||
149 | if event not in list2: | ||
150 | x, y, width, height = list1[event].get_dimensions() | ||
151 | self._dirty_inflate(x - self.cur_x, y - self.cur_y, width, height, GraphFormat.BORDER_THICKNESS) | ||
152 | for event in list2: | ||
153 | if event not in list1: | ||
154 | x, y, width, height = list2[event].get_dimensions() | ||
155 | self._dirty_inflate(x - self.cur_x, y - self.cur_y, width, height, GraphFormat.BORDER_THICKNESS) | ||
156 | |||
137 | def motion_notify(self, widget, motion_event, data=None): | 157 | def motion_notify(self, widget, motion_event, data=None): |
138 | msg = None | 158 | msg = None |
139 | 159 | ||
140 | graph = self.renderer.get_graph() | 160 | graph = self.renderer.get_graph() |
141 | 161 | ||
142 | graph.render_surface(self.renderer.get_schedule(), motion_event.x, motion_event.y, 0, 0, True) | 162 | graph.render_surface(self.renderer.get_schedule(), [(motion_event.x, motion_event.y, |
163 | 0, 0)], True) | ||
143 | just_selected = graph.get_selected_regions(motion_event.x, motion_event.y, 0, 0) | 164 | just_selected = graph.get_selected_regions(motion_event.x, motion_event.y, 0, 0) |
144 | if not just_selected: | 165 | if not just_selected: |
145 | msg = '' | 166 | msg = '' |
@@ -156,25 +177,31 @@ class GraphArea(gtk.DrawingArea): | |||
156 | 177 | ||
157 | self.emit('update-event-description', the_event, msg) | 178 | self.emit('update-event-description', the_event, msg) |
158 | 179 | ||
159 | if self.left_button_start_coor is not None: | 180 | if self.band_rect is not None: |
160 | selected = {} | 181 | selected = {} |
182 | was_selected = self.renderer.get_schedule().get_selected() | ||
161 | if self.ctrl_clicked: | 183 | if self.ctrl_clicked: |
162 | selected = dict(self.last_selected) | 184 | selected = copy.copy(was_selected) |
163 | 185 | ||
164 | # dragging a rectangle | 186 | # dragging a rectangle |
165 | x = min(self.left_button_start_coor[0], motion_event.x) | 187 | x = self.band_rect[0] |
166 | y = min(self.left_button_start_coor[1], motion_event.y) | 188 | y = self.band_rect[1] |
167 | width = abs(self.left_button_start_coor[0] - motion_event.x) | 189 | width = motion_event.x - self.band_rect[0] |
168 | height = abs(self.left_button_start_coor[1] - motion_event.y) | 190 | height = motion_event.y - self.band_rect[1] |
169 | 191 | ||
170 | graph.render_surface(self.renderer.get_schedule(), x, y, width, height, True) | 192 | x_p, y_p, width_p, height_p = self._positivify(x, y, width, height) |
171 | selected.update(graph.get_selected_regions(x, y, width, height)) | 193 | graph.render_surface(self.renderer.get_schedule(), [(x_p, y_p, width_p, height_p)], True) |
194 | selected.update(graph.get_selected_regions(x_p, y_p, width_p, height_p)) | ||
172 | self.renderer.get_schedule().set_selected(selected) | 195 | self.renderer.get_schedule().set_selected(selected) |
173 | 196 | ||
174 | self.select_rect = (x, y, width, height) | 197 | old_x, old_y, old_width, old_height = self.band_rect |
198 | self.band_rect = (x, y, width, height) | ||
175 | 199 | ||
176 | self._dirty() | 200 | self._dirty_rect_border(old_x, old_y, old_width, old_height, GraphFormat.BAND_THICKNESS) |
201 | self._dirty_rect_border(x, y, width, height, GraphFormat.BAND_THICKNESS) | ||
177 | 202 | ||
203 | self._update_event_changes(was_selected, selected) | ||
204 | |||
178 | def button_press(self, widget, button_event, data=None): | 205 | def button_press(self, widget, button_event, data=None): |
179 | graph = self.renderer.get_graph() | 206 | graph = self.renderer.get_graph() |
180 | 207 | ||
@@ -183,15 +210,16 @@ class GraphArea(gtk.DrawingArea): | |||
183 | if button_event.button == 1: | 210 | if button_event.button == 1: |
184 | self.left_button_start_coor = (button_event.x, button_event.y) | 211 | self.left_button_start_coor = (button_event.x, button_event.y) |
185 | graph.render_surface(self.renderer.get_schedule(), \ | 212 | graph.render_surface(self.renderer.get_schedule(), \ |
186 | button_event.x, button_event.y, 0, 0, True) | 213 | [(button_event.x, button_event.y, 0, 0)], True) |
187 | 214 | ||
188 | just_selected = graph.get_selected_regions(button_event.x, button_event.y, 0, 0) | 215 | just_selected = graph.get_selected_regions(button_event.x, button_event.y, 0, 0) |
189 | 216 | ||
190 | max_layer = self._find_max_layer(just_selected) | 217 | max_layer = self._find_max_layer(just_selected) |
191 | 218 | ||
192 | new_now_selected = None | 219 | new_now_selected = None |
220 | was_selected = self.renderer.get_schedule().get_selected() | ||
193 | if self.ctrl_clicked: | 221 | if self.ctrl_clicked: |
194 | new_now_selected = self.renderer.get_schedule().get_selected() | 222 | new_now_selected = copy.copy(was_selected) |
195 | else: | 223 | else: |
196 | new_now_selected = {} | 224 | new_now_selected = {} |
197 | 225 | ||
@@ -206,45 +234,75 @@ class GraphArea(gtk.DrawingArea): | |||
206 | if (len(now_selected) == 1 or self.ctrl_clicked) and event in now_selected: | 234 | if (len(now_selected) == 1 or self.ctrl_clicked) and event in now_selected: |
207 | val = not event.is_selected | 235 | val = not event.is_selected |
208 | if val: | 236 | if val: |
209 | new_now_selected[event] = None | 237 | new_now_selected[event] = graph.get_sel_region(event) |
210 | elif event in new_now_selected: | 238 | elif event in new_now_selected: |
211 | del new_now_selected[event] | 239 | del new_now_selected[event] |
212 | break # only pick one event when just clicking | 240 | break # only pick one event when just clicking |
213 | 241 | ||
214 | self.renderer.get_schedule().set_selected(new_now_selected) | 242 | self.renderer.get_schedule().set_selected(new_now_selected) |
215 | self.last_selected = dict(new_now_selected) | 243 | #self.last_selected = new_now_selected |
216 | 244 | ||
217 | self._dirty() | 245 | self._update_event_changes(new_now_selected, was_selected) |
246 | |||
247 | if self.band_rect is None: | ||
248 | self.band_rect = (button_event.x, button_event.y, 0, 0) | ||
249 | |||
218 | elif button_event.button == 3: | 250 | elif button_event.button == 3: |
219 | self._release_band() | 251 | self._release_band() |
220 | self.emit('request-context-menu', button_event, self.renderer.get_schedule().get_selected()) | 252 | self.emit('request-context-menu', button_event, self.renderer.get_schedule().get_selected()) |
221 | 253 | ||
222 | def button_release(self, widget, button_event, data=None): | 254 | def button_release(self, widget, button_event, data=None): |
223 | self.ctrl_clicked = False | 255 | self.ctrl_clicked = False |
224 | self.last_selected = dict(self.renderer.get_schedule().get_selected()) | 256 | #self.last_selected = copy.copy(self.renderer.get_schedule().get_selected()) |
225 | 257 | ||
226 | if button_event.button == 1: | 258 | if button_event.button == 1: |
227 | self._release_band() | 259 | self._release_band() |
228 | 260 | ||
229 | def _release_band(self): | 261 | def _release_band(self): |
230 | old_select_rect = self.select_rect | 262 | if self.band_rect is not None: |
231 | self.left_button_start_coor = None | 263 | x, y, width, height = self.band_rect |
232 | self.select_rect = None | 264 | self._dirty_rect_border(x, y, width, height, GraphFormat.BAND_THICKNESS) |
233 | if old_select_rect is not None: | 265 | self.band_rect = None |
234 | self._dirty() | ||
235 | 266 | ||
236 | def _dirty(self, x=None, y=None, width=None, height=None): | 267 | def _dirty(self, x, y, width, height): |
237 | if x is None: | 268 | x = max(int(math.floor(x)), 0) |
238 | x = 0 | 269 | y = max(int(math.floor(y)), 0) |
239 | if y is None: | 270 | width = min(int(math.ceil(width)), self.width) |
240 | y = 0 | 271 | height = min(int(math.ceil(height)), self.height) |
241 | if width is None: | 272 | |
242 | width = self.width | 273 | self.dirtied_regions.append((x, y, width, height)) |
243 | if height is None: | 274 | |
244 | height = self.height | ||
245 | rect = gtk.gdk.Rectangle(x, y, width, height) | 275 | rect = gtk.gdk.Rectangle(x, y, width, height) |
246 | self.window.invalidate_rect(rect, True) | 276 | self.window.invalidate_rect(rect, True) |
247 | 277 | ||
278 | def _dirty_inflate(self, x, y, width, height, thickness): | ||
279 | t = thickness * GraphArea.REFRESH_INFLATION_FACTOR | ||
280 | x -= t / 2.0 | ||
281 | y -= t / 2.0 | ||
282 | width += t | ||
283 | height += t | ||
284 | self._dirty(x, y, width, height) | ||
285 | |||
286 | def _dirty_rect_border(self, x, y, width, height, thickness): | ||
287 | # support rectangles with negative width and height (i.e. -width = width, but going leftwards | ||
288 | # instead of rightwards) | ||
289 | x, y, width, height = self._positivify(x, y, width, height) | ||
290 | |||
291 | self._dirty_inflate(x, y, width, 0, thickness) | ||
292 | self._dirty_inflate(x, y, 0, height, thickness) | ||
293 | self._dirty_inflate(x, y + height, width, 0, thickness) | ||
294 | self._dirty_inflate(x + width, y, 0, height, thickness) | ||
295 | |||
296 | def _positivify(self, x, y, width, height): | ||
297 | if width < 0: | ||
298 | x += width | ||
299 | width = -width | ||
300 | if height < 0: | ||
301 | y += height | ||
302 | height = -height | ||
303 | |||
304 | return x, y, width, height | ||
305 | |||
248 | class GraphWindow(gtk.ScrolledWindow): | 306 | class GraphWindow(gtk.ScrolledWindow): |
249 | def __init__(self, renderer): | 307 | def __init__(self, renderer): |
250 | super(GraphWindow, self).__init__(None, None) | 308 | super(GraphWindow, self).__init__(None, None) |
@@ -310,20 +368,13 @@ class MainWindow(gtk.Window): | |||
310 | WINDOW_WIDTH_REQ = 500 | 368 | WINDOW_WIDTH_REQ = 500 |
311 | WINDOW_HEIGHT_REQ = 300 | 369 | WINDOW_HEIGHT_REQ = 300 |
312 | 370 | ||
313 | def __init__(self, request_renderer_change): | 371 | def __init__(self): |
314 | super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL) | 372 | super(MainWindow, self).__init__(gtk.WINDOW_TOPLEVEL) |
315 | 373 | ||
316 | self.connect('delete_event', self.delete_event) | 374 | self.connect('delete_event', self.delete_event) |
317 | self.connect('destroy', self.die) | 375 | self.connect('destroy', self.die) |
318 | |||
319 | self.connect('request-renderer-change', request_renderer_change) | ||
320 | 376 | ||
321 | self.file_menu = gtk.Menu() | 377 | self.file_menu = gtk.Menu() |
322 | self.open_item = gtk.MenuItem('_Open', True) | ||
323 | self.open_item.connect('activate', self.open_item_activate) | ||
324 | self.file_menu.append(self.open_item) | ||
325 | self.open_item.show() | ||
326 | |||
327 | self.quit_item = gtk.MenuItem('_Quit', True) | 378 | self.quit_item = gtk.MenuItem('_Quit', True) |
328 | self.quit_item.connect('activate', self.quit_item_activate) | 379 | self.quit_item.connect('activate', self.quit_item_activate) |
329 | self.quit_item.show() | 380 | self.quit_item.show() |
@@ -397,21 +448,6 @@ class MainWindow(gtk.Window): | |||
397 | menu = GraphContextMenu(selected) | 448 | menu = GraphContextMenu(selected) |
398 | menu.popup(None, None, None, button, time) | 449 | menu.popup(None, None, None, button, time) |
399 | 450 | ||
400 | def open_item_activate(self, widget): | ||
401 | dialog = gtk.FileChooserDialog('Open File', self, | ||
402 | gtk.FILE_CHOOSER_ACTION_OPEN, | ||
403 | (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, | ||
404 | gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT)) | ||
405 | dialog.set_select_multiple(True) | ||
406 | |||
407 | if dialog.run() == gtk.RESPONSE_ACCEPT: | ||
408 | filenames = dialog.get_filenames() | ||
409 | dialog.destroy() | ||
410 | self.emit('request-renderer-change', filenames, None) | ||
411 | else: | ||
412 | dialog.destroy() | ||
413 | |||
414 | |||
415 | def quit_item_activate(self, widget): | 451 | def quit_item_activate(self, widget): |
416 | self.destroy() | 452 | self.destroy() |
417 | 453 | ||