Imported Upstream version 3.2.2
[debian/gnuradio] / grc / gui / ActionHandler.py
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
new file mode 100644 (file)
index 0000000..ff137f6
--- /dev/null
@@ -0,0 +1,408 @@
+"""
+Copyright 2007, 2008, 2009 Free Software Foundation, Inc.
+This file is part of GNU Radio
+
+GNU Radio Companion is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+GNU Radio Companion is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+"""
+
+import os
+import signal 
+from Constants import IMAGE_FILE_EXTENSION
+import Actions
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+import Preferences
+from threading import Thread
+import Messages
+from .. base import ParseXML
+import random
+from Platform import Platform
+from MainWindow import MainWindow
+from ParamsDialog import ParamsDialog
+import Dialogs
+from FileDialogs import OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, SaveImageFileDialog
+
+gobject.threads_init()
+
+class ActionHandler:
+       """
+       The action handler will setup all the major window components,
+       and handle button presses and flow graph operations from the GUI.
+       """
+
+       def __init__(self, file_paths, platform):
+               """
+               ActionHandler constructor.
+               Create the main window, setup the message handler, import the preferences,
+               and connect all of the action handlers. Finally, enter the gtk main loop and block.
+               @param file_paths a list of flow graph file passed from command line
+               @param platform platform module
+               """
+               self.clipboard = None
+               platform = Platform(platform)
+               for action in Actions.get_all_actions(): action.connect('activate', self._handle_actions)
+               #setup the main window
+               self.main_window = MainWindow(self.handle_states, platform)
+               self.main_window.connect('delete_event', self._quit)
+               self.main_window.connect('key-press-event', self._handle_key_press)
+               self.get_page = self.main_window.get_page
+               self.get_flow_graph = self.main_window.get_flow_graph
+               self.get_focus_flag = self.main_window.get_focus_flag
+               #setup the messages
+               Messages.register_messenger(self.main_window.add_report_line)
+               Messages.send_init(platform)
+               #initialize
+               self.init_file_paths = file_paths
+               self.handle_states(Actions.APPLICATION_INITIALIZE)
+               #enter the mainloop
+               gtk.main()
+
+       def _handle_key_press(self, widget, event):
+               """
+               Handle key presses from the keyboard and translate key combinations into actions.
+               This key press handler is called prior to the gtk key press handler.
+               This handler bypasses built in accelerator key handling when in focus because
+               * some keys are ignored by the accelerators like the direction keys,
+               * some keys are not registered to any accelerators but are still used.
+               When not in focus, gtk and the accelerators handle the the key press.
+               @return false to let gtk handle the key action
+               """
+               #dont allow key presses to queue up
+               if gtk.events_pending(): return True
+               #extract action name from this key press
+               key_name = gtk.gdk.keyval_name(event.keyval)
+               mod_mask = event.state
+               action_name = Actions.get_action_name_from_key_name(key_name, mod_mask)
+               #handle the action if flow graph is in focus
+               if action_name and self.get_focus_flag():
+                       self.handle_states(action_name)
+                       return True #handled by this method
+               return False #let gtk handle the key press
+
+       def _quit(self, window, event):
+               """
+               Handle the delete event from the main window.
+               Generated by pressing X to close, alt+f4, or right click+close.
+               This method in turns calls the state handler to quit.
+               @return true
+               """
+               self.handle_states(Actions.APPLICATION_QUIT)
+               return True
+
+       def _handle_actions(self, event):
+               """
+               Handle all of the activate signals from the gtk actions.
+               The action signals derive from clicking on a toolbar or menu bar button.
+               Forward the action to the state handler.
+               """
+               self.handle_states(event.get_name())
+
+       def handle_states(self, state=''):
+               """
+               Handle the state changes in the GUI.
+               Handle all of the state changes that arise from the action handler or other gui and
+               inputs in the application. The state passed to the handle_states method is a string descriping
+               the change. A series of if/elif statements handle the state by greying out action buttons, causing
+               changes in the flow graph, saving/opening files... The handle_states method is passed to the
+               contructors of many of the classes used in this application enabling them to report any state change.
+               @param state a string describing the state change
+               """
+               #print state
+               ##################################################
+               # Initalize/Quit
+               ##################################################
+               if state == Actions.APPLICATION_INITIALIZE:
+                       for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled
+                       # enable a select few actions
+                       for action in (
+                               Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW,
+                               Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS,
+                               Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY,
+                               Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY,
+                               Actions.COLORS_WINDOW_DISPLAY,
+                       ): Actions.get_action_from_name(action).set_sensitive(True)
+                       if not self.init_file_paths:
+                               self.init_file_paths = Preferences.files_open()
+                       if not self.init_file_paths: self.init_file_paths = ['']
+                       for file_path in self.init_file_paths:
+                               if file_path: self.main_window.new_page(file_path) #load pages from file paths
+                       if Preferences.file_open() in self.init_file_paths:
+                               self.main_window.new_page(Preferences.file_open(), show=True)
+                       if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists
+               elif state == Actions.APPLICATION_QUIT:
+                       if self.main_window.close_pages():
+                               gtk.main_quit()
+                               exit(0)
+               ##################################################
+               # Selections
+               ##################################################
+               elif state == Actions.ELEMENT_SELECT:
+                       pass #do nothing, update routines below
+               elif state == Actions.NOTHING_SELECT:
+                       self.get_flow_graph().unselect()
+               ##################################################
+               # Enable/Disable
+               ##################################################
+               elif state == Actions.BLOCK_ENABLE:
+                       if self.get_flow_graph().enable_selected(True):
+                               self.get_flow_graph().update()
+                               self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                               self.get_page().set_saved(False)
+               elif state == Actions.BLOCK_DISABLE:
+                       if self.get_flow_graph().enable_selected(False):
+                               self.get_flow_graph().update()
+                               self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                               self.get_page().set_saved(False)
+               ##################################################
+               # Cut/Copy/Paste
+               ##################################################
+               elif state == Actions.BLOCK_CUT:
+                       self.handle_states(Actions.BLOCK_COPY)
+                       self.handle_states(Actions.ELEMENT_DELETE)
+               elif state == Actions.BLOCK_COPY:
+                       self.clipboard = self.get_flow_graph().copy_to_clipboard()
+               elif state == Actions.BLOCK_PASTE:
+                       if self.clipboard:
+                               self.get_flow_graph().paste_from_clipboard(self.clipboard)
+                               self.get_flow_graph().update()
+                               self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                               self.get_page().set_saved(False)
+               ##################################################
+               # Move/Rotate/Delete/Create
+               ##################################################
+               elif state == Actions.BLOCK_MOVE:
+                       self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                       self.get_page().set_saved(False)
+               elif state == Actions.BLOCK_ROTATE_CCW:
+                       if self.get_flow_graph().rotate_selected(90):
+                               self.get_flow_graph().update()
+                               self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                               self.get_page().set_saved(False)
+               elif state == Actions.BLOCK_ROTATE_CW:
+                       if self.get_flow_graph().rotate_selected(-90):
+                               self.get_flow_graph().update()
+                               self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                               self.get_page().set_saved(False)
+               elif state == Actions.ELEMENT_DELETE:
+                       if self.get_flow_graph().remove_selected():
+                               self.get_flow_graph().update()
+                               self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                               self.handle_states(Actions.NOTHING_SELECT)
+                               self.get_page().set_saved(False)
+               elif state == Actions.ELEMENT_CREATE:
+                       self.get_flow_graph().update()
+                       self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                       self.handle_states(Actions.NOTHING_SELECT)
+                       self.get_page().set_saved(False)
+               elif state == Actions.BLOCK_INC_TYPE:
+                       if self.get_flow_graph().type_controller_modify_selected(1):
+                               self.get_flow_graph().update()
+                               self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                               self.get_page().set_saved(False)
+               elif state == Actions.BLOCK_DEC_TYPE:
+                       if self.get_flow_graph().type_controller_modify_selected(-1):
+                               self.get_flow_graph().update()
+                               self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                               self.get_page().set_saved(False)
+               elif state == Actions.PORT_CONTROLLER_INC:
+                       if self.get_flow_graph().port_controller_modify_selected(1):
+                               self.get_flow_graph().update()
+                               self.get_flow_graph().update() #2 times
+                               self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                               self.get_page().set_saved(False)
+               elif state == Actions.PORT_CONTROLLER_DEC:
+                       if self.get_flow_graph().port_controller_modify_selected(-1):
+                               self.get_flow_graph().update()
+                               self.get_flow_graph().update() #2 times
+                               self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                               self.get_page().set_saved(False)
+               ##################################################
+               # Window stuff
+               ##################################################
+               elif state == Actions.ABOUT_WINDOW_DISPLAY:
+                       Dialogs.AboutDialog(self.get_flow_graph().get_parent())
+               elif state == Actions.HELP_WINDOW_DISPLAY:
+                       Dialogs.HelpDialog()
+               elif state == Actions.COLORS_WINDOW_DISPLAY:
+                       Dialogs.ColorsDialog(self.get_flow_graph().get_parent())
+               ##################################################
+               # Param Modifications
+               ##################################################
+               elif state == Actions.BLOCK_PARAM_MODIFY:
+                       selected_block = self.get_flow_graph().get_selected_block()
+                       if selected_block and ParamsDialog(selected_block).run():
+                               self.get_flow_graph().update()
+                               self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data())
+                               self.get_page().set_saved(False)
+               ##################################################
+               # Undo/Redo
+               ##################################################
+               elif state == Actions.FLOW_GRAPH_UNDO:
+                       n = self.get_page().get_state_cache().get_prev_state()
+                       if n:
+                               self.get_flow_graph().unselect()
+                               self.get_flow_graph().import_data(n)
+                               self.get_flow_graph().update()
+                               self.get_page().set_saved(False)
+               elif state == Actions.FLOW_GRAPH_REDO:
+                       n = self.get_page().get_state_cache().get_next_state()
+                       if n:
+                               self.get_flow_graph().unselect()
+                               self.get_flow_graph().import_data(n)
+                               self.get_flow_graph().update()
+                               self.get_page().set_saved(False)
+               ##################################################
+               # New/Open/Save/Close
+               ##################################################
+               elif state == Actions.FLOW_GRAPH_NEW:
+                       self.main_window.new_page()
+               elif state == Actions.FLOW_GRAPH_OPEN:
+                       file_paths = OpenFlowGraphFileDialog(self.get_page().get_file_path()).run()
+                       if file_paths: #open a new page for each file, show only the first
+                               for i,file_path in enumerate(file_paths):
+                                       self.main_window.new_page(file_path, show=(i==0))
+               elif state == Actions.FLOW_GRAPH_CLOSE:
+                       self.main_window.close_page()
+               elif state == Actions.FLOW_GRAPH_SAVE:
+                       #read-only or undefined file path, do save-as
+                       if self.get_page().get_read_only() or not self.get_page().get_file_path():
+                               self.handle_states(Actions.FLOW_GRAPH_SAVE_AS)
+                       #otherwise try to save
+                       else:
+                               try:
+                                       ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path())
+                                       self.get_page().set_saved(True)
+                               except IOError:
+                                       Messages.send_fail_save(self.get_page().get_file_path())
+                                       self.get_page().set_saved(False)
+               elif state == Actions.FLOW_GRAPH_SAVE_AS:
+                       file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run()
+                       if file_path is not None:
+                               self.get_page().set_file_path(file_path)
+                               self.handle_states(Actions.FLOW_GRAPH_SAVE)
+               elif state == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
+                       file_path = SaveImageFileDialog(self.get_page().get_file_path()).run()
+                       if file_path is not None:
+                               pixbuf = self.get_flow_graph().get_drawing_area().get_pixbuf()
+                               pixbuf.save(file_path, IMAGE_FILE_EXTENSION[1:])
+               ##################################################
+               # Gen/Exec/Stop
+               ##################################################
+               elif state == Actions.FLOW_GRAPH_GEN:
+                       if not self.get_page().get_pid():
+                               if not self.get_page().get_saved() or not self.get_page().get_file_path():
+                                       self.handle_states(Actions.FLOW_GRAPH_SAVE) #only save if file path missing or not saved
+                               if self.get_page().get_saved() and self.get_page().get_file_path():
+                                       generator = self.get_page().get_generator()
+                                       try:
+                                               Messages.send_start_gen(generator.get_file_path())
+                                               generator.write()
+                                       except Exception,e: Messages.send_fail_gen(e)
+                               else: self.generator = None
+               elif state == Actions.FLOW_GRAPH_EXEC:
+                       if not self.get_page().get_pid():
+                               self.handle_states(Actions.FLOW_GRAPH_GEN)
+                               if self.get_page().get_saved() and self.get_page().get_file_path():
+                                       ExecFlowGraphThread(self)
+               elif state == Actions.FLOW_GRAPH_KILL:
+                       if self.get_page().get_pid():
+                               try: os.kill(self.get_page().get_pid(), signal.SIGKILL)
+                               except: print "could not kill pid: %s"%self.get_page().get_pid()
+               elif state == '': #pass and run the global actions
+                       pass
+               else: print '!!! State "%s" not handled !!!'%state
+               ##################################################
+               # Global Actions for all States
+               ##################################################
+               #update general buttons
+               Actions.get_action_from_name(Actions.ELEMENT_DELETE).set_sensitive(bool(self.get_flow_graph().get_selected_elements()))
+               Actions.get_action_from_name(Actions.BLOCK_PARAM_MODIFY).set_sensitive(bool(self.get_flow_graph().get_selected_block()))
+               Actions.get_action_from_name(Actions.BLOCK_ROTATE_CCW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
+               Actions.get_action_from_name(Actions.BLOCK_ROTATE_CW).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
+               #update cut/copy/paste
+               Actions.get_action_from_name(Actions.BLOCK_CUT).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
+               Actions.get_action_from_name(Actions.BLOCK_COPY).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
+               Actions.get_action_from_name(Actions.BLOCK_PASTE).set_sensitive(bool(self.clipboard))
+               #update enable/disable
+               Actions.get_action_from_name(Actions.BLOCK_ENABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
+               Actions.get_action_from_name(Actions.BLOCK_DISABLE).set_sensitive(bool(self.get_flow_graph().get_selected_blocks()))
+               #set the exec and stop buttons
+               self.update_exec_stop()
+               #saved status
+               Actions.get_action_from_name(Actions.FLOW_GRAPH_SAVE).set_sensitive(not self.get_page().get_saved())
+               self.main_window.update()
+               try: #set the size of the flow graph area (if changed)
+                       new_size = self.get_flow_graph().get_option('window_size')
+                       if self.get_flow_graph().get_size() != tuple(new_size):
+                               self.get_flow_graph().set_size(*new_size)
+               except: pass
+               #draw the flow graph
+               self.get_flow_graph().update_selected()
+               self.get_flow_graph().queue_draw()
+
+       def update_exec_stop(self):
+               """
+               Update the exec and stop buttons.
+               Lock and unlock the mutex for race conditions with exec flow graph threads.
+               """
+               sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_pid()
+               Actions.get_action_from_name(Actions.FLOW_GRAPH_GEN).set_sensitive(sensitive)
+               Actions.get_action_from_name(Actions.FLOW_GRAPH_EXEC).set_sensitive(sensitive)
+               Actions.get_action_from_name(Actions.FLOW_GRAPH_KILL).set_sensitive(self.get_page().get_pid() != None)
+
+class ExecFlowGraphThread(Thread):
+       """Execute the flow graph as a new process and wait on it to finish."""
+
+       def __init__ (self, action_handler):
+               """
+               ExecFlowGraphThread constructor.
+               @param action_handler an instance of an ActionHandler
+               """
+               Thread.__init__(self)
+               self.update_exec_stop = action_handler.update_exec_stop
+               self.flow_graph = action_handler.get_flow_graph()
+               #store page and dont use main window calls in run
+               self.page = action_handler.get_page()
+               Messages.send_start_exec(self.page.get_generator().get_file_path())
+               #get the popen
+               try:
+                       self.p = self.page.get_generator().get_popen()
+                       self.page.set_pid(self.p.pid)
+                       #update
+                       self.update_exec_stop()
+                       self.start()
+               except Exception, e:
+                       Messages.send_verbose_exec(str(e))
+                       Messages.send_end_exec()
+
+       def run(self):
+               """
+               Wait on the executing process by reading from its stdout.
+               Use gobject.idle_add when calling functions that modify gtk objects.
+               """
+               #handle completion
+               r = "\n"
+               while(r):
+                       gobject.idle_add(Messages.send_verbose_exec, r)
+                       r = os.read(self.p.stdout.fileno(), 1024)
+               gobject.idle_add(self.done)
+
+       def done(self):
+               """Perform end of execution tasks."""
+               Messages.send_end_exec()
+               self.page.set_pid(None)
+               self.update_exec_stop()