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.
25 from Constants import *
32 from threading import Thread
36 from grc.gui.elements.Platform import Platform
40 The action handler will setup all the major window components,
41 and handle button presses and flow graph operations from the GUI.
44 def __init__(self, file_paths, platform):
46 ActionHandler constructor.
47 Create the main window, setup the message handler, import the preferences,
48 and connect all of the action handlers. Finally, enter the gtk main loop and block.
49 @param file_paths a list of flow graph file passed from command line
50 @param platform platform module
53 platform = Platform(platform)
54 if PY_GTK_ICON: gtk.window_set_default_icon_from_file(PY_GTK_ICON)
55 for action in ACTIONS_LIST: action.connect('activate', self._handle_actions)
56 #setup the main window
57 self.main_window = gui.MainWindow(self.handle_states, platform)
58 self.main_window.connect('delete_event', self._quit)
59 self.main_window.connect('key_press_event', self._handle_key_press)
60 self.get_page = self.main_window.get_page
61 self.get_flow_graph = self.main_window.get_flow_graph
62 self.get_focus_flag = self.main_window.drawing_area.get_focus_flag
64 Messages.register_messenger(self.main_window.add_report_line)
67 self.init_file_paths = file_paths
68 self.handle_states(APPLICATION_INITIALIZE)
70 gtk.gdk.threads_init()
73 def _handle_key_press(self, widget, event):
75 Handle key presses from the keyboard.
76 Translate key combos into actions.
77 Key combinations that do not include special keys, such as ctrl or Fcn*,
78 Also, require that the flow graph has mouse focus when choosing to handle keys.
79 @return true if the flow graph is in active use
81 keyname = gtk.gdk.keyval_name(event.keyval)
82 ctrl = event.state & gtk.gdk.CONTROL_MASK
83 alt = event.state & gtk.gdk.MOD1_MASK
84 shift = event.state & gtk.gdk.SHIFT_MASK
85 #################### save/open/new/close ###############################
86 if ctrl and keyname == 's':
87 self.handle_states(FLOW_GRAPH_SAVE)
88 elif ctrl and keyname == 'o':
89 self.handle_states(FLOW_GRAPH_OPEN)
90 elif ctrl and keyname == 'n':
91 self.handle_states(FLOW_GRAPH_NEW)
92 elif ctrl and keyname == 'q':
93 self.handle_states(FLOW_GRAPH_CLOSE)
94 #################### Cut/Copy/Paste ###############################
95 elif self.get_focus_flag() and ctrl and keyname == 'x': #mouse focus
96 self.handle_states(BLOCK_CUT)
97 elif self.get_focus_flag() and ctrl and keyname == 'c': #mouse focus
98 self.handle_states(BLOCK_COPY)
99 elif self.get_focus_flag() and ctrl and keyname == 'v': #mouse focus
100 self.handle_states(BLOCK_PASTE)
101 #################### Undo/Redo ###############################
102 elif ctrl and keyname == 'z':
103 self.handle_states(FLOW_GRAPH_UNDO)
104 elif ctrl and keyname == 'y':
105 self.handle_states(FLOW_GRAPH_REDO)
106 #################### Delete ###############################
107 elif self.get_focus_flag() and keyname == 'Delete': #mouse focus
108 self.handle_states(ELEMENT_DELETE)
109 #################### Params ###############################
110 elif self.get_focus_flag() and keyname == 'Return': #mouse focus
111 self.handle_states(BLOCK_PARAM_MODIFY)
112 #################### Rotate ###############################
113 elif self.get_focus_flag() and keyname == 'Right': #mouse focus
114 self.handle_states(BLOCK_ROTATE_RIGHT)
115 elif self.get_focus_flag() and keyname == 'Left': #mouse focus
116 self.handle_states(BLOCK_ROTATE_LEFT)
117 #################### Enable/Disable ###############################
118 elif self.get_focus_flag() and keyname == 'e': #mouse focus
119 self.handle_states(BLOCK_ENABLE)
120 elif self.get_focus_flag() and keyname == 'd': #mouse focus
121 self.handle_states(BLOCK_DISABLE)
122 #################### Data Type ###############################
123 elif self.get_focus_flag() and keyname == 'Down': #mouse focus
124 self.handle_states(BLOCK_INC_TYPE)
125 elif self.get_focus_flag() and keyname == 'Up': #mouse focus
126 self.handle_states(BLOCK_DEC_TYPE)
127 #################### Port Controllers ###############################
128 elif self.get_focus_flag() and keyname in ('equal','plus', 'KP_Add'): #mouse focus
129 self.handle_states(PORT_CONTROLLER_INC)
130 elif self.get_focus_flag() and keyname in ('minus', 'KP_Subtract'): #mouse focus
131 self.handle_states(PORT_CONTROLLER_DEC)
132 #################### Gen/Exec/Stop/Print ###############################
133 elif keyname == 'F5':
134 self.handle_states(FLOW_GRAPH_GEN)
135 elif keyname == 'F6':
136 self.handle_states(FLOW_GRAPH_EXEC)
137 elif keyname == 'F7':
138 self.handle_states(FLOW_GRAPH_KILL)
139 elif keyname == 'Print':
140 self.handle_states(FLOW_GRAPH_SCREEN_CAPTURE)
141 #propagate this if the fg is not in focus or nothing is selected
142 return self.get_focus_flag() and self.get_flow_graph().is_selected()
144 def _quit(self, window, event):
146 Handle the delete event from the main window.
147 Generated by pressing X to close, alt+f4, or right click+close.
148 This method in turns calls the state handler to quit.
151 self.handle_states(APPLICATION_QUIT)
154 def _handle_actions(self, event):
156 Handle all of the activate signals from the gtk actions.
157 The action signals derive from clicking on a toolbar or menu bar button.
158 Forward the action to the state handler.
160 self.handle_states(event.get_name())
162 def handle_states(self, state=''):
164 Handle the state changes in the GUI.
165 Handle all of the state changes that arise from the action handler or other gui and
166 inputs in the application. The state passed to the handle_states method is a string descriping
167 the change. A series of if/elif statements handle the state by greying out action buttons, causing
168 changes in the flow graph, saving/opening files... The handle_states method is passed to the
169 contructors of many of the classes used in this application enabling them to report any state change.
170 @param state a string describing the state change
173 ##############################################################################################
175 ##############################################################################################
176 if state == APPLICATION_INITIALIZE:
177 for action in ACTIONS_LIST: action.set_sensitive(False) #set all actions disabled
178 # enable a select few actions
180 APPLICATION_QUIT, FLOW_GRAPH_NEW, FLOW_GRAPH_OPEN, FLOW_GRAPH_SAVE_AS, FLOW_GRAPH_CLOSE,
181 ABOUT_WINDOW_DISPLAY, HOTKEYS_WINDOW_DISPLAY,
182 PREFS_WINDOW_DISPLAY, FLOW_GRAPH_SCREEN_CAPTURE,
183 ): get_action_from_name(action).set_sensitive(True)
184 if not self.init_file_paths and Preferences.restore_files(): self.init_file_paths = Preferences.files_open()
185 if not self.init_file_paths: self.init_file_paths = ['']
186 for file_path in self.init_file_paths:
187 if file_path: self.main_window.new_page(file_path) #load pages from file paths
188 if Preferences.file_open() in self.init_file_paths: self.main_window.new_page(Preferences.file_open(), show=True)
189 if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
190 elif state == APPLICATION_QUIT:
191 if self.main_window.close_pages():
194 ##############################################################################################
196 ##############################################################################################
197 elif state == ELEMENT_SELECT:
198 self.get_flow_graph().update()
199 elif state == NOTHING_SELECT:
200 self.get_flow_graph().unselect()
201 self.get_flow_graph().update()
202 ##############################################################################################
204 ##############################################################################################
205 elif state == BLOCK_ENABLE:
206 if self.get_flow_graph().enable_selected(True):
207 self.get_flow_graph().update()
208 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
209 self.get_page().set_saved(False)
210 elif state == BLOCK_DISABLE:
211 if self.get_flow_graph().enable_selected(False):
212 self.get_flow_graph().update()
213 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
214 self.get_page().set_saved(False)
215 ##############################################################################################
217 ##############################################################################################
218 elif state == BLOCK_CUT:
219 self.handle_states(BLOCK_COPY)
220 self.handle_states(ELEMENT_DELETE)
221 elif state == BLOCK_COPY:
222 self.clipboard = self.get_flow_graph().copy_to_clipboard()
223 elif state == BLOCK_PASTE:
225 self.get_flow_graph().paste_from_clipboard(self.clipboard)
226 self.get_flow_graph().update()
227 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
228 self.get_page().set_saved(False)
229 ##############################################################################################
230 # Move/Rotate/Delete/Create
231 ##############################################################################################
232 elif state == BLOCK_MOVE:
233 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
234 self.get_page().set_saved(False)
235 elif state == BLOCK_ROTATE_LEFT:
236 if self.get_flow_graph().rotate_selected(DIR_LEFT):
237 self.get_flow_graph().update()
238 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
239 self.get_page().set_saved(False)
240 elif state == BLOCK_ROTATE_RIGHT:
241 if self.get_flow_graph().rotate_selected(DIR_RIGHT):
242 self.get_flow_graph().update()
243 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
244 self.get_page().set_saved(False)
245 elif state == ELEMENT_DELETE:
246 if self.get_flow_graph().remove_selected():
247 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
248 self.handle_states(NOTHING_SELECT)
249 self.get_page().set_saved(False)
250 elif state == ELEMENT_CREATE:
251 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
252 self.handle_states(NOTHING_SELECT)
253 self.get_page().set_saved(False)
254 elif state == BLOCK_INC_TYPE:
255 if self.get_flow_graph().type_controller_modify_selected(1):
256 self.get_flow_graph().update()
257 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
258 self.get_page().set_saved(False)
259 elif state == BLOCK_DEC_TYPE:
260 if self.get_flow_graph().type_controller_modify_selected(-1):
261 self.get_flow_graph().update()
262 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
263 self.get_page().set_saved(False)
264 elif state == PORT_CONTROLLER_INC:
265 if self.get_flow_graph().port_controller_modify_selected(1):
266 self.get_flow_graph().update()
267 self.get_flow_graph().update() #2 times
268 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
269 self.get_page().set_saved(False)
270 elif state == PORT_CONTROLLER_DEC:
271 if self.get_flow_graph().port_controller_modify_selected(-1):
272 self.get_flow_graph().update()
273 self.get_flow_graph().update() #2 times
274 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
275 self.get_page().set_saved(False)
276 ##############################################################################################
278 ##############################################################################################
279 elif state == PREFS_WINDOW_DISPLAY:
280 gui.PreferencesDialog()
281 self.get_flow_graph().update()
282 elif state == ABOUT_WINDOW_DISPLAY:
284 elif state == HOTKEYS_WINDOW_DISPLAY:
286 ##############################################################################################
287 # Param Modifications
288 ##############################################################################################
289 elif state == BLOCK_PARAM_MODIFY:
290 if self.get_flow_graph().param_modify_selected():
291 self.get_flow_graph().update()
292 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
293 self.get_page().set_saved(False)
294 ##############################################################################################
296 ##############################################################################################
297 elif state == FLOW_GRAPH_UNDO:
298 n = self.get_page().get_state_cache().get_prev_state()
300 self.get_flow_graph().unselect()
301 self.get_flow_graph().import_data(n)
302 self.get_flow_graph().update()
303 self.get_page().set_saved(False)
304 elif state == FLOW_GRAPH_REDO:
305 n = self.get_page().get_state_cache().get_next_state()
307 self.get_flow_graph().unselect()
308 self.get_flow_graph().import_data(n)
309 self.get_flow_graph().update()
310 self.get_page().set_saved(False)
311 ##############################################################################################
312 # New/Open/Save/Close
313 ##############################################################################################
314 elif state == FLOW_GRAPH_NEW:
315 self.main_window.new_page()
316 elif state == FLOW_GRAPH_OPEN:
317 file_paths = gui.OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
318 if file_paths: #open a new page for each file, show only the first
319 for i,file_path in enumerate(file_paths):
320 self.main_window.new_page(file_path, show=(i==0))
321 elif state == FLOW_GRAPH_CLOSE:
322 self.main_window.close_page()
323 elif state == FLOW_GRAPH_SAVE:
324 if not self.get_page().get_file_path(): self.handle_states(FLOW_GRAPH_SAVE_AS)
327 ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
328 self.get_page().set_saved(True)
330 Messages.send_fail_save(self.get_page().get_file_path())
331 self.get_page().set_saved(False)
332 elif state == FLOW_GRAPH_SAVE_AS:
333 file_path = gui.SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
334 if file_path != None:
335 self.get_page().set_file_path(file_path)
336 self.handle_states(FLOW_GRAPH_SAVE)
337 elif state == FLOW_GRAPH_SCREEN_CAPTURE:
338 file_path = gui.SaveImageFileDialog(self.get_page().get_file_path()).run()
339 if file_path != None:
340 pixmap = self.get_flow_graph().get_drawing_area().pixmap
341 width, height = pixmap.get_size()
342 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height)
343 pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height)
344 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
345 ##############################################################################################
347 ##############################################################################################
348 elif state == FLOW_GRAPH_GEN:
349 if not self.get_page().get_pid():
350 if not self.get_page().get_saved() or not self.get_page().get_file_path():
351 self.handle_states(FLOW_GRAPH_SAVE) #only save if file path missing or not saved
352 if self.get_page().get_saved() and self.get_page().get_file_path():
353 generator = self.get_page().get_generator()
355 Messages.send_start_gen(generator.get_file_path())
357 except Exception,e: Messages.send_fail_gen(e)
358 else: self.generator = None
359 elif state == FLOW_GRAPH_EXEC:
360 if not self.get_page().get_pid():
361 self.handle_states(FLOW_GRAPH_GEN)
362 if self.get_page().get_saved() and self.get_page().get_file_path():
363 ExecFlowGraphThread(self)
364 elif state == FLOW_GRAPH_KILL:
365 if self.get_page().get_pid():
366 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
367 except: print "could not kill pid: %s"%self.get_page().get_pid()
368 elif state == '': #pass and run the global actions
370 else: print '!!! State "%s" not handled !!!'%state
371 ##############################################################################################
372 # Global Actions for all States
373 ##############################################################################################
374 #update general buttons
375 get_action_from_name(ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
376 get_action_from_name(BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
377 get_action_from_name(BLOCK_ROTATE_RIGHT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
378 get_action_from_name(BLOCK_ROTATE_LEFT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
379 #update cut/copy/paste
380 get_action_from_name(BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
381 get_action_from_name(BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
382 get_action_from_name(BLOCK_PASTE).set_sensitive(bool(self.clipboard))
383 #update enable/disable
384 get_action_from_name(BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
385 get_action_from_name(BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
386 #set the exec and stop buttons
387 self.update_exec_stop()
389 get_action_from_name(FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
390 self.main_window.update()
392 self.get_flow_graph().draw()
394 def update_exec_stop(self):
396 Update the exec and stop buttons.
397 Lock and unlock the mutex for race conditions with exec flow graph threads.
399 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
400 get_action_from_name(FLOW_GRAPH_GEN).set_sensitive(sensitive)
401 get_action_from_name(FLOW_GRAPH_EXEC).set_sensitive(sensitive)
402 get_action_from_name(FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
404 class ExecFlowGraphThread(Thread):
405 """Execute the flow graph as a new process and wait on it to finish."""
406 def __init__ (self, action_handler):
408 ExecFlowGraphThread constructor.
409 @param action_handler an instance of an ActionHandler
411 Thread.__init__(self)
412 self.update_exec_stop = action_handler.update_exec_stop
413 self.flow_graph = action_handler.get_flow_graph()
414 #store page and dont use main window calls in run
415 self.page = action_handler.get_page()
416 Messages.send_start_exec(self.page.get_generator().get_file_path())
419 self.p = self.page.get_generator().get_popen()
420 self.page.set_pid(self.p.pid)
422 self.update_exec_stop()
425 Messages.send_verbose_exec(str(e))
426 Messages.send_end_exec()
429 """Wait on the flow graph."""
433 gtk.gdk.threads_enter()
434 Messages.send_verbose_exec(r)
435 gtk.gdk.threads_leave()
436 r = os.read(self.p.stdout.fileno(), 1024)
437 gtk.gdk.threads_enter()
438 Messages.send_end_exec()
439 self.page.set_pid(None)
440 self.update_exec_stop()
441 gtk.gdk.threads_leave()