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.COLORS_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_flow_graph().update() #2 times
225 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
226 self.get_page().set_saved(False)
227 elif state == Actions.PORT_CONTROLLER_DEC:
228 if self.get_flow_graph().port_controller_modify_selected(-1):
229 self.get_flow_graph().update()
230 self.get_flow_graph().update() #2 times
231 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
232 self.get_page().set_saved(False)
233 ##################################################
235 ##################################################
236 elif state == Actions.ABOUT_WINDOW_DISPLAY:
237 Dialogs.AboutDialog(self.get_flow_graph().get_parent())
238 elif state == Actions.HELP_WINDOW_DISPLAY:
240 elif state == Actions.COLORS_WINDOW_DISPLAY:
241 Dialogs.ColorsDialog(self.get_flow_graph().get_parent())
242 ##################################################
243 # Param Modifications
244 ##################################################
245 elif state == Actions.BLOCK_PARAM_MODIFY:
246 selected_block = self.get_flow_graph().get_selected_block()
247 if selected_block and ParamsDialog(selected_block).run():
248 self.get_flow_graph().update()
249 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
250 self.get_page().set_saved(False)
251 ##################################################
253 ##################################################
254 elif state == Actions.FLOW_GRAPH_UNDO:
255 n = self.get_page().get_state_cache().get_prev_state()
257 self.get_flow_graph().unselect()
258 self.get_flow_graph().import_data(n)
259 self.get_flow_graph().update()
260 self.get_page().set_saved(False)
261 elif state == Actions.FLOW_GRAPH_REDO:
262 n = self.get_page().get_state_cache().get_next_state()
264 self.get_flow_graph().unselect()
265 self.get_flow_graph().import_data(n)
266 self.get_flow_graph().update()
267 self.get_page().set_saved(False)
268 ##################################################
269 # New/Open/Save/Close
270 ##################################################
271 elif state == Actions.FLOW_GRAPH_NEW:
272 self.main_window.new_page()
273 elif state == Actions.FLOW_GRAPH_OPEN:
274 file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
275 if file_paths: #open a new page for each file, show only the first
276 for i,file_path in enumerate(file_paths):
277 self.main_window.new_page(file_path, show=(i==0))
278 elif state == Actions.FLOW_GRAPH_CLOSE:
279 self.main_window.close_page()
280 elif state == Actions.FLOW_GRAPH_SAVE:
281 #read-only or undefined file path, do save-as
282 if self.get_page().get_read_only() or not self.get_page().get_file_path():
283 self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
284 #otherwise try to save
287 ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
288 self.get_page().set_saved(True)
290 Messages.send_fail_save(self.get_page().get_file_path())
291 self.get_page().set_saved(False)
292 elif state == Actions.FLOW_GRAPH_SAVE_AS:
293 file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
294 if file_path is not None:
295 self.get_page().set_file_path(file_path)
296 self.handle_states(Actions.FLOW_GRAPH_SAVE)
297 elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
298 file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
299 if file_path is not None:
300 pixbuf = self.get_flow_graph().get_drawing_area().get_pixbuf()
301 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
302 ##################################################
304 ##################################################
305 elif state == Actions.FLOW_GRAPH_GEN:
306 if not self.get_page().get_pid():
307 if not self.get_page().get_saved() or not self.get_page().get_file_path():
308 self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved
309 if self.get_page().get_saved() and self.get_page().get_file_path():
310 generator = self.get_page().get_generator()
312 Messages.send_start_gen(generator.get_file_path())
314 except Exception,e: Messages.send_fail_gen(e)
315 else: self.generator = None
316 elif state == Actions.FLOW_GRAPH_EXEC:
317 if not self.get_page().get_pid():
318 self.handle_states(Actions.FLOW_GRAPH_GEN)
319 if self.get_page().get_saved() and self.get_page().get_file_path():
320 ExecFlowGraphThread(self)
321 elif state == Actions.FLOW_GRAPH_KILL:
322 if self.get_page().get_pid():
323 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
324 except: print "could not kill pid: %s"%self.get_page().get_pid()
325 elif state == '': #pass and run the global actions
327 else: print '!!! State "%s" not handled !!!'%state
328 ##################################################
329 # Global Actions for all States
330 ##################################################
331 #update general buttons
332 Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
333 Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
334 Actions.get_action_from_name(Actions.BLOCK_ROTATE_CCW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
335 Actions.get_action_from_name(Actions.BLOCK_ROTATE_CW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
336 #update cut/copy/paste
337 Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
338 Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
339 Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard))
340 #update enable/disable
341 Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
342 Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
343 #set the exec and stop buttons
344 self.update_exec_stop()
346 Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
347 self.main_window.update()
348 try: #set the size of the flow graph area (if changed)
349 new_size = self.get_flow_graph().get_option('window_size')
350 if self.get_flow_graph().get_size() != tuple(new_size):
351 self.get_flow_graph().set_size(*new_size)
354 self.get_flow_graph().update_selected()
355 self.get_flow_graph().queue_draw()
357 def update_exec_stop(self):
359 Update the exec and stop buttons.
360 Lock and unlock the mutex for race conditions with exec flow graph threads.
362 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
363 Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive)
364 Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive)
365 Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
367 class ExecFlowGraphThread(Thread):
368 """Execute the flow graph as a new process and wait on it to finish."""
370 def __init__ (self, action_handler):
372 ExecFlowGraphThread constructor.
373 @param action_handler an instance of an ActionHandler
375 Thread.__init__(self)
376 self.update_exec_stop = action_handler.update_exec_stop
377 self.flow_graph = action_handler.get_flow_graph()
378 #store page and dont use main window calls in run
379 self.page = action_handler.get_page()
380 Messages.send_start_exec(self.page.get_generator().get_file_path())
383 self.p = self.page.get_generator().get_popen()
384 self.page.set_pid(self.p.pid)
386 self.update_exec_stop()
389 Messages.send_verbose_exec(str(e))
390 Messages.send_end_exec()
394 Wait on the executing process by reading from its stdout.
395 Use gobject.idle_add when calling functions that modify gtk objects.
400 gobject.idle_add(Messages.send_verbose_exec, r)
401 r = os.read(self.p.stdout.fileno(), 1024)
402 gobject.idle_add(self.done)
405 """Perform end of execution tasks."""
406 Messages.send_end_exec()
407 self.page.set_pid(None)
408 self.update_exec_stop()