2 Copyright 2007, 2008, 2009 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 Constants import IMAGE_FILE_EXTENSION
29 from threading import Thread
31 from .. base import ParseXML
33 from Platform import Platform
34 from MainWindow import MainWindow
35 from ParamsDialog import ParamsDialog
37 from FileDialogs import OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, SaveImageFileDialog
39 gobject.threads_init()
43 The action handler will setup all the major window components,
44 and handle button presses and flow graph operations from the GUI.
47 def __init__(self, file_paths, platform):
49 ActionHandler constructor.
50 Create the main window, setup the message handler, import the preferences,
51 and connect all of the action handlers. Finally, enter the gtk main loop and block.
52 @param file_paths a list of flow graph file passed from command line
53 @param platform platform module
56 platform = Platform(platform)
57 for action in Actions.get_all_actions(): action.connect('activate', self._handle_actions)
58 #setup the main window
59 self.main_window = MainWindow(self.handle_states, platform)
60 self.main_window.connect('delete_event', self._quit)
61 self.main_window.connect('key-press-event', self._handle_key_press)
62 self.get_page = self.main_window.get_page
63 self.get_flow_graph = self.main_window.get_flow_graph
64 self.get_focus_flag = self.main_window.get_focus_flag
66 Messages.register_messenger(self.main_window.add_report_line)
67 Messages.send_init(platform)
69 self.init_file_paths = file_paths
70 self.handle_states(Actions.APPLICATION_INITIALIZE)
74 def _handle_key_press(self, widget, event):
76 Handle key presses from the keyboard and translate key combinations into actions.
77 This key press handler is called prior to the gtk key press handler.
78 This handler bypasses built in accelerator key handling when in focus because
79 * some keys are ignored by the accelerators like the direction keys,
80 * some keys are not registered to any accelerators but are still used.
81 When not in focus, gtk and the accelerators handle the the key press.
82 @return false to let gtk handle the key action
84 #dont allow key presses to queue up
85 if gtk.events_pending(): return True
86 #extract action name from this key press
87 key_name = gtk.gdk.keyval_name(event.keyval)
88 mod_mask = event.state
89 action_name = Actions.get_action_name_from_key_name(key_name, mod_mask)
90 #handle the action if flow graph is in focus
91 if action_name and self.get_focus_flag():
92 self.handle_states(action_name)
93 return True #handled by this method
94 return False #let gtk handle the key press
96 def _quit(self, window, event):
98 Handle the delete event from the main window.
99 Generated by pressing X to close, alt+f4, or right click+close.
100 This method in turns calls the state handler to quit.
103 self.handle_states(Actions.APPLICATION_QUIT)
106 def _handle_actions(self, event):
108 Handle all of the activate signals from the gtk actions.
109 The action signals derive from clicking on a toolbar or menu bar button.
110 Forward the action to the state handler.
112 self.handle_states(event.get_name())
114 def handle_states(self, state=''):
116 Handle the state changes in the GUI.
117 Handle all of the state changes that arise from the action handler or other gui and
118 inputs in the application. The state passed to the handle_states method is a string descriping
119 the change. A series of if/elif statements handle the state by greying out action buttons, causing
120 changes in the flow graph, saving/opening files... The handle_states method is passed to the
121 contructors of many of the classes used in this application enabling them to report any state change.
122 @param state a string describing the state change
125 ##################################################
127 ##################################################
128 if state == Actions.APPLICATION_INITIALIZE:
129 for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled
130 # enable a select few actions
132 Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
133 Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
134 Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
135 Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY,
136 Actions.TYPES_WINDOW_DISPLAY,
137 ): Actions.get_action_from_name(action).set_sensitive(True)
138 if not self.init_file_paths:
139 self.init_file_paths = Preferences.files_open()
140 if not self.init_file_paths: self.init_file_paths = ['']
141 for file_path in self.init_file_paths:
142 if file_path: self.main_window.new_page(file_path) #load pages from file paths
143 if Preferences.file_open() in self.init_file_paths:
144 self.main_window.new_page(Preferences.file_open(), show=True)
145 if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
146 elif state == Actions.APPLICATION_QUIT:
147 if self.main_window.close_pages():
150 ##################################################
152 ##################################################
153 elif state == Actions.ELEMENT_SELECT:
154 pass #do nothing, update routines below
155 elif state == Actions.NOTHING_SELECT:
156 self.get_flow_graph().unselect()
157 ##################################################
159 ##################################################
160 elif state == Actions.BLOCK_ENABLE:
161 if self.get_flow_graph().enable_selected(True):
162 self.get_flow_graph().update()
163 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
164 self.get_page().set_saved(False)
165 elif state == Actions.BLOCK_DISABLE:
166 if self.get_flow_graph().enable_selected(False):
167 self.get_flow_graph().update()
168 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
169 self.get_page().set_saved(False)
170 ##################################################
172 ##################################################
173 elif state == Actions.BLOCK_CUT:
174 self.handle_states(Actions.BLOCK_COPY)
175 self.handle_states(Actions.ELEMENT_DELETE)
176 elif state == Actions.BLOCK_COPY:
177 self.clipboard = self.get_flow_graph().copy_to_clipboard()
178 elif state == Actions.BLOCK_PASTE:
180 self.get_flow_graph().paste_from_clipboard(self.clipboard)
181 self.get_flow_graph().update()
182 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
183 self.get_page().set_saved(False)
184 ##################################################
185 # Move/Rotate/Delete/Create
186 ##################################################
187 elif state == Actions.BLOCK_MOVE:
188 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
189 self.get_page().set_saved(False)
190 elif state == Actions.BLOCK_ROTATE_CCW:
191 if self.get_flow_graph().rotate_selected(90):
192 self.get_flow_graph().update()
193 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
194 self.get_page().set_saved(False)
195 elif state == Actions.BLOCK_ROTATE_CW:
196 if self.get_flow_graph().rotate_selected(-90):
197 self.get_flow_graph().update()
198 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
199 self.get_page().set_saved(False)
200 elif state == Actions.ELEMENT_DELETE:
201 if self.get_flow_graph().remove_selected():
202 self.get_flow_graph().update()
203 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
204 self.handle_states(Actions.NOTHING_SELECT)
205 self.get_page().set_saved(False)
206 elif state == Actions.ELEMENT_CREATE:
207 self.get_flow_graph().update()
208 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
209 self.handle_states(Actions.NOTHING_SELECT)
210 self.get_page().set_saved(False)
211 elif state == Actions.BLOCK_INC_TYPE:
212 if self.get_flow_graph().type_controller_modify_selected(1):
213 self.get_flow_graph().update()
214 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
215 self.get_page().set_saved(False)
216 elif state == Actions.BLOCK_DEC_TYPE:
217 if self.get_flow_graph().type_controller_modify_selected(-1):
218 self.get_flow_graph().update()
219 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
220 self.get_page().set_saved(False)
221 elif state == Actions.PORT_CONTROLLER_INC:
222 if self.get_flow_graph().port_controller_modify_selected(1):
223 self.get_flow_graph().update()
224 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
225 self.get_page().set_saved(False)
226 elif state == Actions.PORT_CONTROLLER_DEC:
227 if self.get_flow_graph().port_controller_modify_selected(-1):
228 self.get_flow_graph().update()
229 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
230 self.get_page().set_saved(False)
231 ##################################################
233 ##################################################
234 elif state == Actions.ABOUT_WINDOW_DISPLAY:
235 Dialogs.AboutDialog(self.get_flow_graph().get_parent())
236 elif state == Actions.HELP_WINDOW_DISPLAY:
238 elif state == Actions.TYPES_WINDOW_DISPLAY:
239 Dialogs.TypesDialog(self.get_flow_graph().get_parent())
240 ##################################################
241 # Param Modifications
242 ##################################################
243 elif state == Actions.BLOCK_PARAM_MODIFY:
244 selected_block = self.get_flow_graph().get_selected_block()
245 if selected_block and ParamsDialog(selected_block).run():
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 ##################################################
251 ##################################################
252 elif state == Actions.FLOW_GRAPH_UNDO:
253 n = self.get_page().get_state_cache().get_prev_state()
255 self.get_flow_graph().unselect()
256 self.get_flow_graph().import_data(n)
257 self.get_flow_graph().update()
258 self.get_page().set_saved(False)
259 elif state == Actions.FLOW_GRAPH_REDO:
260 n = self.get_page().get_state_cache().get_next_state()
262 self.get_flow_graph().unselect()
263 self.get_flow_graph().import_data(n)
264 self.get_flow_graph().update()
265 self.get_page().set_saved(False)
266 ##################################################
267 # New/Open/Save/Close
268 ##################################################
269 elif state == Actions.FLOW_GRAPH_NEW:
270 self.main_window.new_page()
271 elif state == Actions.FLOW_GRAPH_OPEN:
272 file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
273 if file_paths: #open a new page for each file, show only the first
274 for i,file_path in enumerate(file_paths):
275 self.main_window.new_page(file_path, show=(i==0))
276 elif state == Actions.FLOW_GRAPH_CLOSE:
277 self.main_window.close_page()
278 elif state == Actions.FLOW_GRAPH_SAVE:
279 #read-only or undefined file path, do save-as
280 if self.get_page().get_read_only() or not self.get_page().get_file_path():
281 self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
282 #otherwise try to save
285 ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
286 self.get_page().set_saved(True)
288 Messages.send_fail_save(self.get_page().get_file_path())
289 self.get_page().set_saved(False)
290 elif state == Actions.FLOW_GRAPH_SAVE_AS:
291 file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
292 if file_path is not None:
293 self.get_page().set_file_path(file_path)
294 self.handle_states(Actions.FLOW_GRAPH_SAVE)
295 elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
296 file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
297 if file_path is not None:
298 pixbuf = self.get_flow_graph().get_drawing_area().get_pixbuf()
299 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
300 ##################################################
302 ##################################################
303 elif state == Actions.FLOW_GRAPH_GEN:
304 if not self.get_page().get_pid():
305 if not self.get_page().get_saved() or not self.get_page().get_file_path():
306 self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved
307 if self.get_page().get_saved() and self.get_page().get_file_path():
308 generator = self.get_page().get_generator()
310 Messages.send_start_gen(generator.get_file_path())
312 except Exception,e: Messages.send_fail_gen(e)
313 else: self.generator = None
314 elif state == Actions.FLOW_GRAPH_EXEC:
315 if not self.get_page().get_pid():
316 self.handle_states(Actions.FLOW_GRAPH_GEN)
317 if self.get_page().get_saved() and self.get_page().get_file_path():
318 ExecFlowGraphThread(self)
319 elif state == Actions.FLOW_GRAPH_KILL:
320 if self.get_page().get_pid():
321 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
322 except: print "could not kill pid: %s"%self.get_page().get_pid()
323 elif state == '': #pass and run the global actions
325 else: print '!!! State "%s" not handled !!!'%state
326 ##################################################
327 # Global Actions for all States
328 ##################################################
329 #update general buttons
330 Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
331 Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
332 Actions.get_action_from_name(Actions.BLOCK_ROTATE_CCW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
333 Actions.get_action_from_name(Actions.BLOCK_ROTATE_CW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
334 #update cut/copy/paste
335 Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
336 Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
337 Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard))
338 #update enable/disable
339 Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
340 Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
341 #set the exec and stop buttons
342 self.update_exec_stop()
344 Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
345 self.main_window.update()
346 try: #set the size of the flow graph area (if changed)
347 new_size = self.get_flow_graph().get_option('window_size')
348 if self.get_flow_graph().get_size() != tuple(new_size):
349 self.get_flow_graph().set_size(*new_size)
352 self.get_flow_graph().update_selected()
353 self.get_flow_graph().queue_draw()
355 def update_exec_stop(self):
357 Update the exec and stop buttons.
358 Lock and unlock the mutex for race conditions with exec flow graph threads.
360 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
361 Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive)
362 Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive)
363 Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
365 class ExecFlowGraphThread(Thread):
366 """Execute the flow graph as a new process and wait on it to finish."""
368 def __init__ (self, action_handler):
370 ExecFlowGraphThread constructor.
371 @param action_handler an instance of an ActionHandler
373 Thread.__init__(self)
374 self.update_exec_stop = action_handler.update_exec_stop
375 self.flow_graph = action_handler.get_flow_graph()
376 #store page and dont use main window calls in run
377 self.page = action_handler.get_page()
378 Messages.send_start_exec(self.page.get_generator().get_file_path())
381 self.p = self.page.get_generator().get_popen()
382 self.page.set_pid(self.p.pid)
384 self.update_exec_stop()
387 Messages.send_verbose_exec(str(e))
388 Messages.send_end_exec()
392 Wait on the executing process by reading from its stdout.
393 Use gobject.idle_add when calling functions that modify gtk objects.
398 gobject.idle_add(Messages.send_verbose_exec, r)
399 r = os.read(self.p.stdout.fileno(), 1024)
400 gobject.idle_add(self.done)
403 """Perform end of execution tasks."""
404 Messages.send_end_exec()
405 self.page.set_pid(None)
406 self.update_exec_stop()