Added virtual sink and logic to clone port.
[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 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(platform)
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.TYPES_WINDOW_DISPLAY,
137                         ): Actions.get_action_from_name(action).set_sensitive(True)
138                         if not self.init_file_paths:
139                                 self.init_file_paths = Preferences.files_open()
140                         if not self.init_file_paths: self.init_file_paths = ['']
141                         for file_path in self.init_file_paths:
142                                 if file_path: self.main_window.new_page(file_path) #load pages from file paths
143                         if Preferences.file_open() in self.init_file_paths:
144                                 self.main_window.new_page(Preferences.file_open(), show=True)
145                         if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
146                 elif state == Actions.APPLICATION_QUIT:
147                         if self.main_window.close_pages():
148                                 gtk.main_quit()
149                                 exit(0)
150                 ##################################################
151                 # Selections
152                 ##################################################
153                 elif state == Actions.ELEMENT_SELECT:
154                         pass #do nothing, update routines below
155                 elif state == Actions.NOTHING_SELECT:
156                         self.get_flow_graph().unselect()
157                 ##################################################
158                 # Enable/Disable
159                 ##################################################
160                 elif state == Actions.BLOCK_ENABLE:
161                         if self.get_flow_graph().enable_selected(True):
162                                 self.get_flow_graph().update()
163                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
164                                 self.get_page().set_saved(False)
165                 elif state == Actions.BLOCK_DISABLE:
166                         if self.get_flow_graph().enable_selected(False):
167                                 self.get_flow_graph().update()
168                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
169                                 self.get_page().set_saved(False)
170                 ##################################################
171                 # Cut/Copy/Paste
172                 ##################################################
173                 elif state == Actions.BLOCK_CUT:
174                         self.handle_states(Actions.BLOCK_COPY)
175                         self.handle_states(Actions.ELEMENT_DELETE)
176                 elif state == Actions.BLOCK_COPY:
177                         self.clipboard = self.get_flow_graph().copy_to_clipboard()
178                 elif state == Actions.BLOCK_PASTE:
179                         if self.clipboard:
180                                 self.get_flow_graph().paste_from_clipboard(self.clipboard)
181                                 self.get_flow_graph().update()
182                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
183                                 self.get_page().set_saved(False)
184                 ##################################################
185                 # Move/Rotate/Delete/Create
186                 ##################################################
187                 elif state == Actions.BLOCK_MOVE:
188                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
189                         self.get_page().set_saved(False)
190                 elif state == Actions.BLOCK_ROTATE_CCW:
191                         if self.get_flow_graph().rotate_selected(90):
192                                 self.get_flow_graph().update()
193                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
194                                 self.get_page().set_saved(False)
195                 elif state == Actions.BLOCK_ROTATE_CW:
196                         if self.get_flow_graph().rotate_selected(-90):
197                                 self.get_flow_graph().update()
198                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
199                                 self.get_page().set_saved(False)
200                 elif state == Actions.ELEMENT_DELETE:
201                         if self.get_flow_graph().remove_selected():
202                                 self.get_flow_graph().update()
203                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
204                                 self.handle_states(Actions.NOTHING_SELECT)
205                                 self.get_page().set_saved(False)
206                 elif state == Actions.ELEMENT_CREATE:
207                         self.get_flow_graph().update()
208                         self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
209                         self.handle_states(Actions.NOTHING_SELECT)
210                         self.get_page().set_saved(False)
211                 elif state == Actions.BLOCK_INC_TYPE:
212                         if self.get_flow_graph().type_controller_modify_selected(1):
213                                 self.get_flow_graph().update()
214                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
215                                 self.get_page().set_saved(False)
216                 elif state == Actions.BLOCK_DEC_TYPE:
217                         if self.get_flow_graph().type_controller_modify_selected(-1):
218                                 self.get_flow_graph().update()
219                                 self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
220                                 self.get_page().set_saved(False)
221                 elif state == Actions.PORT_CONTROLLER_INC:
222                         if self.get_flow_graph().port_controller_modify_selected(1):
223                                 self.get_flow_graph().update()
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_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
230                                 self.get_page().set_saved(False)
231                 ##################################################
232                 # Window stuff
233                 ##################################################
234                 elif state == Actions.ABOUT_WINDOW_DISPLAY:
235                         Dialogs.AboutDialog(self.get_flow_graph().get_parent())
236                 elif state == Actions.HELP_WINDOW_DISPLAY:
237                         Dialogs.HelpDialog()
238                 elif state == Actions.TYPES_WINDOW_DISPLAY:
239                         Dialogs.TypesDialog(self.get_flow_graph().get_parent())
240                 ##################################################
241                 # Param Modifications
242                 ##################################################
243                 elif state == Actions.BLOCK_PARAM_MODIFY:
244                         selected_block = self.get_flow_graph().get_selected_block()
245                         if selected_block and ParamsDialog(selected_block).run():
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                 ##################################################
250                 # Undo/Redo
251                 ##################################################
252                 elif state == Actions.FLOW_GRAPH_UNDO:
253                         n = self.get_page().get_state_cache().get_prev_state()
254                         if n:
255                                 self.get_flow_graph().unselect()
256                                 self.get_flow_graph().import_data(n)
257                                 self.get_flow_graph().update()
258                                 self.get_page().set_saved(False)
259                 elif state == Actions.FLOW_GRAPH_REDO:
260                         n = self.get_page().get_state_cache().get_next_state()
261                         if n:
262                                 self.get_flow_graph().unselect()
263                                 self.get_flow_graph().import_data(n)
264                                 self.get_flow_graph().update()
265                                 self.get_page().set_saved(False)
266                 ##################################################
267                 # New/Open/Save/Close
268                 ##################################################
269                 elif state == Actions.FLOW_GRAPH_NEW:
270                         self.main_window.new_page()
271                 elif state == Actions.FLOW_GRAPH_OPEN:
272                         file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
273                         if file_paths: #open a new page for each file, show only the first
274                                 for i,file_path in enumerate(file_paths):
275                                         self.main_window.new_page(file_path, show=(i==0))
276                 elif state == Actions.FLOW_GRAPH_CLOSE:
277                         self.main_window.close_page()
278                 elif state == Actions.FLOW_GRAPH_SAVE:
279                         #read-only or undefined file path, do save-as
280                         if self.get_page().get_read_only() or not self.get_page().get_file_path():
281                                 self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
282                         #otherwise try to save
283                         else:
284                                 try:
285                                         ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
286                                         self.get_page().set_saved(True)
287                                 except IOError:
288                                         Messages.send_fail_save(self.get_page().get_file_path())
289                                         self.get_page().set_saved(False)
290                 elif state == Actions.FLOW_GRAPH_SAVE_AS:
291                         file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
292                         if file_path is not None:
293                                 self.get_page().set_file_path(file_path)
294                                 self.handle_states(Actions.FLOW_GRAPH_SAVE)
295                 elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
296                         file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
297                         if file_path is not None:
298                                 pixbuf = self.get_flow_graph().get_drawing_area().get_pixbuf()
299                                 pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
300                 ##################################################
301                 # Gen/Exec/Stop
302                 ##################################################
303                 elif state == Actions.FLOW_GRAPH_GEN:
304                         if not self.get_page().get_pid():
305                                 if not self.get_page().get_saved() or not self.get_page().get_file_path():
306                                         self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved
307                                 if self.get_page().get_saved() and self.get_page().get_file_path():
308                                         generator = self.get_page().get_generator()
309                                         try:
310                                                 Messages.send_start_gen(generator.get_file_path())
311                                                 generator.write()
312                                         except Exception,e: Messages.send_fail_gen(e)
313                                 else: self.generator = None
314                 elif state == Actions.FLOW_GRAPH_EXEC:
315                         if not self.get_page().get_pid():
316                                 self.handle_states(Actions.FLOW_GRAPH_GEN)
317                                 if self.get_page().get_saved() and self.get_page().get_file_path():
318                                         ExecFlowGraphThread(self)
319                 elif state == Actions.FLOW_GRAPH_KILL:
320                         if self.get_page().get_pid():
321                                 try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
322                                 except: print "could not kill pid: %s"%self.get_page().get_pid()
323                 elif state == '': #pass and run the global actions
324                         pass
325                 else: print '!!! State "%s" not handled !!!'%state
326                 ##################################################
327                 # Global Actions for all States
328                 ##################################################
329                 #update general buttons
330                 Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
331                 Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
332                 Actions.get_action_from_name(Actions.BLOCK_ROTATE_CCW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
333                 Actions.get_action_from_name(Actions.BLOCK_ROTATE_CW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
334                 #update cut/copy/paste
335                 Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
336                 Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
337                 Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard))
338                 #update enable/disable
339                 Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
340                 Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
341                 #set the exec and stop buttons
342                 self.update_exec_stop()
343                 #saved status
344                 Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
345                 self.main_window.update()
346                 try: #set the size of the flow graph area (if changed)
347                         new_size = self.get_flow_graph().get_option('window_size')
348                         if self.get_flow_graph().get_size() != tuple(new_size):
349                                 self.get_flow_graph().set_size(*new_size)
350                 except: pass
351                 #draw the flow graph
352                 self.get_flow_graph().update_selected()
353                 self.get_flow_graph().queue_draw()
354
355         def update_exec_stop(self):
356                 """
357                 Update the exec and stop buttons.
358                 Lock and unlock the mutex for race conditions with exec flow graph threads.
359                 """
360                 sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
361                 Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive)
362                 Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive)
363                 Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
364
365 class ExecFlowGraphThread(Thread):
366         """Execute the flow graph as a new process and wait on it to finish."""
367
368         def __init__ (self, action_handler):
369                 """
370                 ExecFlowGraphThread constructor.
371                 @param action_handler an instance of an ActionHandler
372                 """
373                 Thread.__init__(self)
374                 self.update_exec_stop = action_handler.update_exec_stop
375                 self.flow_graph = action_handler.get_flow_graph()
376                 #store page and dont use main window calls in run
377                 self.page = action_handler.get_page()
378                 Messages.send_start_exec(self.page.get_generator().get_file_path())
379                 #get the popen
380                 try:
381                         self.p = self.page.get_generator().get_popen()
382                         self.page.set_pid(self.p.pid)
383                         #update
384                         self.update_exec_stop()
385                         self.start()
386                 except Exception, e:
387                         Messages.send_verbose_exec(str(e))
388                         Messages.send_end_exec()
389
390         def run(self):
391                 """
392                 Wait on the executing process by reading from its stdout.
393                 Use gobject.idle_add when calling functions that modify gtk objects.
394                 """
395                 #handle completion
396                 r = "\n"
397                 while(r):
398                         gobject.idle_add(Messages.send_verbose_exec, r)
399                         r = os.read(self.p.stdout.fileno(), 1024)
400                 gobject.idle_add(self.done)
401
402         def done(self):
403                 """Perform end of execution tasks."""
404                 Messages.send_end_exec()
405                 self.page.set_pid(None)
406                 self.update_exec_stop()