diff options
Diffstat (limited to 'viz/viewer.py')
-rw-r--r-- | viz/viewer.py | 253 |
1 files changed, 212 insertions, 41 deletions
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 | ||