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