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