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 for action in Actions.get_all_actions(): action.connect('activate', self._handle_actions)
57 #setup the main window
58 self.main_window = MainWindow(self.handle_states, platform)
59 self.main_window.connect('delete_event', self._quit)
60 self.main_window.connect('key_press_event', self._handle_key_press)
61 self.get_page = self.main_window.get_page
62 self.get_flow_graph = self.main_window.get_flow_graph
63 self.get_focus_flag = self.main_window.drawing_area.get_focus_flag
65 Messages.register_messenger(self.main_window.add_report_line)
68 self.init_file_paths = file_paths
69 self.handle_states(Actions.APPLICATION_INITIALIZE)
73 def _handle_key_press(self, widget, event):
75 Handle key presses from the keyboard and translate key combos into actions.
76 This key press handler is called before the gtk accelerators kick in.
77 This handler ensures that key presses without a mod mask,
78 only pass to the accelerators if the flow graph is in focus.
79 This function also handles keys that accelerators refuse to handle: left/right,
80 and keys that are not registered with an accelerator: +/-.
81 @return false to let the accelerators handle the key action
83 if self.get_focus_flag():
86 'Left': Actions.BLOCK_ROTATE_LEFT,
87 'Right': Actions.BLOCK_ROTATE_RIGHT,
88 'Up': Actions.BLOCK_DEC_TYPE,
89 'Down': Actions.BLOCK_INC_TYPE,
90 'equal': Actions.PORT_CONTROLLER_INC,
91 'plus': Actions.PORT_CONTROLLER_INC,
92 'KP_Add': Actions.PORT_CONTROLLER_INC,
93 'minus': Actions.PORT_CONTROLLER_DEC,
94 'KP_Subtract': Actions.PORT_CONTROLLER_DEC,
95 }[gtk.gdk.keyval_name(event.keyval)])
97 #focus: always return false for accelerator to handle
99 #no focus: only allow accelerator to handle when a mod is used
100 return not event.state
102 def _quit(self, window, event):
104 Handle the delete event from the main window.
105 Generated by pressing X to close, alt+f4, or right click+close.
106 This method in turns calls the state handler to quit.
109 self.handle_states(Actions.APPLICATION_QUIT)
112 def _handle_actions(self, event):
114 Handle all of the activate signals from the gtk actions.
115 The action signals derive from clicking on a toolbar or menu bar button.
116 Forward the action to the state handler.
118 self.handle_states(event.get_name())
120 def handle_states(self, state=''):
122 Handle the state changes in the GUI.
123 Handle all of the state changes that arise from the action handler or other gui and
124 inputs in the application. The state passed to the handle_states method is a string descriping
125 the change. A series of if/elif statements handle the state by greying out action buttons, causing
126 changes in the flow graph, saving/opening files... The handle_states method is passed to the
127 contructors of many of the classes used in this application enabling them to report any state change.
128 @param state a string describing the state change
131 ##################################################
133 ##################################################
134 if state == Actions.APPLICATION_INITIALIZE:
135 for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled
136 # enable a select few actions
138 Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
139 Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
140 Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
141 Actions.FLOW_GRAPH_SCREEN_CAPTURE,
142 ): Actions.get_action_from_name(action).set_sensitive(True)
143 if not self.init_file_paths:
144 self.init_file_paths = Preferences.files_open()
145 if not self.init_file_paths: self.init_file_paths = ['']
146 for file_path in self.init_file_paths:
147 if file_path: self.main_window.new_page(file_path) #load pages from file paths
148 if Preferences.file_open() in self.init_file_paths:
149 self.main_window.new_page(Preferences.file_open(), show=True)
150 if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
151 elif state == Actions.APPLICATION_QUIT:
152 if self.main_window.close_pages():
155 ##################################################
157 ##################################################
158 elif state == Actions.ELEMENT_SELECT:
159 self.get_flow_graph().update()
160 elif state == Actions.NOTHING_SELECT:
161 self.get_flow_graph().unselect()
162 self.get_flow_graph().update()
163 ##################################################
165 ##################################################
166 elif state == Actions.BLOCK_ENABLE:
167 if self.get_flow_graph().enable_selected(True):
168 self.get_flow_graph().update()
169 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
170 self.get_page().set_saved(False)
171 elif state == Actions.BLOCK_DISABLE:
172 if self.get_flow_graph().enable_selected(False):
173 self.get_flow_graph().update()
174 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
175 self.get_page().set_saved(False)
176 ##################################################
178 ##################################################
179 elif state == Actions.BLOCK_CUT:
180 self.handle_states(BLOCK_COPY)
181 self.handle_states(ELEMENT_DELETE)
182 elif state == Actions.BLOCK_COPY:
183 self.clipboard = self.get_flow_graph().copy_to_clipboard()
184 elif state == Actions.BLOCK_PASTE:
186 self.get_flow_graph().paste_from_clipboard(self.clipboard)
187 self.get_flow_graph().update()
188 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
189 self.get_page().set_saved(False)
190 ##################################################
191 # Move/Rotate/Delete/Create
192 ##################################################
193 elif state == Actions.BLOCK_MOVE:
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.BLOCK_ROTATE_LEFT:
197 if self.get_flow_graph().rotate_selected(DIR_LEFT):
198 self.get_flow_graph().update()
199 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
200 self.get_page().set_saved(False)
201 elif state == Actions.BLOCK_ROTATE_RIGHT:
202 if self.get_flow_graph().rotate_selected(DIR_RIGHT):
203 self.get_flow_graph().update()
204 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
205 self.get_page().set_saved(False)
206 elif state == Actions.ELEMENT_DELETE:
207 if self.get_flow_graph().remove_selected():
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.ELEMENT_CREATE:
212 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
213 self.handle_states(Actions.NOTHING_SELECT)
214 self.get_page().set_saved(False)
215 elif state == Actions.BLOCK_INC_TYPE:
216 if self.get_flow_graph().type_controller_modify_selected(1):
217 self.get_flow_graph().update()
218 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
219 self.get_page().set_saved(False)
220 elif state == Actions.BLOCK_DEC_TYPE:
221 if self.get_flow_graph().type_controller_modify_selected(-1):
222 self.get_flow_graph().update()
223 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
224 self.get_page().set_saved(False)
225 elif state == Actions.PORT_CONTROLLER_INC:
226 if self.get_flow_graph().port_controller_modify_selected(1):
227 self.get_flow_graph().update()
228 self.get_flow_graph().update() #2 times
229 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
230 self.get_page().set_saved(False)
231 elif state == Actions.PORT_CONTROLLER_DEC:
232 if self.get_flow_graph().port_controller_modify_selected(-1):
233 self.get_flow_graph().update()
234 self.get_flow_graph().update() #2 times
235 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
236 self.get_page().set_saved(False)
237 ##################################################
239 ##################################################
240 elif state == Actions.ABOUT_WINDOW_DISPLAY:
242 ##################################################
243 # Param Modifications
244 ##################################################
245 elif state == Actions.BLOCK_PARAM_MODIFY:
246 if self.get_flow_graph().param_modify_selected():
247 self.get_flow_graph().update()
248 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
249 self.get_page().set_saved(False)
250 ##################################################
252 ##################################################
253 elif state == Actions.FLOW_GRAPH_UNDO:
254 n = self.get_page().get_state_cache().get_prev_state()
256 self.get_flow_graph().unselect()
257 self.get_flow_graph().import_data(n)
258 self.get_flow_graph().update()
259 self.get_page().set_saved(False)
260 elif state == Actions.FLOW_GRAPH_REDO:
261 n = self.get_page().get_state_cache().get_next_state()
263 self.get_flow_graph().unselect()
264 self.get_flow_graph().import_data(n)
265 self.get_flow_graph().update()
266 self.get_page().set_saved(False)
267 ##################################################
268 # New/Open/Save/Close
269 ##################################################
270 elif state == Actions.FLOW_GRAPH_NEW:
271 self.main_window.new_page()
272 elif state == Actions.FLOW_GRAPH_OPEN:
273 file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
274 if file_paths: #open a new page for each file, show only the first
275 for i,file_path in enumerate(file_paths):
276 self.main_window.new_page(file_path, show=(i==0))
277 elif state == Actions.FLOW_GRAPH_CLOSE:
278 self.main_window.close_page()
279 elif state == Actions.FLOW_GRAPH_SAVE:
280 #read-only or undefined file path, do save-as
281 if self.get_page().get_read_only() or not self.get_page().get_file_path():
282 self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
283 #otherwise try to save
286 ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
287 self.get_page().set_saved(True)
289 Messages.send_fail_save(self.get_page().get_file_path())
290 self.get_page().set_saved(False)
291 elif state == Actions.FLOW_GRAPH_SAVE_AS:
292 file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
293 if file_path is not None:
294 self.get_page().set_file_path(file_path)
295 self.handle_states(Actions.FLOW_GRAPH_SAVE)
296 elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
297 file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
298 if file_path is not None:
299 pixmap = self.get_flow_graph().get_drawing_area().pixmap
300 width, height = pixmap.get_size()
301 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height)
302 pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height)
303 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
304 ##################################################
306 ##################################################
307 elif state == Actions.FLOW_GRAPH_GEN:
308 if not self.get_page().get_pid():
309 if not self.get_page().get_saved() or not self.get_page().get_file_path():
310 self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved
311 if self.get_page().get_saved() and self.get_page().get_file_path():
312 generator = self.get_page().get_generator()
314 Messages.send_start_gen(generator.get_file_path())
316 except Exception,e: Messages.send_fail_gen(e)
317 else: self.generator = None
318 elif state == Actions.FLOW_GRAPH_EXEC:
319 if not self.get_page().get_pid():
320 self.handle_states(Actions.FLOW_GRAPH_GEN)
321 if self.get_page().get_saved() and self.get_page().get_file_path():
322 ExecFlowGraphThread(self)
323 elif state == Actions.FLOW_GRAPH_KILL:
324 if self.get_page().get_pid():
325 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
326 except: print "could not kill pid: %s"%self.get_page().get_pid()
327 elif state == '': #pass and run the global actions
329 else: print '!!! State "%s" not handled !!!'%state
330 ##################################################
331 # Global Actions for all States
332 ##################################################
333 #update general buttons
334 Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
335 Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
336 Actions.get_action_from_name(Actions.BLOCK_ROTATE_RIGHT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
337 Actions.get_action_from_name(Actions.BLOCK_ROTATE_LEFT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
338 #update cut/copy/paste
339 Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
340 Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
341 Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard))
342 #update enable/disable
343 Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
344 Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
345 #set the exec and stop buttons
346 self.update_exec_stop()
348 Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
349 self.main_window.update()
351 self.get_flow_graph().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()