ignore irrelevant modifiers and events pending
[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 MainWindow import MainWindow
34 from PropsDialog import PropsDialog
35 import Dialogs
36 from FileDialogs import OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, SaveImageFileDialog
37
38 gobject.threads_init()
39
40 class ActionHandler:
41         """
42         The action handler will setup all the major window components,
43         and handle button presses and flow graph operations from the GUI.
44         """
45
46         def __init__(self, file_paths, platform):
47                 """
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
53                 """
54                 self.clipboard = None
55                 for action in Actions.get_all_actions(): action.connect('activate', self._handle_actions)
56                 #setup the main window
57                 self.main_window = MainWindow(self.handle_states, platform)
58                 self.main_window.connect('delete_event', self._quit)
59                 self.main_window.connect('key-press-event', self._handle_key_press)
60                 self.get_page = self.main_window.get_page
61                 self.get_flow_graph = self.main_window.get_flow_graph
62                 self.get_focus_flag = self.main_window.get_focus_flag
63                 #setup the messages
64                 Messages.register_messenger(self.main_window.add_report_line)
65                 Messages.send_init(platform)
66                 #initialize
67                 self.init_file_paths = file_paths
68                 self.handle_states(Actions.APPLICATION_INITIALIZE)
69                 #enter the mainloop
70                 gtk.main()
71
72         def _handle_key_press(self, widget, event):
73                 """
74                 Handle key presses from the keyboard and translate key combinations into actions.
75                 This key press handler is called prior to the gtk key press handler.
76                 This handler bypasses built in accelerator key handling when in focus because
77                 * some keys are ignored by the accelerators like the direction keys,
78                 * some keys are not registered to any accelerators but are still used.
79                 When not in focus, gtk and the accelerators handle the the key press.
80                 @return false to let gtk handle the key action
81                 """
82                 #extract action name from this key press
83                 key_name = gtk.gdk.keyval_name(event.keyval)
84                 mod_mask = event.state
85                 action_name = Actions.get_action_name_from_key_name(key_name, mod_mask)
86                 #handle the action if flow graph is in focus
87                 if action_name and self.get_focus_flag():
88                         self.handle_states(action_name)
89                         return True #handled by this method
90                 return False #let gtk handle the key press
91
92         def _quit(self, window, event):
93                 """
94                 Handle the delete event from the main window.
95                 Generated by pressing X to close, alt+f4, or right click+close.
96                 This method in turns calls the state handler to quit.
97                 @return true
98                 """
99                 self.handle_states(Actions.APPLICATION_QUIT)
100                 return True
101
102         def _handle_actions(self, event):
103                 """
104                 Handle all of the activate signals from the gtk actions.
105                 The action signals derive from clicking on a toolbar or menu bar button.
106                 Forward the action to the state handler.
107                 """
108                 self.handle_states(event.get_name())
109
110         def handle_states(self, state=''):
111                 """
112                 Handle the state changes in the GUI.
113                 Handle all of the state changes that arise from the action handler or other gui and
114                 inputs in the application. The state passed to the handle_states method is a string descriping
115                 the change. A series of if/elif statements handle the state by greying out action buttons, causing
116                 changes in the flow graph, saving/opening files... The handle_states method is passed to the
117                 contructors of many of the classes used in this application enabling them to report any state change.
118                 @param state a string describing the state change
119                 """
120                 #print state
121                 ##################################################
122                 # Initalize/Quit
123                 ##################################################
124                 if state == Actions.APPLICATION_INITIALIZE:
125                         for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled
126                         # enable a select few actions
127                         for action in (
128                                 Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
129                                 Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
130                                 Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
131                                 Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY,
132                                 Actions.TYPES_WINDOW_DISPLAY,
133                         ): Actions.get_action_from_name(action).set_sensitive(True)
134                         if not self.init_file_paths:
135                                 self.init_file_paths = Preferences.files_open()
136                         if not self.init_file_paths: self.init_file_paths = ['']
137                         for file_path in self.init_file_paths:
138                                 if file_path: self.main_window.new_page(file_path) #load pages from file paths
139                         if Preferences.file_open() in self.init_file_paths:
140                                 self.main_window.new_page(Preferences.file_open(), show=True)
141                         if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
142                 elif state == Actions.APPLICATION_QUIT:
143                         if self.main_window.close_pages():
144                                 gtk.main_quit()
145                                 exit(0)
146                 ##################################################
147                 # Selections
148                 ##################################################
149                 elif state == Actions.ELEMENT_SELECT:
150                         pass #do nothing, update routines below
151                 elif state == Actions.NOTHING_SELECT:
152                         self.get_flow_graph().unselect()
153                 ##################################################
154                 # Enable/Disable
155                 ##################################################
156                 elif state == Actions.BLOCK_ENABLE:
157                         if self.get_flow_graph().enable_selected(True):
158                                 self.get_flow_graph().update()
159                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
160                                 self.get_page().set_saved(False)
161                 elif state == Actions.BLOCK_DISABLE:
162                         if self.get_flow_graph().enable_selected(False):
163                                 self.get_flow_graph().update()
164                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
165                                 self.get_page().set_saved(False)
166                 ##################################################
167                 # Cut/Copy/Paste
168                 ##################################################
169                 elif state == Actions.BLOCK_CUT:
170                         self.handle_states(Actions.BLOCK_COPY)
171                         self.handle_states(Actions.ELEMENT_DELETE)
172                 elif state == Actions.BLOCK_COPY:
173                         self.clipboard = self.get_flow_graph().copy_to_clipboard()
174                 elif state == Actions.BLOCK_PASTE:
175                         if self.clipboard:
176                                 self.get_flow_graph().paste_from_clipboard(self.clipboard)
177                                 self.get_flow_graph().update()
178                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
179                                 self.get_page().set_saved(False)
180                 ##################################################
181                 # Move/Rotate/Delete/Create
182                 ##################################################
183                 elif state == Actions.BLOCK_MOVE:
184                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
185                         self.get_page().set_saved(False)
186                 elif state == Actions.BLOCK_ROTATE_CCW:
187                         if self.get_flow_graph().rotate_selected(90):
188                                 self.get_flow_graph().update()
189                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
190                                 self.get_page().set_saved(False)
191                 elif state == Actions.BLOCK_ROTATE_CW:
192                         if self.get_flow_graph().rotate_selected(-90):
193                                 self.get_flow_graph().update()
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.ELEMENT_DELETE:
197                         if self.get_flow_graph().remove_selected():
198                                 self.get_flow_graph().update()
199                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
200                                 self.handle_states(Actions.NOTHING_SELECT)
201                                 self.get_page().set_saved(False)
202                 elif state == Actions.ELEMENT_CREATE:
203                         self.get_flow_graph().update()
204                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
205                         self.handle_states(Actions.NOTHING_SELECT)
206                         self.get_page().set_saved(False)
207                 elif state == Actions.BLOCK_INC_TYPE:
208                         if self.get_flow_graph().type_controller_modify_selected(1):
209                                 self.get_flow_graph().update()
210                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
211                                 self.get_page().set_saved(False)
212                 elif state == Actions.BLOCK_DEC_TYPE:
213                         if self.get_flow_graph().type_controller_modify_selected(-1):
214                                 self.get_flow_graph().update()
215                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
216                                 self.get_page().set_saved(False)
217                 elif state == Actions.PORT_CONTROLLER_INC:
218                         if self.get_flow_graph().port_controller_modify_selected(1):
219                                 self.get_flow_graph().update()
220                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
221                                 self.get_page().set_saved(False)
222                 elif state == Actions.PORT_CONTROLLER_DEC:
223                         if self.get_flow_graph().port_controller_modify_selected(-1):
224                                 self.get_flow_graph().update()
225                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
226                                 self.get_page().set_saved(False)
227                 ##################################################
228                 # Window stuff
229                 ##################################################
230                 elif state == Actions.ABOUT_WINDOW_DISPLAY:
231                         Dialogs.AboutDialog(self.get_flow_graph().get_parent())
232                 elif state == Actions.HELP_WINDOW_DISPLAY:
233                         Dialogs.HelpDialog()
234                 elif state == Actions.TYPES_WINDOW_DISPLAY:
235                         Dialogs.TypesDialog(self.get_flow_graph().get_parent())
236                 ##################################################
237                 # Param Modifications
238                 ##################################################
239                 elif state == Actions.BLOCK_PARAM_MODIFY:
240                         selected_block = self.get_flow_graph().get_selected_block()
241                         if selected_block:
242                                 if PropsDialog(selected_block).run():
243                                         #save the new state
244                                         self.get_flow_graph().update()
245                                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
246                                         self.get_page().set_saved(False)
247                                 else:
248                                         #restore the current state
249                                         n = self.get_page().get_state_cache().get_current_state()
250                                         self.get_flow_graph().import_data(n)
251                                         self.get_flow_graph().update()
252                 ##################################################
253                 # Undo/Redo
254                 ##################################################
255                 elif state == Actions.FLOW_GRAPH_UNDO:
256                         n = self.get_page().get_state_cache().get_prev_state()
257                         if n:
258                                 self.get_flow_graph().unselect()
259                                 self.get_flow_graph().import_data(n)
260                                 self.get_flow_graph().update()
261                                 self.get_page().set_saved(False)
262                 elif state == Actions.FLOW_GRAPH_REDO:
263                         n = self.get_page().get_state_cache().get_next_state()
264                         if n:
265                                 self.get_flow_graph().unselect()
266                                 self.get_flow_graph().import_data(n)
267                                 self.get_flow_graph().update()
268                                 self.get_page().set_saved(False)
269                 ##################################################
270                 # New/Open/Save/Close
271                 ##################################################
272                 elif state == Actions.FLOW_GRAPH_NEW:
273                         self.main_window.new_page()
274                 elif state == Actions.FLOW_GRAPH_OPEN:
275                         file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
276                         if file_paths: #open a new page for each file, show only the first
277                                 for i,file_path in enumerate(file_paths):
278                                         self.main_window.new_page(file_path, show=(i==0))
279                 elif state == Actions.FLOW_GRAPH_CLOSE:
280                         self.main_window.close_page()
281                 elif state == Actions.FLOW_GRAPH_SAVE:
282                         #read-only or undefined file path, do save-as
283                         if self.get_page().get_read_only() or not self.get_page().get_file_path():
284                                 self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
285                         #otherwise try to save
286                         else:
287                                 try:
288                                         ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
289                                         self.get_page().set_saved(True)
290                                 except IOError:
291                                         Messages.send_fail_save(self.get_page().get_file_path())
292                                         self.get_page().set_saved(False)
293                 elif state == Actions.FLOW_GRAPH_SAVE_AS:
294                         file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
295                         if file_path is not None:
296                                 self.get_page().set_file_path(file_path)
297                                 self.handle_states(Actions.FLOW_GRAPH_SAVE)
298                 elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
299                         file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
300                         if file_path is not None:
301                                 pixbuf = self.get_flow_graph().get_drawing_area().get_pixbuf()
302                                 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
303                 ##################################################
304                 # Gen/Exec/Stop
305                 ##################################################
306                 elif state == Actions.FLOW_GRAPH_GEN:
307                         if not self.get_page().get_pid():
308                                 if not self.get_page().get_saved() or not self.get_page().get_file_path():
309                                         self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved
310                                 if self.get_page().get_saved() and self.get_page().get_file_path():
311                                         generator = self.get_page().get_generator()
312                                         try:
313                                                 Messages.send_start_gen(generator.get_file_path())
314                                                 generator.write()
315                                         except Exception,e: Messages.send_fail_gen(e)
316                                 else: self.generator = None
317                 elif state == Actions.FLOW_GRAPH_EXEC:
318                         if not self.get_page().get_pid():
319                                 self.handle_states(Actions.FLOW_GRAPH_GEN)
320                                 if self.get_page().get_saved() and self.get_page().get_file_path():
321                                         ExecFlowGraphThread(self)
322                 elif state == Actions.FLOW_GRAPH_KILL:
323                         if self.get_page().get_pid():
324                                 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
325                                 except: print "could not kill pid: %s"%self.get_page().get_pid()
326                 elif state == '': #pass and run the global actions
327                         pass
328                 else: print '!!! State "%s" not handled !!!'%state
329                 ##################################################
330                 # Global Actions for all States
331                 ##################################################
332                 #update general buttons
333                 Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
334                 Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
335                 Actions.get_action_from_name(Actions.BLOCK_ROTATE_CCW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
336                 Actions.get_action_from_name(Actions.BLOCK_ROTATE_CW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
337                 #update cut/copy/paste
338                 Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
339                 Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
340                 Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard))
341                 #update enable/disable
342                 Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
343                 Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
344                 #set the exec and stop buttons
345                 self.update_exec_stop()
346                 #saved status
347                 Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
348                 self.main_window.update()
349                 try: #set the size of the flow graph area (if changed)
350                         new_size = self.get_flow_graph().get_option('window_size')
351                         if self.get_flow_graph().get_size() != tuple(new_size):
352                                 self.get_flow_graph().set_size(*new_size)
353                 except: pass
354                 #draw the flow graph
355                 self.get_flow_graph().update_selected()
356                 self.get_flow_graph().queue_draw()
357
358         def update_exec_stop(self):
359                 """
360                 Update the exec and stop buttons.
361                 Lock and unlock the mutex for race conditions with exec flow graph threads.
362                 """
363                 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
364                 Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive)
365                 Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive)
366                 Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
367
368 class ExecFlowGraphThread(Thread):
369         """Execute the flow graph as a new process and wait on it to finish."""
370
371         def __init__ (self, action_handler):
372                 """
373                 ExecFlowGraphThread constructor.
374                 @param action_handler an instance of an ActionHandler
375                 """
376                 Thread.__init__(self)
377                 self.update_exec_stop = action_handler.update_exec_stop
378                 self.flow_graph = action_handler.get_flow_graph()
379                 #store page and dont use main window calls in run
380                 self.page = action_handler.get_page()
381                 Messages.send_start_exec(self.page.get_generator().get_file_path())
382                 #get the popen
383                 try:
384                         self.p = self.page.get_generator().get_popen()
385                         self.page.set_pid(self.p.pid)
386                         #update
387                         self.update_exec_stop()
388                         self.start()
389                 except Exception, e:
390                         Messages.send_verbose_exec(str(e))
391                         Messages.send_end_exec()
392
393         def run(self):
394                 """
395                 Wait on the executing process by reading from its stdout.
396                 Use gobject.idle_add when calling functions that modify gtk objects.
397                 """
398                 #handle completion
399                 r = "\n"
400                 while(r):
401                         gobject.idle_add(Messages.send_verbose_exec, r)
402                         r = os.read(self.p.stdout.fileno(), 1024)
403                 gobject.idle_add(self.done)
404
405         def done(self):
406                 """Perform end of execution tasks."""
407                 Messages.send_end_exec()
408                 self.page.set_pid(None)
409                 self.update_exec_stop()