2 Copyright 2007 Free Software Foundation, Inc.
3 This file is part of GNU Radio
5 GNU Radio Companion is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 GNU Radio Companion is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 ##@package ActionHandler
20 #ActionHandler builds the interface and handles most of the user inputs.
24 from Constants import *
31 from threading import Thread
35 from grc.gui.elements.Platform import Platform
39 The action handler will setup all the major window components,
40 and handle button presses and flow graph operations from the GUI.
43 def __init__(self, file_paths, platform):
45 ActionHandler constructor.
46 Create the main window, setup the message handler, import the preferences,
47 and connect all of the action handlers. Finally, enter the gtk main loop and block.
48 @param file_paths a list of flow graph file passed from command line
49 @param platform platform module
52 platform = Platform(platform)
53 if PY_GTK_ICON: gtk.window_set_default_icon_from_file(PY_GTK_ICON)
54 for action in ACTIONS_LIST: action.connect('activate', self._handle_actions)
55 #setup the main window
56 self.main_window = gui.MainWindow(self.handle_states, platform)
57 self.main_window.connect('delete_event', self._quit)
58 self.main_window.connect('key_press_event', self._handle_key_press)
59 self.get_page = self.main_window.get_page
60 self.get_flow_graph = self.main_window.get_flow_graph
61 self.get_focus_flag = self.main_window.drawing_area.get_focus_flag
63 Messages.register_messenger(self.main_window.add_report_line)
66 self.init_file_paths = file_paths
67 self.handle_states(APPLICATION_INITIALIZE)
69 gtk.gdk.threads_init()
72 def _handle_key_press(self, widget, event):
74 Handle key presses from the keyboard.
75 Translate key combos into actions.
76 Key combinations that do not include special keys, such as ctrl or Fcn*,
77 Also, require that the flow graph has mouse focus when choosing to handle keys.
78 @return true if the flow graph is in active use
80 keyname = gtk.gdk.keyval_name(event.keyval)
81 ctrl = event.state & gtk.gdk.CONTROL_MASK
82 alt = event.state & gtk.gdk.MOD1_MASK
83 shift = event.state & gtk.gdk.SHIFT_MASK
84 #################### save/open/new/close ###############################
85 if ctrl and keyname == 's':
86 self.handle_states(FLOW_GRAPH_SAVE)
87 elif ctrl and keyname == 'o':
88 self.handle_states(FLOW_GRAPH_OPEN)
89 elif ctrl and keyname == 'n':
90 self.handle_states(FLOW_GRAPH_NEW)
91 elif ctrl and keyname == 'q':
92 self.handle_states(FLOW_GRAPH_CLOSE)
93 #################### Cut/Copy/Paste ###############################
94 elif self.get_focus_flag() and ctrl and keyname == 'x': #mouse focus
95 self.handle_states(BLOCK_CUT)
96 elif self.get_focus_flag() and ctrl and keyname == 'c': #mouse focus
97 self.handle_states(BLOCK_COPY)
98 elif self.get_focus_flag() and ctrl and keyname == 'v': #mouse focus
99 self.handle_states(BLOCK_PASTE)
100 #################### Undo/Redo ###############################
101 elif ctrl and keyname == 'z':
102 self.handle_states(FLOW_GRAPH_UNDO)
103 elif ctrl and keyname == 'y':
104 self.handle_states(FLOW_GRAPH_REDO)
105 #################### Delete ###############################
106 elif self.get_focus_flag() and keyname == 'Delete': #mouse focus
107 self.handle_states(ELEMENT_DELETE)
108 #################### Params ###############################
109 elif self.get_focus_flag() and keyname == 'Return': #mouse focus
110 self.handle_states(BLOCK_PARAM_MODIFY)
111 #################### Rotate ###############################
112 elif self.get_focus_flag() and keyname == 'Right': #mouse focus
113 self.handle_states(BLOCK_ROTATE_RIGHT)
114 elif self.get_focus_flag() and keyname == 'Left': #mouse focus
115 self.handle_states(BLOCK_ROTATE_LEFT)
116 #################### Enable/Disable ###############################
117 elif self.get_focus_flag() and keyname == 'e': #mouse focus
118 self.handle_states(BLOCK_ENABLE)
119 elif self.get_focus_flag() and keyname == 'd': #mouse focus
120 self.handle_states(BLOCK_DISABLE)
121 #################### Data Type ###############################
122 elif self.get_focus_flag() and keyname == 'Down': #mouse focus
123 self.handle_states(BLOCK_INC_TYPE)
124 elif self.get_focus_flag() and keyname == 'Up': #mouse focus
125 self.handle_states(BLOCK_DEC_TYPE)
126 #################### Port Controllers ###############################
127 elif self.get_focus_flag() and keyname in ('equal','plus', 'KP_Add'): #mouse focus
128 self.handle_states(PORT_CONTROLLER_INC)
129 elif self.get_focus_flag() and keyname in ('minus', 'KP_Subtract'): #mouse focus
130 self.handle_states(PORT_CONTROLLER_DEC)
131 #################### Gen/Exec/Stop/Print ###############################
132 elif keyname == 'F5':
133 self.handle_states(FLOW_GRAPH_GEN)
134 elif keyname == 'F6':
135 self.handle_states(FLOW_GRAPH_EXEC)
136 elif keyname == 'F7':
137 self.handle_states(FLOW_GRAPH_KILL)
138 elif keyname == 'Print':
139 self.handle_states(FLOW_GRAPH_SCREEN_CAPTURE)
140 #propagate this if the fg is not in focus or nothing is selected
141 return self.get_focus_flag() and self.get_flow_graph().is_selected()
143 def _quit(self, window, event):
145 Handle the delete event from the main window.
146 Generated by pressing X to close, alt+f4, or right click+close.
147 This method in turns calls the state handler to quit.
150 self.handle_states(APPLICATION_QUIT)
153 def _handle_actions(self, event):
155 Handle all of the activate signals from the gtk actions.
156 The action signals derive from clicking on a toolbar or menu bar button.
157 Forward the action to the state handler.
159 self.handle_states(event.get_name())
161 def handle_states(self, state=''):
163 Handle the state changes in the GUI.
164 Handle all of the state changes that arise from the action handler or other gui and
165 inputs in the application. The state passed to the handle_states method is a string descriping
166 the change. A series of if/elif statements handle the state by greying out action buttons, causing
167 changes in the flow graph, saving/opening files... The handle_states method is passed to the
168 contructors of many of the classes used in this application enabling them to report any state change.
169 @param state a string describing the state change
172 ##############################################################################################
174 ##############################################################################################
175 if state == APPLICATION_INITIALIZE:
176 for action in ACTIONS_LIST: action.set_sensitive(False) #set all actions disabled
177 # enable a select few actions
179 APPLICATION_QUIT, FLOW_GRAPH_NEW, FLOW_GRAPH_OPEN, FLOW_GRAPH_SAVE_AS, FLOW_GRAPH_CLOSE,
180 ABOUT_WINDOW_DISPLAY, HOTKEYS_WINDOW_DISPLAY,
181 PREFS_WINDOW_DISPLAY, FLOW_GRAPH_SCREEN_CAPTURE,
182 ): get_action_from_name(action).set_sensitive(True)
183 if not self.init_file_paths and Preferences.restore_files(): self.init_file_paths = Preferences.files_open()
184 if not self.init_file_paths: self.init_file_paths = ['']
185 for file_path in self.init_file_paths:
186 if file_path: self.main_window.new_page(file_path) #load pages from file paths
187 if Preferences.file_open() in self.init_file_paths: self.main_window.new_page(Preferences.file_open(), show=True)
188 if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
189 elif state == APPLICATION_QUIT:
190 if self.main_window.close_pages():
193 ##############################################################################################
195 ##############################################################################################
196 elif state == ELEMENT_SELECT:
197 self.get_flow_graph().update()
198 elif state == NOTHING_SELECT:
199 self.get_flow_graph().unselect()
200 self.get_flow_graph().update()
201 ##############################################################################################
203 ##############################################################################################
204 elif state == BLOCK_ENABLE:
205 if self.get_flow_graph().enable_selected(True):
206 self.get_flow_graph().update()
207 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
208 self.get_page().set_saved(False)
209 elif state == BLOCK_DISABLE:
210 if self.get_flow_graph().enable_selected(False):
211 self.get_flow_graph().update()
212 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
213 self.get_page().set_saved(False)
214 ##############################################################################################
216 ##############################################################################################
217 elif state == BLOCK_CUT:
218 self.handle_states(BLOCK_COPY)
219 self.handle_states(ELEMENT_DELETE)
220 elif state == BLOCK_COPY:
221 self.clipboard = self.get_flow_graph().copy_to_clipboard()
222 elif state == BLOCK_PASTE:
224 self.get_flow_graph().paste_from_clipboard(self.clipboard)
225 self.get_flow_graph().update()
226 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
227 self.get_page().set_saved(False)
228 ##############################################################################################
229 # Move/Rotate/Delete/Create
230 ##############################################################################################
231 elif state == BLOCK_MOVE:
232 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
233 self.get_page().set_saved(False)
234 elif state == BLOCK_ROTATE_LEFT:
235 if self.get_flow_graph().rotate_selected(DIR_LEFT):
236 self.get_flow_graph().update()
237 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
238 self.get_page().set_saved(False)
239 elif state == BLOCK_ROTATE_RIGHT:
240 if self.get_flow_graph().rotate_selected(DIR_RIGHT):
241 self.get_flow_graph().update()
242 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
243 self.get_page().set_saved(False)
244 elif state == ELEMENT_DELETE:
245 if self.get_flow_graph().remove_selected():
246 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
247 self.handle_states(NOTHING_SELECT)
248 self.get_page().set_saved(False)
249 elif state == ELEMENT_CREATE:
250 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
251 self.handle_states(NOTHING_SELECT)
252 self.get_page().set_saved(False)
253 elif state == BLOCK_INC_TYPE:
254 if self.get_flow_graph().type_controller_modify_selected(1):
255 self.get_flow_graph().update()
256 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
257 self.get_page().set_saved(False)
258 elif state == BLOCK_DEC_TYPE:
259 if self.get_flow_graph().type_controller_modify_selected(-1):
260 self.get_flow_graph().update()
261 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
262 self.get_page().set_saved(False)
263 elif state == PORT_CONTROLLER_INC:
264 if self.get_flow_graph().port_controller_modify_selected(1):
265 self.get_flow_graph().update()
266 self.get_flow_graph().update() #2 times
267 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
268 self.get_page().set_saved(False)
269 elif state == PORT_CONTROLLER_DEC:
270 if self.get_flow_graph().port_controller_modify_selected(-1):
271 self.get_flow_graph().update()
272 self.get_flow_graph().update() #2 times
273 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
274 self.get_page().set_saved(False)
275 ##############################################################################################
277 ##############################################################################################
278 elif state == PREFS_WINDOW_DISPLAY:
279 gui.PreferencesDialog()
280 self.get_flow_graph().update()
281 elif state == ABOUT_WINDOW_DISPLAY:
283 elif state == HOTKEYS_WINDOW_DISPLAY:
285 ##############################################################################################
286 # Param Modifications
287 ##############################################################################################
288 elif state == BLOCK_PARAM_MODIFY:
289 if self.get_flow_graph().param_modify_selected():
290 self.get_flow_graph().update()
291 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
292 self.get_page().set_saved(False)
293 ##############################################################################################
295 ##############################################################################################
296 elif state == FLOW_GRAPH_UNDO:
297 n = self.get_page().get_state_cache().get_prev_state()
299 self.get_flow_graph().unselect()
300 self.get_flow_graph().import_data(n)
301 self.get_flow_graph().update()
302 self.get_page().set_saved(False)
303 elif state == FLOW_GRAPH_REDO:
304 n = self.get_page().get_state_cache().get_next_state()
306 self.get_flow_graph().unselect()
307 self.get_flow_graph().import_data(n)
308 self.get_flow_graph().update()
309 self.get_page().set_saved(False)
310 ##############################################################################################
311 # New/Open/Save/Close
312 ##############################################################################################
313 elif state == FLOW_GRAPH_NEW:
314 self.main_window.new_page()
315 elif state == FLOW_GRAPH_OPEN:
316 file_paths = gui.OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
317 if file_paths: #open a new page for each file, show only the first
318 for i,file_path in enumerate(file_paths):
319 self.main_window.new_page(file_path, show=(i==0))
320 elif state == FLOW_GRAPH_CLOSE:
321 self.main_window.close_page()
322 elif state == FLOW_GRAPH_SAVE:
323 if not self.get_page().get_file_path(): self.handle_states(FLOW_GRAPH_SAVE_AS)
326 ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
327 self.get_page().set_saved(True)
329 Messages.send_fail_save(self.get_page().get_file_path())
330 self.get_page().set_saved(False)
331 elif state == FLOW_GRAPH_SAVE_AS:
332 file_path = gui.SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
333 if file_path != None:
334 self.get_page().set_file_path(file_path)
335 self.handle_states(FLOW_GRAPH_SAVE)
336 elif state == FLOW_GRAPH_SCREEN_CAPTURE:
337 file_path = gui.SaveImageFileDialog(self.get_page().get_file_path()).run()
338 if file_path != None:
339 pixmap = self.get_flow_graph().get_drawing_area().pixmap
340 width, height = pixmap.get_size()
341 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height)
342 pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height)
343 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
344 ##############################################################################################
346 ##############################################################################################
347 elif state == FLOW_GRAPH_GEN:
348 if not self.get_page().get_pid():
349 if not self.get_page().get_saved() or not self.get_page().get_file_path():
350 self.handle_states(FLOW_GRAPH_SAVE) #only save if file path missing or not saved
351 if self.get_page().get_saved() and self.get_page().get_file_path():
352 generator = self.get_page().get_generator()
354 Messages.send_start_gen(generator.get_file_path())
356 except Exception,e: Messages.send_fail_gen(e)
357 else: self.generator = None
358 elif state == FLOW_GRAPH_EXEC:
359 if not self.get_page().get_pid():
360 self.handle_states(FLOW_GRAPH_GEN)
361 if self.get_page().get_saved() and self.get_page().get_file_path():
362 ExecFlowGraphThread(self)
363 elif state == FLOW_GRAPH_KILL:
364 if self.get_page().get_pid():
365 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
366 except: print "could not kill pid: %s"%self.get_page().get_pid()
367 elif state == '': #pass and run the global actions
369 else: print '!!! State "%s" not handled !!!'%state
370 ##############################################################################################
371 # Global Actions for all States
372 ##############################################################################################
373 #update general buttons
374 get_action_from_name(ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
375 get_action_from_name(BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
376 get_action_from_name(BLOCK_ROTATE_RIGHT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
377 get_action_from_name(BLOCK_ROTATE_LEFT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
378 #update cut/copy/paste
379 get_action_from_name(BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
380 get_action_from_name(BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
381 get_action_from_name(BLOCK_PASTE).set_sensitive(bool(self.clipboard))
382 #update enable/disable
383 get_action_from_name(BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
384 get_action_from_name(BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
385 #set the exec and stop buttons
386 self.update_exec_stop()
388 get_action_from_name(FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
389 self.main_window.update()
391 self.get_flow_graph().draw()
393 def update_exec_stop(self):
395 Update the exec and stop buttons.
396 Lock and unlock the mutex for race conditions with exec flow graph threads.
398 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
399 get_action_from_name(FLOW_GRAPH_GEN).set_sensitive(sensitive)
400 get_action_from_name(FLOW_GRAPH_EXEC).set_sensitive(sensitive)
401 get_action_from_name(FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
403 class ExecFlowGraphThread(Thread):
404 """Execute the flow graph as a new process and wait on it to finish."""
405 def __init__ (self, action_handler):
407 ExecFlowGraphThread constructor.
408 @param action_handler an instance of an ActionHandler
410 Thread.__init__(self)
411 self.update_exec_stop = action_handler.update_exec_stop
412 self.flow_graph = action_handler.get_flow_graph()
413 #store page and dont use main window calls in run
414 self.page = action_handler.get_page()
415 Messages.send_start_exec(self.page.get_generator().get_file_path())
418 self.p = self.page.get_generator().get_popen()
419 self.page.set_pid(self.p.pid)
421 self.update_exec_stop()
424 Messages.send_verbose_exec(str(e))
425 Messages.send_end_exec()
428 """Wait on the flow graph."""
432 gtk.gdk.threads_enter()
433 Messages.send_verbose_exec(r)
434 gtk.gdk.threads_leave()
435 r = os.read(self.p.stdout.fileno(), 1024)
436 gtk.gdk.threads_enter()
437 Messages.send_end_exec()
438 self.page.set_pid(None)
439 self.update_exec_stop()
440 gtk.gdk.threads_leave()