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 ParamsDialog import ParamsDialog
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 #dont allow key presses to queue up
83 if gtk.events_pending(): return True
84 #extract action name from this key press
85 key_name = gtk.gdk.keyval_name(event.keyval)
86 mod_mask = event.state
87 action_name = Actions.get_action_name_from_key_name(key_name, mod_mask)
88 #handle the action if flow graph is in focus
89 if action_name and self.get_focus_flag():
90 self.handle_states(action_name)
91 return True #handled by this method
92 return False #let gtk handle the key press
94 def _quit(self, window, event):
96 Handle the delete event from the main window.
97 Generated by pressing X to close, alt+f4, or right click+close.
98 This method in turns calls the state handler to quit.
101 self.handle_states(Actions.APPLICATION_QUIT)
104 def _handle_actions(self, event):
106 Handle all of the activate signals from the gtk actions.
107 The action signals derive from clicking on a toolbar or menu bar button.
108 Forward the action to the state handler.
110 self.handle_states(event.get_name())
112 def handle_states(self, state=''):
114 Handle the state changes in the GUI.
115 Handle all of the state changes that arise from the action handler or other gui and
116 inputs in the application. The state passed to the handle_states method is a string descriping
117 the change. A series of if/elif statements handle the state by greying out action buttons, causing
118 changes in the flow graph, saving/opening files... The handle_states method is passed to the
119 contructors of many of the classes used in this application enabling them to report any state change.
120 @param state a string describing the state change
123 ##################################################
125 ##################################################
126 if state == Actions.APPLICATION_INITIALIZE:
127 for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled
128 # enable a select few actions
130 Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
131 Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
132 Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
133 Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY,
134 Actions.TYPES_WINDOW_DISPLAY,
135 ): Actions.get_action_from_name(action).set_sensitive(True)
136 if not self.init_file_paths:
137 self.init_file_paths = Preferences.files_open()
138 if not self.init_file_paths: self.init_file_paths = ['']
139 for file_path in self.init_file_paths:
140 if file_path: self.main_window.new_page(file_path) #load pages from file paths
141 if Preferences.file_open() in self.init_file_paths:
142 self.main_window.new_page(Preferences.file_open(), show=True)
143 if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
144 elif state == Actions.APPLICATION_QUIT:
145 if self.main_window.close_pages():
148 ##################################################
150 ##################################################
151 elif state == Actions.ELEMENT_SELECT:
152 pass #do nothing, update routines below
153 elif state == Actions.NOTHING_SELECT:
154 self.get_flow_graph().unselect()
155 ##################################################
157 ##################################################
158 elif state == Actions.BLOCK_ENABLE:
159 if self.get_flow_graph().enable_selected(True):
160 self.get_flow_graph().update()
161 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
162 self.get_page().set_saved(False)
163 elif state == Actions.BLOCK_DISABLE:
164 if self.get_flow_graph().enable_selected(False):
165 self.get_flow_graph().update()
166 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
167 self.get_page().set_saved(False)
168 ##################################################
170 ##################################################
171 elif state == Actions.BLOCK_CUT:
172 self.handle_states(Actions.BLOCK_COPY)
173 self.handle_states(Actions.ELEMENT_DELETE)
174 elif state == Actions.BLOCK_COPY:
175 self.clipboard = self.get_flow_graph().copy_to_clipboard()
176 elif state == Actions.BLOCK_PASTE:
178 self.get_flow_graph().paste_from_clipboard(self.clipboard)
179 self.get_flow_graph().update()
180 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
181 self.get_page().set_saved(False)
182 ##################################################
183 # Move/Rotate/Delete/Create
184 ##################################################
185 elif state == Actions.BLOCK_MOVE:
186 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
187 self.get_page().set_saved(False)
188 elif state == Actions.BLOCK_ROTATE_CCW:
189 if self.get_flow_graph().rotate_selected(90):
190 self.get_flow_graph().update()
191 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
192 self.get_page().set_saved(False)
193 elif state == Actions.BLOCK_ROTATE_CW:
194 if self.get_flow_graph().rotate_selected(-90):
195 self.get_flow_graph().update()
196 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
197 self.get_page().set_saved(False)
198 elif state == Actions.ELEMENT_DELETE:
199 if self.get_flow_graph().remove_selected():
200 self.get_flow_graph().update()
201 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
202 self.handle_states(Actions.NOTHING_SELECT)
203 self.get_page().set_saved(False)
204 elif state == Actions.ELEMENT_CREATE:
205 self.get_flow_graph().update()
206 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
207 self.handle_states(Actions.NOTHING_SELECT)
208 self.get_page().set_saved(False)
209 elif state == Actions.BLOCK_INC_TYPE:
210 if self.get_flow_graph().type_controller_modify_selected(1):
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_DEC_TYPE:
215 if self.get_flow_graph().type_controller_modify_selected(-1):
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 elif state == Actions.PORT_CONTROLLER_INC:
220 if self.get_flow_graph().port_controller_modify_selected(1):
221 self.get_flow_graph().update()
222 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
223 self.get_page().set_saved(False)
224 elif state == Actions.PORT_CONTROLLER_DEC:
225 if self.get_flow_graph().port_controller_modify_selected(-1):
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 ##################################################
231 ##################################################
232 elif state == Actions.ABOUT_WINDOW_DISPLAY:
233 Dialogs.AboutDialog(self.get_flow_graph().get_parent())
234 elif state == Actions.HELP_WINDOW_DISPLAY:
236 elif state == Actions.TYPES_WINDOW_DISPLAY:
237 Dialogs.TypesDialog(self.get_flow_graph().get_parent())
238 ##################################################
239 # Param Modifications
240 ##################################################
241 elif state == Actions.BLOCK_PARAM_MODIFY:
242 selected_block = self.get_flow_graph().get_selected_block()
243 if selected_block and ParamsDialog(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)
247 ##################################################
249 ##################################################
250 elif state == Actions.FLOW_GRAPH_UNDO:
251 n = self.get_page().get_state_cache().get_prev_state()
253 self.get_flow_graph().unselect()
254 self.get_flow_graph().import_data(n)
255 self.get_flow_graph().update()
256 self.get_page().set_saved(False)
257 elif state == Actions.FLOW_GRAPH_REDO:
258 n = self.get_page().get_state_cache().get_next_state()
260 self.get_flow_graph().unselect()
261 self.get_flow_graph().import_data(n)
262 self.get_flow_graph().update()
263 self.get_page().set_saved(False)
264 ##################################################
265 # New/Open/Save/Close
266 ##################################################
267 elif state == Actions.FLOW_GRAPH_NEW:
268 self.main_window.new_page()
269 elif state == Actions.FLOW_GRAPH_OPEN:
270 file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
271 if file_paths: #open a new page for each file, show only the first
272 for i,file_path in enumerate(file_paths):
273 self.main_window.new_page(file_path, show=(i==0))
274 elif state == Actions.FLOW_GRAPH_CLOSE:
275 self.main_window.close_page()
276 elif state == Actions.FLOW_GRAPH_SAVE:
277 #read-only or undefined file path, do save-as
278 if self.get_page().get_read_only() or not self.get_page().get_file_path():
279 self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
280 #otherwise try to save
283 ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
284 self.get_page().set_saved(True)
286 Messages.send_fail_save(self.get_page().get_file_path())
287 self.get_page().set_saved(False)
288 elif state == Actions.FLOW_GRAPH_SAVE_AS:
289 file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
290 if file_path is not None:
291 self.get_page().set_file_path(file_path)
292 self.handle_states(Actions.FLOW_GRAPH_SAVE)
293 elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
294 file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
295 if file_path is not None:
296 pixbuf = self.get_flow_graph().get_drawing_area().get_pixbuf()
297 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
298 ##################################################
300 ##################################################
301 elif state == Actions.FLOW_GRAPH_GEN:
302 if not self.get_page().get_pid():
303 if not self.get_page().get_saved() or not self.get_page().get_file_path():
304 self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved
305 if self.get_page().get_saved() and self.get_page().get_file_path():
306 generator = self.get_page().get_generator()
308 Messages.send_start_gen(generator.get_file_path())
310 except Exception,e: Messages.send_fail_gen(e)
311 else: self.generator = None
312 elif state == Actions.FLOW_GRAPH_EXEC:
313 if not self.get_page().get_pid():
314 self.handle_states(Actions.FLOW_GRAPH_GEN)
315 if self.get_page().get_saved() and self.get_page().get_file_path():
316 ExecFlowGraphThread(self)
317 elif state == Actions.FLOW_GRAPH_KILL:
318 if self.get_page().get_pid():
319 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
320 except: print "could not kill pid: %s"%self.get_page().get_pid()
321 elif state == '': #pass and run the global actions
323 else: print '!!! State "%s" not handled !!!'%state
324 ##################################################
325 # Global Actions for all States
326 ##################################################
327 #update general buttons
328 Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
329 Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
330 Actions.get_action_from_name(Actions.BLOCK_ROTATE_CCW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
331 Actions.get_action_from_name(Actions.BLOCK_ROTATE_CW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
332 #update cut/copy/paste
333 Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
334 Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
335 Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard))
336 #update enable/disable
337 Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
338 Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
339 #set the exec and stop buttons
340 self.update_exec_stop()
342 Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
343 self.main_window.update()
344 try: #set the size of the flow graph area (if changed)
345 new_size = self.get_flow_graph().get_option('window_size')
346 if self.get_flow_graph().get_size() != tuple(new_size):
347 self.get_flow_graph().set_size(*new_size)
350 self.get_flow_graph().update_selected()
351 self.get_flow_graph().queue_draw()
353 def update_exec_stop(self):
355 Update the exec and stop buttons.
356 Lock and unlock the mutex for race conditions with exec flow graph threads.
358 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
359 Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive)
360 Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive)
361 Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
363 class ExecFlowGraphThread(Thread):
364 """Execute the flow graph as a new process and wait on it to finish."""
366 def __init__ (self, action_handler):
368 ExecFlowGraphThread constructor.
369 @param action_handler an instance of an ActionHandler
371 Thread.__init__(self)
372 self.update_exec_stop = action_handler.update_exec_stop
373 self.flow_graph = action_handler.get_flow_graph()
374 #store page and dont use main window calls in run
375 self.page = action_handler.get_page()
376 Messages.send_start_exec(self.page.get_generator().get_file_path())
379 self.p = self.page.get_generator().get_popen()
380 self.page.set_pid(self.p.pid)
382 self.update_exec_stop()
385 Messages.send_verbose_exec(str(e))
386 Messages.send_end_exec()
390 Wait on the executing process by reading from its stdout.
391 Use gobject.idle_add when calling functions that modify gtk objects.
396 gobject.idle_add(Messages.send_verbose_exec, r)
397 r = os.read(self.p.stdout.fileno(), 1024)
398 gobject.idle_add(self.done)
401 """Perform end of execution tasks."""
402 Messages.send_end_exec()
403 self.page.set_pid(None)
404 self.update_exec_stop()