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