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