Merged r9481:9518 on jblum/grc_reorganize into trunk. Reorganized grc source under...
[debian/gnuradio] / grc / src / gui / 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
20 import os
21 import signal
22 from .. platforms.base.Constants import PY_GTK_ICON, IMAGE_FILE_EXTENSION
23 from Constants import DIR_LEFT, DIR_RIGHT
24 import Actions
25 import pygtk
26 pygtk.require('2.0')
27 import gtk
28 import Preferences
29 from threading import Thread
30 import Messages
31 from .. utils import ParseXML
32 import random
33 from .. platforms.gui.Platform import Platform
34 from MainWindow import MainWindow
35 from Dialogs import PreferencesDialog, AboutDialog, HotKeysDialog
36 from FileDialogs import OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, SaveImageFileDialog
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.ACTIONS_LIST: 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.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(Actions.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(Actions.FLOW_GRAPH_SAVE)
88                 elif ctrl and keyname == 'o':
89                         self.handle_states(Actions.FLOW_GRAPH_OPEN)
90                 elif ctrl and keyname == 'n':
91                         self.handle_states(Actions.FLOW_GRAPH_NEW)
92                 elif ctrl and keyname == 'q':
93                         self.handle_states(Actions.FLOW_GRAPH_CLOSE)
94                 #################### Cut/Copy/Paste ###############################
95                 elif self.get_focus_flag() and ctrl and keyname == 'x': #mouse focus
96                         self.handle_states(Actions.BLOCK_CUT)
97                 elif self.get_focus_flag() and ctrl and keyname == 'c': #mouse focus
98                         self.handle_states(Actions.BLOCK_COPY)
99                 elif self.get_focus_flag() and ctrl and keyname == 'v': #mouse focus
100                         self.handle_states(Actions.BLOCK_PASTE)
101                 #################### Undo/Redo ###############################
102                 elif ctrl and keyname == 'z':
103                         self.handle_states(Actions.FLOW_GRAPH_UNDO)
104                 elif ctrl and keyname == 'y':
105                         self.handle_states(Actions.FLOW_GRAPH_REDO)
106                 #################### Delete ###############################
107                 elif self.get_focus_flag() and keyname == 'Delete':     #mouse focus
108                         self.handle_states(Actions.ELEMENT_DELETE)
109                 #################### Params     ###############################
110                 elif self.get_focus_flag() and keyname == 'Return':     #mouse focus
111                         self.handle_states(Actions.BLOCK_PARAM_MODIFY)
112                 #################### Rotate ###############################
113                 elif self.get_focus_flag() and keyname == 'Right': #mouse focus
114                         self.handle_states(Actions.BLOCK_ROTATE_RIGHT)
115                 elif self.get_focus_flag() and keyname == 'Left': #mouse focus
116                         self.handle_states(Actions.BLOCK_ROTATE_LEFT)
117                 #################### Enable/Disable ###############################
118                 elif self.get_focus_flag() and keyname == 'e': #mouse focus
119                         self.handle_states(Actions.BLOCK_ENABLE)
120                 elif self.get_focus_flag() and keyname == 'd': #mouse focus
121                         self.handle_states(Actions.BLOCK_DISABLE)
122                 #################### Data Type ###############################
123                 elif self.get_focus_flag() and keyname == 'Down': #mouse focus
124                         self.handle_states(Actions.BLOCK_INC_TYPE)
125                 elif self.get_focus_flag() and keyname == 'Up': #mouse focus
126                         self.handle_states(Actions.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(Actions.PORT_CONTROLLER_INC)
130                 elif self.get_focus_flag() and keyname in ('minus', 'KP_Subtract'): #mouse focus
131                         self.handle_states(Actions.PORT_CONTROLLER_DEC)
132                 #################### Gen/Exec/Stop/Print ###############################
133                 elif keyname == 'F5':
134                         self.handle_states(Actions.FLOW_GRAPH_GEN)
135                 elif keyname == 'F6':
136                         self.handle_states(Actions.FLOW_GRAPH_EXEC)
137                 elif keyname == 'F7':
138                         self.handle_states(Actions.FLOW_GRAPH_KILL)
139                 elif keyname == 'Print':
140                         self.handle_states(Actions.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(Actions.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 == Actions.APPLICATION_INITIALIZE:
177                         for action in Actions.ACTIONS_LIST: action.set_sensitive(False) #set all actions disabled
178                         # enable a select few actions
179                         for action in (
180                                 Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
181                                 Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
182                                 Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
183                                 Actions.HOTKEYS_WINDOW_DISPLAY, Actions.PREFS_WINDOW_DISPLAY,
184                                 Actions.FLOW_GRAPH_SCREEN_CAPTURE,
185                         ): Actions.get_action_from_name(action).set_sensitive(True)
186                         if not self.init_file_paths and Preferences.restore_files():
187                                 self.init_file_paths = Preferences.files_open()
188                         if not self.init_file_paths: self.init_file_paths = ['']
189                         for file_path in self.init_file_paths:
190                                 if file_path: self.main_window.new_page(file_path) #load pages from file paths
191                         if Preferences.file_open() in self.init_file_paths:
192                                 self.main_window.new_page(Preferences.file_open(), show=True)
193                         if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
194                 elif state == Actions.APPLICATION_QUIT:
195                         if self.main_window.close_pages():
196                                 gtk.main_quit()
197                                 exit(0)
198                 ##################################################
199                 # Selections
200                 ##################################################
201                 elif state == Actions.ELEMENT_SELECT:
202                         self.get_flow_graph().update()
203                 elif state == Actions.NOTHING_SELECT:
204                         self.get_flow_graph().unselect()
205                         self.get_flow_graph().update()
206                 ##################################################
207                 # Enable/Disable
208                 ##################################################
209                 elif state == Actions.BLOCK_ENABLE:
210                         if self.get_flow_graph().enable_selected(True):
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                 elif state == Actions.BLOCK_DISABLE:
215                         if self.get_flow_graph().enable_selected(False):
216                                 self.get_flow_graph().update()
217                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
218                                 self.get_page().set_saved(False)
219                 ##################################################
220                 # Cut/Copy/Paste
221                 ##################################################
222                 elif state == Actions.BLOCK_CUT:
223                         self.handle_states(BLOCK_COPY)
224                         self.handle_states(ELEMENT_DELETE)
225                 elif state == Actions.BLOCK_COPY:
226                         self.clipboard = self.get_flow_graph().copy_to_clipboard()
227                 elif state == Actions.BLOCK_PASTE:
228                         if self.clipboard:
229                                 self.get_flow_graph().paste_from_clipboard(self.clipboard)
230                                 self.get_flow_graph().update()
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                 # Move/Rotate/Delete/Create
235                 ##################################################
236                 elif state == Actions.BLOCK_MOVE:
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 == Actions.BLOCK_ROTATE_LEFT:
240                         if self.get_flow_graph().rotate_selected(DIR_LEFT):
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 == Actions.BLOCK_ROTATE_RIGHT:
245                         if self.get_flow_graph().rotate_selected(DIR_RIGHT):
246                                 self.get_flow_graph().update()
247                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
248                                 self.get_page().set_saved(False)
249                 elif state == Actions.ELEMENT_DELETE:
250                         if self.get_flow_graph().remove_selected():
251                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
252                                 self.handle_states(Actions.NOTHING_SELECT)
253                                 self.get_page().set_saved(False)
254                 elif state == Actions.ELEMENT_CREATE:
255                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
256                         self.handle_states(Actions.NOTHING_SELECT)
257                         self.get_page().set_saved(False)
258                 elif state == Actions.BLOCK_INC_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 == Actions.BLOCK_DEC_TYPE:
264                         if self.get_flow_graph().type_controller_modify_selected(-1):
265                                 self.get_flow_graph().update()
266                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
267                                 self.get_page().set_saved(False)
268                 elif state == Actions.PORT_CONTROLLER_INC:
269                         if self.get_flow_graph().port_controller_modify_selected(1):
270                                 self.get_flow_graph().update()
271                                 self.get_flow_graph().update() #2 times
272                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
273                                 self.get_page().set_saved(False)
274                 elif state == Actions.PORT_CONTROLLER_DEC:
275                         if self.get_flow_graph().port_controller_modify_selected(-1):
276                                 self.get_flow_graph().update()
277                                 self.get_flow_graph().update() #2 times
278                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
279                                 self.get_page().set_saved(False)
280                 ##################################################
281                 # Window stuff
282                 ##################################################
283                 elif state == Actions.PREFS_WINDOW_DISPLAY:
284                         PreferencesDialog()
285                         self.get_flow_graph().update()
286                 elif state == Actions.ABOUT_WINDOW_DISPLAY:
287                         AboutDialog()
288                 elif state == Actions.HOTKEYS_WINDOW_DISPLAY:
289                         HotKeysDialog()
290                 ##################################################
291                 # Param Modifications
292                 ##################################################
293                 elif state == Actions.BLOCK_PARAM_MODIFY:
294                         if self.get_flow_graph().param_modify_selected():
295                                 self.get_flow_graph().update()
296                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
297                                 self.get_page().set_saved(False)
298                 ##################################################
299                 # Undo/Redo
300                 ##################################################
301                 elif state == Actions.FLOW_GRAPH_UNDO:
302                         n = self.get_page().get_state_cache().get_prev_state()
303                         if n:
304                                 self.get_flow_graph().unselect()
305                                 self.get_flow_graph().import_data(n)
306                                 self.get_flow_graph().update()
307                                 self.get_page().set_saved(False)
308                 elif state == Actions.FLOW_GRAPH_REDO:
309                         n = self.get_page().get_state_cache().get_next_state()
310                         if n:
311                                 self.get_flow_graph().unselect()
312                                 self.get_flow_graph().import_data(n)
313                                 self.get_flow_graph().update()
314                                 self.get_page().set_saved(False)
315                 ##################################################
316                 # New/Open/Save/Close
317                 ##################################################
318                 elif state == Actions.FLOW_GRAPH_NEW:
319                         self.main_window.new_page()
320                 elif state == Actions.FLOW_GRAPH_OPEN:
321                         file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
322                         if file_paths: #open a new page for each file, show only the first
323                                 for i,file_path in enumerate(file_paths):
324                                         self.main_window.new_page(file_path, show=(i==0))
325                 elif state == Actions.FLOW_GRAPH_CLOSE:
326                         self.main_window.close_page()
327                 elif state == Actions.FLOW_GRAPH_SAVE:
328                         if not self.get_page().get_file_path(): self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
329                         else:
330                                 try:
331                                         ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
332                                         self.get_page().set_saved(True)
333                                 except IOError:
334                                         Messages.send_fail_save(self.get_page().get_file_path())
335                                         self.get_page().set_saved(False)
336                 elif state == Actions.FLOW_GRAPH_SAVE_AS:
337                         file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
338                         if file_path != None:
339                                 self.get_page().set_file_path(file_path)
340                                 self.handle_states(Actions.FLOW_GRAPH_SAVE)
341                 elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
342                         file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
343                         if file_path != None:
344                                 pixmap = self.get_flow_graph().get_drawing_area().pixmap
345                                 width, height = pixmap.get_size()
346                                 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height)
347                                 pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height)
348                                 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
349                 ##################################################
350                 # Gen/Exec/Stop
351                 ##################################################
352                 elif state == Actions.FLOW_GRAPH_GEN:
353                         if not self.get_page().get_pid():
354                                 if not self.get_page().get_saved() or not self.get_page().get_file_path():
355                                         self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved
356                                 if self.get_page().get_saved() and self.get_page().get_file_path():
357                                         generator = self.get_page().get_generator()
358                                         try:
359                                                 Messages.send_start_gen(generator.get_file_path())
360                                                 generator.write()
361                                         except Exception,e: Messages.send_fail_gen(e)
362                                 else: self.generator = None
363                 elif state == Actions.FLOW_GRAPH_EXEC:
364                         if not self.get_page().get_pid():
365                                 self.handle_states(Actions.FLOW_GRAPH_GEN)
366                                 if self.get_page().get_saved() and self.get_page().get_file_path():
367                                         ExecFlowGraphThread(self)
368                 elif state == Actions.FLOW_GRAPH_KILL:
369                         if self.get_page().get_pid():
370                                 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
371                                 except: print "could not kill pid: %s"%self.get_page().get_pid()
372                 elif state == '': #pass and run the global actions
373                         pass
374                 else: print '!!! State "%s" not handled !!!'%state
375                 ##################################################
376                 # Global Actions for all States
377                 ##################################################
378                 #update general buttons
379                 Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
380                 Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
381                 Actions.get_action_from_name(Actions.BLOCK_ROTATE_RIGHT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
382                 Actions.get_action_from_name(Actions.BLOCK_ROTATE_LEFT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
383                 #update cut/copy/paste
384                 Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
385                 Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
386                 Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard))
387                 #update enable/disable
388                 Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
389                 Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
390                 #set the exec and stop buttons
391                 self.update_exec_stop()
392                 #saved status
393                 Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
394                 self.main_window.update()
395                 #draw the flow graph
396                 self.get_flow_graph().draw()
397
398         def update_exec_stop(self):
399                 """
400                 Update the exec and stop buttons.
401                 Lock and unlock the mutex for race conditions with exec flow graph threads.
402                 """
403                 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
404                 Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive)
405                 Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive)
406                 Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
407
408 class ExecFlowGraphThread(Thread):
409         """Execute the flow graph as a new process and wait on it to finish."""
410         def __init__ (self, action_handler):
411                 """
412                 ExecFlowGraphThread constructor.
413                 @param action_handler an instance of an ActionHandler
414                 """
415                 Thread.__init__(self)
416                 self.update_exec_stop = action_handler.update_exec_stop
417                 self.flow_graph = action_handler.get_flow_graph()
418                 #store page and dont use main window calls in run
419                 self.page = action_handler.get_page()
420                 Messages.send_start_exec(self.page.get_generator().get_file_path())
421                 #get the popen
422                 try:
423                         self.p = self.page.get_generator().get_popen()
424                         self.page.set_pid(self.p.pid)
425                         #update
426                         self.update_exec_stop()
427                         self.start()
428                 except Exception, e:
429                         Messages.send_verbose_exec(str(e))
430                         Messages.send_end_exec()
431
432         def run(self):
433                 """Wait on the flow graph."""
434                 #handle completion
435                 r = "\n"
436                 while(r):
437                         gtk.gdk.threads_enter()
438                         Messages.send_verbose_exec(r)
439                         gtk.gdk.threads_leave()
440                         r = os.read(self.p.stdout.fileno(), 1024)
441                 gtk.gdk.threads_enter()
442                 Messages.send_end_exec()
443                 self.page.set_pid(None)
444                 self.update_exec_stop()
445                 gtk.gdk.threads_leave()