another distcheck fix
[debian/gnuradio] / grc / src / grc / ActionHandler.py
1 """
2 Copyright 2007 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 ##@package ActionHandler
20 #ActionHandler builds the interface and handles most of the user inputs.
21
22 import os
23 import signal
24 from Constants import *
25 from Actions import *
26 import pygtk
27 pygtk.require('2.0')
28 import gtk
29 import gui
30 import Preferences
31 from threading import Thread
32 import Messages
33 import ParseXML
34 import random
35 from grc.gui.elements.Platform import Platform
36
37 class ActionHandler:
38         """
39         The action handler will setup all the major window components,
40         and handle button presses and flow graph operations from the GUI.
41         """
42
43         def __init__(self, file_paths, platform):
44                 """!
45                 ActionHandler constructor.
46                 Create the main window, setup the message handler, import the preferences,
47                 and connect all of the action handlers. Finally, enter the gtk main loop and block.
48                 @param file_paths a list of flow graph file passed from command line
49                 @param platform platform module
50                 """
51                 self.clipboard = None
52                 platform = Platform(platform)
53                 if PY_GTK_ICON: gtk.window_set_default_icon_from_file(PY_GTK_ICON)
54                 for action in ACTIONS_LIST: action.connect('activate', self._handle_actions)
55                 #setup the main window
56                 self.main_window = gui.MainWindow(self.handle_states, platform)
57                 self.main_window.connect('delete_event', self._quit)
58                 self.main_window.connect('key_press_event', self._handle_key_press)
59                 self.get_page = self.main_window.get_page
60                 self.get_flow_graph = self.main_window.get_flow_graph
61                 self.get_focus_flag = self.main_window.drawing_area.get_focus_flag
62                 #setup the messages
63                 Messages.register_messenger(self.main_window.add_report_line)
64                 Messages.send_init()
65                 #initialize
66                 self.init_file_paths = file_paths
67                 self.handle_states(APPLICATION_INITIALIZE)
68                 #enter the mainloop
69                 gtk.gdk.threads_init()
70                 gtk.main()
71
72         def _handle_key_press(self, widget, event):
73                 """
74                 Handle key presses from the keyboard.
75                 Translate key combos into actions.
76                 Key combinations that do not include special keys, such as ctrl or Fcn*,
77                 Also, require that the flow graph has mouse focus when choosing to handle keys.
78                 @return true if the flow graph is in active use
79                 """
80                 keyname = gtk.gdk.keyval_name(event.keyval)
81                 ctrl = event.state & gtk.gdk.CONTROL_MASK
82                 alt = event.state & gtk.gdk.MOD1_MASK
83                 shift = event.state & gtk.gdk.SHIFT_MASK
84                 #################### save/open/new/close ###############################
85                 if ctrl and keyname == 's':
86                         self.handle_states(FLOW_GRAPH_SAVE)
87                 elif ctrl and keyname == 'o':
88                         self.handle_states(FLOW_GRAPH_OPEN)
89                 elif ctrl and keyname == 'n':
90                         self.handle_states(FLOW_GRAPH_NEW)
91                 elif ctrl and keyname == 'q':
92                         self.handle_states(FLOW_GRAPH_CLOSE)
93                 #################### Cut/Copy/Paste ###############################
94                 elif self.get_focus_flag() and ctrl and keyname == 'x': #mouse focus
95                         self.handle_states(BLOCK_CUT)
96                 elif self.get_focus_flag() and ctrl and keyname == 'c': #mouse focus
97                         self.handle_states(BLOCK_COPY)
98                 elif self.get_focus_flag() and ctrl and keyname == 'v': #mouse focus
99                         self.handle_states(BLOCK_PASTE)
100                 #################### Undo/Redo ###############################
101                 elif ctrl and keyname == 'z':
102                         self.handle_states(FLOW_GRAPH_UNDO)
103                 elif ctrl and keyname == 'y':
104                         self.handle_states(FLOW_GRAPH_REDO)
105                 #################### Delete ###############################
106                 elif self.get_focus_flag() and keyname == 'Delete':     #mouse focus
107                         self.handle_states(ELEMENT_DELETE)
108                 #################### Params     ###############################
109                 elif self.get_focus_flag() and keyname == 'Return':     #mouse focus
110                         self.handle_states(BLOCK_PARAM_MODIFY)
111                 #################### Rotate ###############################
112                 elif self.get_focus_flag() and keyname == 'Right': #mouse focus
113                         self.handle_states(BLOCK_ROTATE_RIGHT)
114                 elif self.get_focus_flag() and keyname == 'Left': #mouse focus
115                         self.handle_states(BLOCK_ROTATE_LEFT)
116                 #################### Enable/Disable ###############################
117                 elif self.get_focus_flag() and keyname == 'e': #mouse focus
118                         self.handle_states(BLOCK_ENABLE)
119                 elif self.get_focus_flag() and keyname == 'd': #mouse focus
120                         self.handle_states(BLOCK_DISABLE)
121                 #################### Data Type ###############################
122                 elif self.get_focus_flag() and keyname == 'Down': #mouse focus
123                         self.handle_states(BLOCK_INC_TYPE)
124                 elif self.get_focus_flag() and keyname == 'Up': #mouse focus
125                         self.handle_states(BLOCK_DEC_TYPE)
126                 #################### Port Controllers ###############################
127                 elif self.get_focus_flag() and keyname in ('equal','plus', 'KP_Add'): #mouse focus
128                         self.handle_states(PORT_CONTROLLER_INC)
129                 elif self.get_focus_flag() and keyname in ('minus', 'KP_Subtract'): #mouse focus
130                         self.handle_states(PORT_CONTROLLER_DEC)
131                 #################### Gen/Exec/Stop/Print ###############################
132                 elif keyname == 'F5':
133                         self.handle_states(FLOW_GRAPH_GEN)
134                 elif keyname == 'F6':
135                         self.handle_states(FLOW_GRAPH_EXEC)
136                 elif keyname == 'F7':
137                         self.handle_states(FLOW_GRAPH_KILL)
138                 elif keyname == 'Print':
139                         self.handle_states(FLOW_GRAPH_SCREEN_CAPTURE)
140                 #propagate this if the fg is not in focus or nothing is selected
141                 return self.get_focus_flag() and self.get_flow_graph().is_selected()
142
143         def _quit(self, window, event):
144                 """!
145                 Handle the delete event from the main window.
146                 Generated by pressing X to close, alt+f4, or right click+close.
147                 This method in turns calls the state handler to quit.
148                 @return true
149                 """
150                 self.handle_states(APPLICATION_QUIT)
151                 return True
152
153         def _handle_actions(self, event):
154                 """
155                 Handle all of the activate signals from the gtk actions.
156                 The action signals derive from clicking on a toolbar or menu bar button.
157                 Forward the action to the state handler.
158                 """
159                 self.handle_states(event.get_name())
160
161         def handle_states(self, state=''):
162                 """!
163                 Handle the state changes in the GUI.
164                 Handle all of the state changes that arise from the action handler or other gui and
165                 inputs in the application. The state passed to the handle_states method is a string descriping
166                 the change. A series of if/elif statements handle the state by greying out action buttons, causing
167                 changes in the flow graph, saving/opening files... The handle_states method is passed to the
168                 contructors of many of the classes used in this application enabling them to report any state change.
169                 @param state a string describing the state change
170                 """
171                 #print state
172                 ##############################################################################################
173                 # Initalize/Quit
174                 ##############################################################################################
175                 if state == APPLICATION_INITIALIZE:
176                         for action in ACTIONS_LIST: action.set_sensitive(False) #set all actions disabled
177                         # enable a select few actions
178                         for action in (
179                                 APPLICATION_QUIT, FLOW_GRAPH_NEW, FLOW_GRAPH_OPEN, FLOW_GRAPH_SAVE_AS, FLOW_GRAPH_CLOSE,
180                                 ABOUT_WINDOW_DISPLAY, HOTKEYS_WINDOW_DISPLAY,
181                                 PREFS_WINDOW_DISPLAY, FLOW_GRAPH_SCREEN_CAPTURE,
182                         ): get_action_from_name(action).set_sensitive(True)
183                         if not self.init_file_paths and Preferences.restore_files(): self.init_file_paths = Preferences.files_open()
184                         if not self.init_file_paths: self.init_file_paths = ['']
185                         for file_path in self.init_file_paths:
186                                 if file_path: self.main_window.new_page(file_path) #load pages from file paths
187                         if Preferences.file_open() in self.init_file_paths: self.main_window.new_page(Preferences.file_open(), show=True)
188                         if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
189                 elif state == APPLICATION_QUIT:
190                         if self.main_window.close_pages():
191                                 gtk.main_quit()
192                                 exit(0)
193                 ##############################################################################################
194                 # Selections
195                 ##############################################################################################
196                 elif state == ELEMENT_SELECT:
197                         self.get_flow_graph().update()
198                 elif state == NOTHING_SELECT:
199                         self.get_flow_graph().unselect()
200                         self.get_flow_graph().update()
201                 ##############################################################################################
202                 # Enable/Disable
203                 ##############################################################################################
204                 elif state == BLOCK_ENABLE:
205                         if self.get_flow_graph().enable_selected(True):
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 == BLOCK_DISABLE:
210                         if self.get_flow_graph().enable_selected(False):
211                                 self.get_flow_graph().update()
212                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
213                                 self.get_page().set_saved(False)
214                 ##############################################################################################
215                 # Cut/Copy/Paste
216                 ##############################################################################################
217                 elif state == BLOCK_CUT:
218                         self.handle_states(BLOCK_COPY)
219                         self.handle_states(ELEMENT_DELETE)
220                 elif state == BLOCK_COPY:
221                         self.clipboard = self.get_flow_graph().copy_to_clipboard()
222                 elif state == BLOCK_PASTE:
223                         if self.clipboard:
224                                 self.get_flow_graph().paste_from_clipboard(self.clipboard)
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                 ##############################################################################################
229                 # Move/Rotate/Delete/Create
230                 ##############################################################################################
231                 elif state == BLOCK_MOVE:
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 == BLOCK_ROTATE_LEFT:
235                         if self.get_flow_graph().rotate_selected(DIR_LEFT):
236                                 self.get_flow_graph().update()
237                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
238                                 self.get_page().set_saved(False)
239                 elif state == BLOCK_ROTATE_RIGHT:
240                         if self.get_flow_graph().rotate_selected(DIR_RIGHT):
241                                 self.get_flow_graph().update()
242                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
243                                 self.get_page().set_saved(False)
244                 elif state == ELEMENT_DELETE:
245                         if self.get_flow_graph().remove_selected():
246                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
247                                 self.handle_states(NOTHING_SELECT)
248                                 self.get_page().set_saved(False)
249                 elif state == ELEMENT_CREATE:
250                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
251                         self.handle_states(NOTHING_SELECT)
252                         self.get_page().set_saved(False)
253                 elif state == BLOCK_INC_TYPE:
254                         if self.get_flow_graph().type_controller_modify_selected(1):
255                                 self.get_flow_graph().update()
256                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
257                                 self.get_page().set_saved(False)
258                 elif state == BLOCK_DEC_TYPE:
259                         if self.get_flow_graph().type_controller_modify_selected(-1):
260                                 self.get_flow_graph().update()
261                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
262                                 self.get_page().set_saved(False)
263                 elif state == PORT_CONTROLLER_INC:
264                         if self.get_flow_graph().port_controller_modify_selected(1):
265                                 self.get_flow_graph().update()
266                                 self.get_flow_graph().update() #2 times
267                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
268                                 self.get_page().set_saved(False)
269                 elif state == PORT_CONTROLLER_DEC:
270                         if self.get_flow_graph().port_controller_modify_selected(-1):
271                                 self.get_flow_graph().update()
272                                 self.get_flow_graph().update() #2 times
273                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
274                                 self.get_page().set_saved(False)
275                 ##############################################################################################
276                 # Window stuff
277                 ##############################################################################################
278                 elif state == PREFS_WINDOW_DISPLAY:
279                         gui.PreferencesDialog()
280                         self.get_flow_graph().update()
281                 elif state == ABOUT_WINDOW_DISPLAY:
282                         gui.AboutDialog()
283                 elif state == HOTKEYS_WINDOW_DISPLAY:
284                         gui.HotKeysDialog()
285                 ##############################################################################################
286                 # Param Modifications
287                 ##############################################################################################
288                 elif state == BLOCK_PARAM_MODIFY:
289                         if self.get_flow_graph().param_modify_selected():
290                                 self.get_flow_graph().update()
291                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
292                                 self.get_page().set_saved(False)
293                 ##############################################################################################
294                 # Undo/Redo
295                 ##############################################################################################
296                 elif state == FLOW_GRAPH_UNDO:
297                         n = self.get_page().get_state_cache().get_prev_state()
298                         if n:
299                                 self.get_flow_graph().unselect()
300                                 self.get_flow_graph().import_data(n)
301                                 self.get_flow_graph().update()
302                                 self.get_page().set_saved(False)
303                 elif state == FLOW_GRAPH_REDO:
304                         n = self.get_page().get_state_cache().get_next_state()
305                         if n:
306                                 self.get_flow_graph().unselect()
307                                 self.get_flow_graph().import_data(n)
308                                 self.get_flow_graph().update()
309                                 self.get_page().set_saved(False)
310                 ##############################################################################################
311                 # New/Open/Save/Close
312                 ##############################################################################################
313                 elif state == FLOW_GRAPH_NEW:
314                         self.main_window.new_page()
315                 elif state == FLOW_GRAPH_OPEN:
316                         file_paths = gui.OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
317                         if file_paths: #open a new page for each file, show only the first
318                                 for i,file_path in enumerate(file_paths):
319                                         self.main_window.new_page(file_path, show=(i==0))
320                 elif state == FLOW_GRAPH_CLOSE:
321                         self.main_window.close_page()
322                 elif state == FLOW_GRAPH_SAVE:
323                         if not self.get_page().get_file_path(): self.handle_states(FLOW_GRAPH_SAVE_AS)
324                         else:
325                                 try:
326                                         ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
327                                         self.get_page().set_saved(True)
328                                 except IOError:
329                                         Messages.send_fail_save(self.get_page().get_file_path())
330                                         self.get_page().set_saved(False)
331                 elif state == FLOW_GRAPH_SAVE_AS:
332                         file_path = gui.SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
333                         if file_path != None:
334                                 self.get_page().set_file_path(file_path)
335                                 self.handle_states(FLOW_GRAPH_SAVE)
336                 elif state == FLOW_GRAPH_SCREEN_CAPTURE:
337                         file_path = gui.SaveImageFileDialog(self.get_page().get_file_path()).run()
338                         if file_path != None:
339                                 pixmap = self.get_flow_graph().get_drawing_area().pixmap
340                                 width, height = pixmap.get_size()
341                                 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height)
342                                 pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height)
343                                 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
344                 ##############################################################################################
345                 # Gen/Exec/Stop
346                 ##############################################################################################
347                 elif state == FLOW_GRAPH_GEN:
348                         if not self.get_page().get_pid():
349                                 if not self.get_page().get_saved() or not self.get_page().get_file_path():
350                                         self.handle_states(FLOW_GRAPH_SAVE) #only save if file path missing or not saved
351                                 if self.get_page().get_saved() and self.get_page().get_file_path():
352                                         generator = self.get_page().get_generator()
353                                         try:
354                                                 Messages.send_start_gen(generator.get_file_path())
355                                                 generator.write()
356                                         except Exception,e: Messages.send_fail_gen(e)
357                                 else: self.generator = None
358                 elif state == FLOW_GRAPH_EXEC:
359                         if not self.get_page().get_pid():
360                                 self.handle_states(FLOW_GRAPH_GEN)
361                                 if self.get_page().get_saved() and self.get_page().get_file_path():
362                                         ExecFlowGraphThread(self)
363                 elif state == FLOW_GRAPH_KILL:
364                         if self.get_page().get_pid():
365                                 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
366                                 except: print "could not kill pid: %s"%self.get_page().get_pid()
367                 elif state == '': #pass and run the global actions
368                         pass
369                 else: print '!!! State "%s" not handled !!!'%state
370                 ##############################################################################################
371                 # Global Actions for all States
372                 ##############################################################################################
373                 #update general buttons
374                 get_action_from_name(ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
375                 get_action_from_name(BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
376                 get_action_from_name(BLOCK_ROTATE_RIGHT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
377                 get_action_from_name(BLOCK_ROTATE_LEFT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
378                 #update cut/copy/paste
379                 get_action_from_name(BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
380                 get_action_from_name(BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
381                 get_action_from_name(BLOCK_PASTE).set_sensitive(bool(self.clipboard))
382                 #update enable/disable
383                 get_action_from_name(BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
384                 get_action_from_name(BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
385                 #set the exec and stop buttons
386                 self.update_exec_stop()
387                 #saved status
388                 get_action_from_name(FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
389                 self.main_window.update()
390                 #draw the flow graph
391                 self.get_flow_graph().draw()
392
393         def update_exec_stop(self):
394                 """
395                 Update the exec and stop buttons.
396                 Lock and unlock the mutex for race conditions with exec flow graph threads.
397                 """
398                 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
399                 get_action_from_name(FLOW_GRAPH_GEN).set_sensitive(sensitive)
400                 get_action_from_name(FLOW_GRAPH_EXEC).set_sensitive(sensitive)
401                 get_action_from_name(FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
402
403 class ExecFlowGraphThread(Thread):
404         """Execute the flow graph as a new process and wait on it to finish."""
405         def __init__ (self, action_handler):
406                 """!
407                 ExecFlowGraphThread constructor.
408                 @param action_handler an instance of an ActionHandler
409                 """
410                 Thread.__init__(self)
411                 self.update_exec_stop = action_handler.update_exec_stop
412                 self.flow_graph = action_handler.get_flow_graph()
413                 #store page and dont use main window calls in run
414                 self.page = action_handler.get_page()
415                 Messages.send_start_exec(self.page.get_generator().get_file_path())
416                 #get the popen
417                 try:
418                         self.p = self.page.get_generator().get_popen()
419                         self.page.set_pid(self.p.pid)
420                         #update
421                         self.update_exec_stop()
422                         self.start()
423                 except Exception, e:
424                         Messages.send_verbose_exec(str(e))
425                         Messages.send_end_exec()
426
427         def run(self):
428                 """Wait on the flow graph."""
429                 #handle completion
430                 r = "\n"
431                 while(r):
432                         gtk.gdk.threads_enter()
433                         Messages.send_verbose_exec(r)
434                         gtk.gdk.threads_leave()
435                         r = os.read(self.p.stdout.fileno(), 1024)
436                 gtk.gdk.threads_enter()
437                 Messages.send_end_exec()
438                 self.page.set_pid(None)
439                 self.update_exec_stop()
440                 gtk.gdk.threads_leave()
441