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
22 from .. platforms.base.Constants import PY_GTK_ICON, IMAGE_FILE_EXTENSION
23 from Constants import DIR_LEFT, DIR_RIGHT
29 from threading import Thread
31 from .. utils import ParseXML
33 from .. platforms.gui.Platform import Platform
34 from MainWindow import MainWindow
35 from Dialogs import PreferencesDialog, AboutDialog, HotKeysDialog
36 from FileDialogs import OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, SaveImageFileDialog
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.ACTIONS_LIST: action.connect('activate', self._handle_actions)
56 #setup the main window
57 self.main_window = 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(Actions.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(Actions.FLOW_GRAPH_SAVE)
88 elif ctrl and keyname == 'o':
89 self.handle_states(Actions.FLOW_GRAPH_OPEN)
90 elif ctrl and keyname == 'n':
91 self.handle_states(Actions.FLOW_GRAPH_NEW)
92 elif ctrl and keyname == 'q':
93 self.handle_states(Actions.FLOW_GRAPH_CLOSE)
94 #################### Cut/Copy/Paste ###############################
95 elif self.get_focus_flag() and ctrl and keyname == 'x': #mouse focus
96 self.handle_states(Actions.BLOCK_CUT)
97 elif self.get_focus_flag() and ctrl and keyname == 'c': #mouse focus
98 self.handle_states(Actions.BLOCK_COPY)
99 elif self.get_focus_flag() and ctrl and keyname == 'v': #mouse focus
100 self.handle_states(Actions.BLOCK_PASTE)
101 #################### Undo/Redo ###############################
102 elif ctrl and keyname == 'z':
103 self.handle_states(Actions.FLOW_GRAPH_UNDO)
104 elif ctrl and keyname == 'y':
105 self.handle_states(Actions.FLOW_GRAPH_REDO)
106 #################### Delete ###############################
107 elif self.get_focus_flag() and keyname == 'Delete': #mouse focus
108 self.handle_states(Actions.ELEMENT_DELETE)
109 #################### Params ###############################
110 elif self.get_focus_flag() and keyname == 'Return': #mouse focus
111 self.handle_states(Actions.BLOCK_PARAM_MODIFY)
112 #################### Rotate ###############################
113 elif self.get_focus_flag() and keyname == 'Right': #mouse focus
114 self.handle_states(Actions.BLOCK_ROTATE_RIGHT)
115 elif self.get_focus_flag() and keyname == 'Left': #mouse focus
116 self.handle_states(Actions.BLOCK_ROTATE_LEFT)
117 #################### Enable/Disable ###############################
118 elif self.get_focus_flag() and keyname == 'e': #mouse focus
119 self.handle_states(Actions.BLOCK_ENABLE)
120 elif self.get_focus_flag() and keyname == 'd': #mouse focus
121 self.handle_states(Actions.BLOCK_DISABLE)
122 #################### Data Type ###############################
123 elif self.get_focus_flag() and keyname == 'Down': #mouse focus
124 self.handle_states(Actions.BLOCK_INC_TYPE)
125 elif self.get_focus_flag() and keyname == 'Up': #mouse focus
126 self.handle_states(Actions.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(Actions.PORT_CONTROLLER_INC)
130 elif self.get_focus_flag() and keyname in ('minus', 'KP_Subtract'): #mouse focus
131 self.handle_states(Actions.PORT_CONTROLLER_DEC)
132 #################### Gen/Exec/Stop/Print ###############################
133 elif keyname == 'F5':
134 self.handle_states(Actions.FLOW_GRAPH_GEN)
135 elif keyname == 'F6':
136 self.handle_states(Actions.FLOW_GRAPH_EXEC)
137 elif keyname == 'F7':
138 self.handle_states(Actions.FLOW_GRAPH_KILL)
139 elif keyname == 'Print':
140 self.handle_states(Actions.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(Actions.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 == Actions.APPLICATION_INITIALIZE:
177 for action in Actions.ACTIONS_LIST: action.set_sensitive(False) #set all actions disabled
178 # enable a select few actions
180 Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
181 Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
182 Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
183 Actions.HOTKEYS_WINDOW_DISPLAY, Actions.PREFS_WINDOW_DISPLAY,
184 Actions.FLOW_GRAPH_SCREEN_CAPTURE,
185 ): Actions.get_action_from_name(action).set_sensitive(True)
186 if not self.init_file_paths and Preferences.restore_files():
187 self.init_file_paths = Preferences.files_open()
188 if not self.init_file_paths: self.init_file_paths = ['']
189 for file_path in self.init_file_paths:
190 if file_path: self.main_window.new_page(file_path) #load pages from file paths
191 if Preferences.file_open() in self.init_file_paths:
192 self.main_window.new_page(Preferences.file_open(), show=True)
193 if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
194 elif state == Actions.APPLICATION_QUIT:
195 if self.main_window.close_pages():
198 ##################################################
200 ##################################################
201 elif state == Actions.ELEMENT_SELECT:
202 self.get_flow_graph().update()
203 elif state == Actions.NOTHING_SELECT:
204 self.get_flow_graph().unselect()
205 self.get_flow_graph().update()
206 ##################################################
208 ##################################################
209 elif state == Actions.BLOCK_ENABLE:
210 if self.get_flow_graph().enable_selected(True):
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 elif state == Actions.BLOCK_DISABLE:
215 if self.get_flow_graph().enable_selected(False):
216 self.get_flow_graph().update()
217 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
218 self.get_page().set_saved(False)
219 ##################################################
221 ##################################################
222 elif state == Actions.BLOCK_CUT:
223 self.handle_states(BLOCK_COPY)
224 self.handle_states(ELEMENT_DELETE)
225 elif state == Actions.BLOCK_COPY:
226 self.clipboard = self.get_flow_graph().copy_to_clipboard()
227 elif state == Actions.BLOCK_PASTE:
229 self.get_flow_graph().paste_from_clipboard(self.clipboard)
230 self.get_flow_graph().update()
231 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
232 self.get_page().set_saved(False)
233 ##################################################
234 # Move/Rotate/Delete/Create
235 ##################################################
236 elif state == Actions.BLOCK_MOVE:
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 == Actions.BLOCK_ROTATE_LEFT:
240 if self.get_flow_graph().rotate_selected(DIR_LEFT):
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 == Actions.BLOCK_ROTATE_RIGHT:
245 if self.get_flow_graph().rotate_selected(DIR_RIGHT):
246 self.get_flow_graph().update()
247 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
248 self.get_page().set_saved(False)
249 elif state == Actions.ELEMENT_DELETE:
250 if self.get_flow_graph().remove_selected():
251 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
252 self.handle_states(Actions.NOTHING_SELECT)
253 self.get_page().set_saved(False)
254 elif state == Actions.ELEMENT_CREATE:
255 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
256 self.handle_states(Actions.NOTHING_SELECT)
257 self.get_page().set_saved(False)
258 elif state == Actions.BLOCK_INC_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 == Actions.BLOCK_DEC_TYPE:
264 if self.get_flow_graph().type_controller_modify_selected(-1):
265 self.get_flow_graph().update()
266 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
267 self.get_page().set_saved(False)
268 elif state == Actions.PORT_CONTROLLER_INC:
269 if self.get_flow_graph().port_controller_modify_selected(1):
270 self.get_flow_graph().update()
271 self.get_flow_graph().update() #2 times
272 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
273 self.get_page().set_saved(False)
274 elif state == Actions.PORT_CONTROLLER_DEC:
275 if self.get_flow_graph().port_controller_modify_selected(-1):
276 self.get_flow_graph().update()
277 self.get_flow_graph().update() #2 times
278 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
279 self.get_page().set_saved(False)
280 ##################################################
282 ##################################################
283 elif state == Actions.PREFS_WINDOW_DISPLAY:
285 self.get_flow_graph().update()
286 elif state == Actions.ABOUT_WINDOW_DISPLAY:
288 elif state == Actions.HOTKEYS_WINDOW_DISPLAY:
290 ##################################################
291 # Param Modifications
292 ##################################################
293 elif state == Actions.BLOCK_PARAM_MODIFY:
294 if self.get_flow_graph().param_modify_selected():
295 self.get_flow_graph().update()
296 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
297 self.get_page().set_saved(False)
298 ##################################################
300 ##################################################
301 elif state == Actions.FLOW_GRAPH_UNDO:
302 n = self.get_page().get_state_cache().get_prev_state()
304 self.get_flow_graph().unselect()
305 self.get_flow_graph().import_data(n)
306 self.get_flow_graph().update()
307 self.get_page().set_saved(False)
308 elif state == Actions.FLOW_GRAPH_REDO:
309 n = self.get_page().get_state_cache().get_next_state()
311 self.get_flow_graph().unselect()
312 self.get_flow_graph().import_data(n)
313 self.get_flow_graph().update()
314 self.get_page().set_saved(False)
315 ##################################################
316 # New/Open/Save/Close
317 ##################################################
318 elif state == Actions.FLOW_GRAPH_NEW:
319 self.main_window.new_page()
320 elif state == Actions.FLOW_GRAPH_OPEN:
321 file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
322 if file_paths: #open a new page for each file, show only the first
323 for i,file_path in enumerate(file_paths):
324 self.main_window.new_page(file_path, show=(i==0))
325 elif state == Actions.FLOW_GRAPH_CLOSE:
326 self.main_window.close_page()
327 elif state == Actions.FLOW_GRAPH_SAVE:
328 if not self.get_page().get_file_path(): self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
331 ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
332 self.get_page().set_saved(True)
334 Messages.send_fail_save(self.get_page().get_file_path())
335 self.get_page().set_saved(False)
336 elif state == Actions.FLOW_GRAPH_SAVE_AS:
337 file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
338 if file_path != None:
339 self.get_page().set_file_path(file_path)
340 self.handle_states(Actions.FLOW_GRAPH_SAVE)
341 elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
342 file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
343 if file_path != None:
344 pixmap = self.get_flow_graph().get_drawing_area().pixmap
345 width, height = pixmap.get_size()
346 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height)
347 pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height)
348 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
349 ##################################################
351 ##################################################
352 elif state == Actions.FLOW_GRAPH_GEN:
353 if not self.get_page().get_pid():
354 if not self.get_page().get_saved() or not self.get_page().get_file_path():
355 self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved
356 if self.get_page().get_saved() and self.get_page().get_file_path():
357 generator = self.get_page().get_generator()
359 Messages.send_start_gen(generator.get_file_path())
361 except Exception,e: Messages.send_fail_gen(e)
362 else: self.generator = None
363 elif state == Actions.FLOW_GRAPH_EXEC:
364 if not self.get_page().get_pid():
365 self.handle_states(Actions.FLOW_GRAPH_GEN)
366 if self.get_page().get_saved() and self.get_page().get_file_path():
367 ExecFlowGraphThread(self)
368 elif state == Actions.FLOW_GRAPH_KILL:
369 if self.get_page().get_pid():
370 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
371 except: print "could not kill pid: %s"%self.get_page().get_pid()
372 elif state == '': #pass and run the global actions
374 else: print '!!! State "%s" not handled !!!'%state
375 ##################################################
376 # Global Actions for all States
377 ##################################################
378 #update general buttons
379 Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
380 Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
381 Actions.get_action_from_name(Actions.BLOCK_ROTATE_RIGHT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
382 Actions.get_action_from_name(Actions.BLOCK_ROTATE_LEFT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
383 #update cut/copy/paste
384 Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
385 Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
386 Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard))
387 #update enable/disable
388 Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
389 Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
390 #set the exec and stop buttons
391 self.update_exec_stop()
393 Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
394 self.main_window.update()
396 self.get_flow_graph().draw()
398 def update_exec_stop(self):
400 Update the exec and stop buttons.
401 Lock and unlock the mutex for race conditions with exec flow graph threads.
403 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
404 Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive)
405 Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive)
406 Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
408 class ExecFlowGraphThread(Thread):
409 """Execute the flow graph as a new process and wait on it to finish."""
410 def __init__ (self, action_handler):
412 ExecFlowGraphThread constructor.
413 @param action_handler an instance of an ActionHandler
415 Thread.__init__(self)
416 self.update_exec_stop = action_handler.update_exec_stop
417 self.flow_graph = action_handler.get_flow_graph()
418 #store page and dont use main window calls in run
419 self.page = action_handler.get_page()
420 Messages.send_start_exec(self.page.get_generator().get_file_path())
423 self.p = self.page.get_generator().get_popen()
424 self.page.set_pid(self.p.pid)
426 self.update_exec_stop()
429 Messages.send_verbose_exec(str(e))
430 Messages.send_end_exec()
433 """Wait on the flow graph."""
437 gtk.gdk.threads_enter()
438 Messages.send_verbose_exec(r)
439 gtk.gdk.threads_leave()
440 r = os.read(self.p.stdout.fileno(), 1024)
441 gtk.gdk.threads_enter()
442 Messages.send_end_exec()
443 self.page.set_pid(None)
444 self.update_exec_stop()
445 gtk.gdk.threads_leave()