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 .. utils import ParseXML
33 from .. platforms.gui.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)
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.get_action_from_name(action).set_sensitive(True)
137 if not self.init_file_paths:
138 self.init_file_paths = Preferences.files_open()
139 if not self.init_file_paths: self.init_file_paths = ['']
140 for file_path in self.init_file_paths:
141 if file_path: self.main_window.new_page(file_path) #load pages from file paths
142 if Preferences.file_open() in self.init_file_paths:
143 self.main_window.new_page(Preferences.file_open(), show=True)
144 if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
145 elif state == Actions.APPLICATION_QUIT:
146 if self.main_window.close_pages():
149 ##################################################
151 ##################################################
152 elif state == Actions.ELEMENT_SELECT:
153 pass #do nothing, update routines below
154 elif state == Actions.NOTHING_SELECT:
155 self.get_flow_graph().unselect()
156 ##################################################
158 ##################################################
159 elif state == Actions.BLOCK_ENABLE:
160 if self.get_flow_graph().enable_selected(True):
161 self.get_flow_graph().update()
162 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
163 self.get_page().set_saved(False)
164 elif state == Actions.BLOCK_DISABLE:
165 if self.get_flow_graph().enable_selected(False):
166 self.get_flow_graph().update()
167 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
168 self.get_page().set_saved(False)
169 ##################################################
171 ##################################################
172 elif state == Actions.BLOCK_CUT:
173 self.handle_states(Actions.BLOCK_COPY)
174 self.handle_states(Actions.ELEMENT_DELETE)
175 elif state == Actions.BLOCK_COPY:
176 self.clipboard = self.get_flow_graph().copy_to_clipboard()
177 elif state == Actions.BLOCK_PASTE:
179 self.get_flow_graph().paste_from_clipboard(self.clipboard)
180 self.get_flow_graph().update()
181 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
182 self.get_page().set_saved(False)
183 ##################################################
184 # Move/Rotate/Delete/Create
185 ##################################################
186 elif state == Actions.BLOCK_MOVE:
187 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
188 self.get_page().set_saved(False)
189 elif state == Actions.BLOCK_ROTATE_CCW:
190 if self.get_flow_graph().rotate_selected(90):
191 self.get_flow_graph().update()
192 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
193 self.get_page().set_saved(False)
194 elif state == Actions.BLOCK_ROTATE_CW:
195 if self.get_flow_graph().rotate_selected(-90):
196 self.get_flow_graph().update()
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.ELEMENT_DELETE:
200 if self.get_flow_graph().remove_selected():
201 self.get_flow_graph().update()
202 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
203 self.handle_states(Actions.NOTHING_SELECT)
204 self.get_page().set_saved(False)
205 elif state == Actions.ELEMENT_CREATE:
206 self.get_flow_graph().update()
207 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
208 self.handle_states(Actions.NOTHING_SELECT)
209 self.get_page().set_saved(False)
210 elif state == Actions.BLOCK_INC_TYPE:
211 if self.get_flow_graph().type_controller_modify_selected(1):
212 self.get_flow_graph().update()
213 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
214 self.get_page().set_saved(False)
215 elif state == Actions.BLOCK_DEC_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.PORT_CONTROLLER_INC:
221 if self.get_flow_graph().port_controller_modify_selected(1):
222 self.get_flow_graph().update()
223 self.get_flow_graph().update() #2 times
224 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
225 self.get_page().set_saved(False)
226 elif state == Actions.PORT_CONTROLLER_DEC:
227 if self.get_flow_graph().port_controller_modify_selected(-1):
228 self.get_flow_graph().update()
229 self.get_flow_graph().update() #2 times
230 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
231 self.get_page().set_saved(False)
232 ##################################################
234 ##################################################
235 elif state == Actions.ABOUT_WINDOW_DISPLAY:
236 Dialogs.AboutDialog()
237 elif state == Actions.HELP_WINDOW_DISPLAY:
239 ##################################################
240 # Param Modifications
241 ##################################################
242 elif state == Actions.BLOCK_PARAM_MODIFY:
243 selected_block = self.get_flow_graph().get_selected_block()
244 if selected_block and ParamsDialog(selected_block).run():
245 self.get_flow_graph().update()
246 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
247 self.get_page().set_saved(False)
248 ##################################################
250 ##################################################
251 elif state == Actions.FLOW_GRAPH_UNDO:
252 n = self.get_page().get_state_cache().get_prev_state()
254 self.get_flow_graph().unselect()
255 self.get_flow_graph().import_data(n)
256 self.get_flow_graph().update()
257 self.get_page().set_saved(False)
258 elif state == Actions.FLOW_GRAPH_REDO:
259 n = self.get_page().get_state_cache().get_next_state()
261 self.get_flow_graph().unselect()
262 self.get_flow_graph().import_data(n)
263 self.get_flow_graph().update()
264 self.get_page().set_saved(False)
265 ##################################################
266 # New/Open/Save/Close
267 ##################################################
268 elif state == Actions.FLOW_GRAPH_NEW:
269 self.main_window.new_page()
270 elif state == Actions.FLOW_GRAPH_OPEN:
271 file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
272 if file_paths: #open a new page for each file, show only the first
273 for i,file_path in enumerate(file_paths):
274 self.main_window.new_page(file_path, show=(i==0))
275 elif state == Actions.FLOW_GRAPH_CLOSE:
276 self.main_window.close_page()
277 elif state == Actions.FLOW_GRAPH_SAVE:
278 #read-only or undefined file path, do save-as
279 if self.get_page().get_read_only() or not self.get_page().get_file_path():
280 self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
281 #otherwise try to save
284 ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
285 self.get_page().set_saved(True)
287 Messages.send_fail_save(self.get_page().get_file_path())
288 self.get_page().set_saved(False)
289 elif state == Actions.FLOW_GRAPH_SAVE_AS:
290 file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
291 if file_path is not None:
292 self.get_page().set_file_path(file_path)
293 self.handle_states(Actions.FLOW_GRAPH_SAVE)
294 elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
295 file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
296 if file_path is not None:
297 pixmap = self.get_flow_graph().get_pixmap()
298 width, height = pixmap.get_size()
299 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height)
300 pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height)
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()