Imported Upstream version 3.2.2
[debian/gnuradio] / grc / gui / ActionHandler.py
1 """
2 Copyright 2007, 2008, 2009 Free Software Foundation, Inc.
3 This file is part of GNU Radio
4
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.
9
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.
14
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
18 """
19
20 import os
21 import signal 
22 from Constants import IMAGE_FILE_EXTENSION
23 import Actions
24 import pygtk
25 pygtk.require('2.0')
26 import gtk
27 import gobject
28 import Preferences
29 from threading import Thread
30 import Messages
31 from .. base import ParseXML
32 import random
33 from Platform import Platform
34 from MainWindow import MainWindow
35 from ParamsDialog import ParamsDialog
36 import Dialogs
37 from FileDialogs import OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, SaveImageFileDialog
38
39 gobject.threads_init()
40
41 class ActionHandler:
42         """
43         The action handler will setup all the major window components,
44         and handle button presses and flow graph operations from the GUI.
45         """
46
47         def __init__(self, file_paths, platform):
48                 """
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
54                 """
55                 self.clipboard = None
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
65                 #setup the messages
66                 Messages.register_messenger(self.main_window.add_report_line)
67                 Messages.send_init(platform)
68                 #initialize
69                 self.init_file_paths = file_paths
70                 self.handle_states(Actions.APPLICATION_INITIALIZE)
71                 #enter the mainloop
72                 gtk.main()
73
74         def _handle_key_press(self, widget, event):
75                 """
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
83                 """
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
95
96         def _quit(self, window, event):
97                 """
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.
101                 @return true
102                 """
103                 self.handle_states(Actions.APPLICATION_QUIT)
104                 return True
105
106         def _handle_actions(self, event):
107                 """
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.
111                 """
112                 self.handle_states(event.get_name())
113
114         def handle_states(self, state=''):
115                 """
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
123                 """
124                 #print state
125                 ##################################################
126                 # Initalize/Quit
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
131                         for action in (
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():
148                                 gtk.main_quit()
149                                 exit(0)
150                 ##################################################
151                 # Selections
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                 ##################################################
158                 # Enable/Disable
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                 ##################################################
171                 # Cut/Copy/Paste
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:
179                         if self.clipboard:
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                 ##################################################
234                 # Window stuff
235                 ##################################################
236                 elif state == Actions.ABOUT_WINDOW_DISPLAY:
237                         Dialogs.AboutDialog(self.get_flow_graph().get_parent())
238                 elif state == Actions.HELP_WINDOW_DISPLAY:
239                         Dialogs.HelpDialog()
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                 ##################################################
252                 # Undo/Redo
253                 ##################################################
254                 elif state == Actions.FLOW_GRAPH_UNDO:
255                         n = self.get_page().get_state_cache().get_prev_state()
256                         if n:
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()
263                         if n:
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
285                         else:
286                                 try:
287                                         ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
288                                         self.get_page().set_saved(True)
289                                 except IOError:
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                 ##################################################
303                 # Gen/Exec/Stop
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()
311                                         try:
312                                                 Messages.send_start_gen(generator.get_file_path())
313                                                 generator.write()
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
326                         pass
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()
345                 #saved status
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)
352                 except: pass
353                 #draw the flow graph
354                 self.get_flow_graph().update_selected()
355                 self.get_flow_graph().queue_draw()
356
357         def update_exec_stop(self):
358                 """
359                 Update the exec and stop buttons.
360                 Lock and unlock the mutex for race conditions with exec flow graph threads.
361                 """
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)
366
367 class ExecFlowGraphThread(Thread):
368         """Execute the flow graph as a new process and wait on it to finish."""
369
370         def __init__ (self, action_handler):
371                 """
372                 ExecFlowGraphThread constructor.
373                 @param action_handler an instance of an ActionHandler
374                 """
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())
381                 #get the popen
382                 try:
383                         self.p = self.page.get_generator().get_popen()
384                         self.page.set_pid(self.p.pid)
385                         #update
386                         self.update_exec_stop()
387                         self.start()
388                 except Exception, e:
389                         Messages.send_verbose_exec(str(e))
390                         Messages.send_end_exec()
391
392         def run(self):
393                 """
394                 Wait on the executing process by reading from its stdout.
395                 Use gobject.idle_add when calling functions that modify gtk objects.
396                 """
397                 #handle completion
398                 r = "\n"
399                 while(r):
400                         gobject.idle_add(Messages.send_verbose_exec, r)
401                         r = os.read(self.p.stdout.fileno(), 1024)
402                 gobject.idle_add(self.done)
403
404         def done(self):
405                 """Perform end of execution tasks."""
406                 Messages.send_end_exec()
407                 self.page.set_pid(None)
408                 self.update_exec_stop()