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 Constants import DIR_LEFT, DIR_RIGHT, IMAGE_FILE_EXTENSION
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 AboutDialog
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 platform = Platform(platform)
56 #setup icon using icon theme
57 try: gtk.window_set_default_icon(gtk.IconTheme().load_icon('gnuradio-grc', 256, 0))
59 for action in Actions.get_all_actions(): action.connect('activate', self._handle_actions)
60 #setup the main window
61 self.main_window = MainWindow(self.handle_states, platform)
62 self.main_window.connect('delete_event', self._quit)
63 self.main_window.connect('key_press_event', self._handle_key_press)
64 self.get_page = self.main_window.get_page
65 self.get_flow_graph = self.main_window.get_flow_graph
66 self.get_focus_flag = self.main_window.drawing_area.get_focus_flag
68 Messages.register_messenger(self.main_window.add_report_line)
71 self.init_file_paths = file_paths
72 self.handle_states(Actions.APPLICATION_INITIALIZE)
76 def _handle_key_press(self, widget, event):
78 Handle key presses from the keyboard and translate key combos into actions.
79 This key press handler is called before the gtk accelerators kick in.
80 This handler ensures that key presses without a mod mask,
81 only pass to the accelerators if the flow graph is in focus.
82 This function also handles keys that accelerators refuse to handle: left/right,
83 and keys that are not registered with an accelerator: +/-.
84 @return false to let the accelerators handle the key action
86 if self.get_focus_flag():
89 'Left': Actions.BLOCK_ROTATE_LEFT,
90 'Right': Actions.BLOCK_ROTATE_RIGHT,
91 'Up': Actions.BLOCK_DEC_TYPE,
92 'Down': Actions.BLOCK_INC_TYPE,
93 'equal': Actions.PORT_CONTROLLER_INC,
94 'plus': Actions.PORT_CONTROLLER_INC,
95 'KP_Add': Actions.PORT_CONTROLLER_INC,
96 'minus': Actions.PORT_CONTROLLER_DEC,
97 'KP_Subtract': Actions.PORT_CONTROLLER_DEC,
98 }[gtk.gdk.keyval_name(event.keyval)])
100 #focus: always return false for accelerator to handle
102 #no focus: only allow accelerator to handle when a mod is used
103 return not event.state
105 def _quit(self, window, event):
107 Handle the delete event from the main window.
108 Generated by pressing X to close, alt+f4, or right click+close.
109 This method in turns calls the state handler to quit.
112 self.handle_states(Actions.APPLICATION_QUIT)
115 def _handle_actions(self, event):
117 Handle all of the activate signals from the gtk actions.
118 The action signals derive from clicking on a toolbar or menu bar button.
119 Forward the action to the state handler.
121 self.handle_states(event.get_name())
123 def handle_states(self, state=''):
125 Handle the state changes in the GUI.
126 Handle all of the state changes that arise from the action handler or other gui and
127 inputs in the application. The state passed to the handle_states method is a string descriping
128 the change. A series of if/elif statements handle the state by greying out action buttons, causing
129 changes in the flow graph, saving/opening files... The handle_states method is passed to the
130 contructors of many of the classes used in this application enabling them to report any state change.
131 @param state a string describing the state change
134 ##################################################
136 ##################################################
137 if state == Actions.APPLICATION_INITIALIZE:
138 for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled
139 # enable a select few actions
141 Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
142 Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
143 Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
144 Actions.FLOW_GRAPH_SCREEN_CAPTURE,
145 ): Actions.get_action_from_name(action).set_sensitive(True)
146 if not self.init_file_paths:
147 self.init_file_paths = Preferences.files_open()
148 if not self.init_file_paths: self.init_file_paths = ['']
149 for file_path in self.init_file_paths:
150 if file_path: self.main_window.new_page(file_path) #load pages from file paths
151 if Preferences.file_open() in self.init_file_paths:
152 self.main_window.new_page(Preferences.file_open(), show=True)
153 if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
154 elif state == Actions.APPLICATION_QUIT:
155 if self.main_window.close_pages():
158 ##################################################
160 ##################################################
161 elif state == Actions.ELEMENT_SELECT:
162 self.get_flow_graph().update()
163 elif state == Actions.NOTHING_SELECT:
164 self.get_flow_graph().unselect()
165 self.get_flow_graph().update()
166 ##################################################
168 ##################################################
169 elif state == Actions.BLOCK_ENABLE:
170 if self.get_flow_graph().enable_selected(True):
171 self.get_flow_graph().update()
172 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
173 self.get_page().set_saved(False)
174 elif state == Actions.BLOCK_DISABLE:
175 if self.get_flow_graph().enable_selected(False):
176 self.get_flow_graph().update()
177 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
178 self.get_page().set_saved(False)
179 ##################################################
181 ##################################################
182 elif state == Actions.BLOCK_CUT:
183 self.handle_states(BLOCK_COPY)
184 self.handle_states(ELEMENT_DELETE)
185 elif state == Actions.BLOCK_COPY:
186 self.clipboard = self.get_flow_graph().copy_to_clipboard()
187 elif state == Actions.BLOCK_PASTE:
189 self.get_flow_graph().paste_from_clipboard(self.clipboard)
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 ##################################################
194 # Move/Rotate/Delete/Create
195 ##################################################
196 elif state == Actions.BLOCK_MOVE:
197 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
198 self.get_page().set_saved(False)
199 elif state == Actions.BLOCK_ROTATE_LEFT:
200 if self.get_flow_graph().rotate_selected(DIR_LEFT):
201 self.get_flow_graph().update()
202 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
203 self.get_page().set_saved(False)
204 elif state == Actions.BLOCK_ROTATE_RIGHT:
205 if self.get_flow_graph().rotate_selected(DIR_RIGHT):
206 self.get_flow_graph().update()
207 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
208 self.get_page().set_saved(False)
209 elif state == Actions.ELEMENT_DELETE:
210 if self.get_flow_graph().remove_selected():
211 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
212 self.handle_states(Actions.NOTHING_SELECT)
213 self.get_page().set_saved(False)
214 elif state == Actions.ELEMENT_CREATE:
215 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
216 self.handle_states(Actions.NOTHING_SELECT)
217 self.get_page().set_saved(False)
218 elif state == Actions.BLOCK_INC_TYPE:
219 if self.get_flow_graph().type_controller_modify_selected(1):
220 self.get_flow_graph().update()
221 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
222 self.get_page().set_saved(False)
223 elif state == Actions.BLOCK_DEC_TYPE:
224 if self.get_flow_graph().type_controller_modify_selected(-1):
225 self.get_flow_graph().update()
226 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
227 self.get_page().set_saved(False)
228 elif state == Actions.PORT_CONTROLLER_INC:
229 if self.get_flow_graph().port_controller_modify_selected(1):
230 self.get_flow_graph().update()
231 self.get_flow_graph().update() #2 times
232 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
233 self.get_page().set_saved(False)
234 elif state == Actions.PORT_CONTROLLER_DEC:
235 if self.get_flow_graph().port_controller_modify_selected(-1):
236 self.get_flow_graph().update()
237 self.get_flow_graph().update() #2 times
238 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
239 self.get_page().set_saved(False)
240 ##################################################
242 ##################################################
243 elif state == Actions.ABOUT_WINDOW_DISPLAY:
245 ##################################################
246 # Param Modifications
247 ##################################################
248 elif state == Actions.BLOCK_PARAM_MODIFY:
249 if self.get_flow_graph().param_modify_selected():
250 self.get_flow_graph().update()
251 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
252 self.get_page().set_saved(False)
253 ##################################################
255 ##################################################
256 elif state == Actions.FLOW_GRAPH_UNDO:
257 n = self.get_page().get_state_cache().get_prev_state()
259 self.get_flow_graph().unselect()
260 self.get_flow_graph().import_data(n)
261 self.get_flow_graph().update()
262 self.get_page().set_saved(False)
263 elif state == Actions.FLOW_GRAPH_REDO:
264 n = self.get_page().get_state_cache().get_next_state()
266 self.get_flow_graph().unselect()
267 self.get_flow_graph().import_data(n)
268 self.get_flow_graph().update()
269 self.get_page().set_saved(False)
270 ##################################################
271 # New/Open/Save/Close
272 ##################################################
273 elif state == Actions.FLOW_GRAPH_NEW:
274 self.main_window.new_page()
275 elif state == Actions.FLOW_GRAPH_OPEN:
276 file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
277 if file_paths: #open a new page for each file, show only the first
278 for i,file_path in enumerate(file_paths):
279 self.main_window.new_page(file_path, show=(i==0))
280 elif state == Actions.FLOW_GRAPH_CLOSE:
281 self.main_window.close_page()
282 elif state == Actions.FLOW_GRAPH_SAVE:
283 #read-only or undefined file path, do save-as
284 if self.get_page().get_read_only() or not self.get_page().get_file_path():
285 self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
286 #otherwise try to save
289 ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
290 self.get_page().set_saved(True)
292 Messages.send_fail_save(self.get_page().get_file_path())
293 self.get_page().set_saved(False)
294 elif state == Actions.FLOW_GRAPH_SAVE_AS:
295 file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
296 if file_path is not None:
297 self.get_page().set_file_path(file_path)
298 self.handle_states(Actions.FLOW_GRAPH_SAVE)
299 elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
300 file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
301 if file_path is not None:
302 pixmap = self.get_flow_graph().get_drawing_area().pixmap
303 width, height = pixmap.get_size()
304 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height)
305 pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height)
306 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
307 ##################################################
309 ##################################################
310 elif state == Actions.FLOW_GRAPH_GEN:
311 if not self.get_page().get_pid():
312 if not self.get_page().get_saved() or not self.get_page().get_file_path():
313 self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved
314 if self.get_page().get_saved() and self.get_page().get_file_path():
315 generator = self.get_page().get_generator()
317 Messages.send_start_gen(generator.get_file_path())
319 except Exception,e: Messages.send_fail_gen(e)
320 else: self.generator = None
321 elif state == Actions.FLOW_GRAPH_EXEC:
322 if not self.get_page().get_pid():
323 self.handle_states(Actions.FLOW_GRAPH_GEN)
324 if self.get_page().get_saved() and self.get_page().get_file_path():
325 ExecFlowGraphThread(self)
326 elif state == Actions.FLOW_GRAPH_KILL:
327 if self.get_page().get_pid():
328 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
329 except: print "could not kill pid: %s"%self.get_page().get_pid()
330 elif state == '': #pass and run the global actions
332 else: print '!!! State "%s" not handled !!!'%state
333 ##################################################
334 # Global Actions for all States
335 ##################################################
336 #update general buttons
337 Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
338 Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
339 Actions.get_action_from_name(Actions.BLOCK_ROTATE_RIGHT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
340 Actions.get_action_from_name(Actions.BLOCK_ROTATE_LEFT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
341 #update cut/copy/paste
342 Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
343 Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
344 Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard))
345 #update enable/disable
346 Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
347 Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
348 #set the exec and stop buttons
349 self.update_exec_stop()
351 Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
352 self.main_window.update()
354 self.get_flow_graph().draw()
356 def update_exec_stop(self):
358 Update the exec and stop buttons.
359 Lock and unlock the mutex for race conditions with exec flow graph threads.
361 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
362 Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive)
363 Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive)
364 Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
366 class ExecFlowGraphThread(Thread):
367 """Execute the flow graph as a new process and wait on it to finish."""
369 def __init__ (self, action_handler):
371 ExecFlowGraphThread constructor.
372 @param action_handler an instance of an ActionHandler
374 Thread.__init__(self)
375 self.update_exec_stop = action_handler.update_exec_stop
376 self.flow_graph = action_handler.get_flow_graph()
377 #store page and dont use main window calls in run
378 self.page = action_handler.get_page()
379 Messages.send_start_exec(self.page.get_generator().get_file_path())
382 self.p = self.page.get_generator().get_popen()
383 self.page.set_pid(self.p.pid)
385 self.update_exec_stop()
388 Messages.send_verbose_exec(str(e))
389 Messages.send_end_exec()
393 Wait on the executing process by reading from its stdout.
394 Use gobject.idle_add when calling functions that modify gtk objects.
399 gobject.idle_add(Messages.send_verbose_exec, r)
400 r = os.read(self.p.stdout.fileno(), 1024)
401 gobject.idle_add(self.done)
404 """Perform end of execution tasks."""
405 Messages.send_end_exec()
406 self.page.set_pid(None)
407 self.update_exec_stop()