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