diff options
Diffstat (limited to 'grc/gui/Application.py')
-rw-r--r-- | grc/gui/Application.py | 808 |
1 files changed, 808 insertions, 0 deletions
diff --git a/grc/gui/Application.py b/grc/gui/Application.py new file mode 100644 index 0000000000..c1456c3a8d --- /dev/null +++ b/grc/gui/Application.py @@ -0,0 +1,808 @@ +""" +Copyright 2007-2011 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 +""" + + +from __future__ import absolute_import, print_function + +import os +import subprocess +import logging + +from gi.repository import Gtk, Gio, GLib, GObject + +from . import Dialogs, Actions, Executor, FileDialogs, Utils, Bars +from .MainWindow import MainWindow +from .ParserErrorsDialog import ParserErrorsDialog +from .PropsDialog import PropsDialog + +from ..core import ParseXML, Messages + + +log = logging.getLogger(__name__) + + +class Application(Gtk.Application): + """ + 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): + Gtk.Application.__init__(self) + """ + Application 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. + + Args: + file_paths: a list of flow graph file passed from command line + platform: platform module + """ + self.clipboard = None + self.dialog = None + + # Setup the main window + self.platform = platform + self.config = platform.config + + log.debug("Application()") + # Connect all actions to _handle_action + for x in Actions.get_actions(): + Actions.connect(x, handler=self._handle_action) + Actions.actions[x].enable() + if x.startswith("app."): + self.add_action(Actions.actions[x]) + # Setup the shortcut keys + # These are the globally defined shortcuts + keypress = Actions.actions[x].keypresses + if keypress: + self.set_accels_for_action(x, keypress) + + # Initialize + self.init_file_paths = [os.path.abspath(file_path) for file_path in file_paths] + self.init = False + + def do_startup(self): + Gtk.Application.do_startup(self) + log.debug("Application.do_startup()") + + # Setup the menu + log.debug("Creating menu") + ''' + self.menu = Bars.Menu() + self.set_menu() + if self.prefers_app_menu(): + self.set_app_menu(self.menu) + else: + self.set_menubar(self.menu) + ''' + + def do_activate(self): + Gtk.Application.do_activate(self) + log.debug("Application.do_activate()") + + self.main_window = MainWindow(self, self.platform) + self.main_window.connect('delete-event', self._quit) + self.get_focus_flag = self.main_window.get_focus_flag + + #setup the messages + Messages.register_messenger(self.main_window.add_console_line) + Messages.send_init(self.platform) + + log.debug("Calling Actions.APPLICATION_INITIALIZE") + Actions.APPLICATION_INITIALIZE() + + 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. + + Returns: + true + """ + Actions.APPLICATION_QUIT() + return True + + def _handle_action(self, action, *args): + log.debug("_handle_action({0}, {1})".format(action, args)) + main = self.main_window + page = main.current_page + flow_graph = page.flow_graph if page else None + + def flow_graph_update(fg=flow_graph): + main.vars.update_gui(fg.blocks) + fg.update() + + ################################################## + # Initialize/Quit + ################################################## + if action == Actions.APPLICATION_INITIALIZE: + log.debug("APPLICATION_INITIALIZE") + file_path_to_show = self.config.file_open() + for file_path in (self.init_file_paths or self.config.get_open_files()): + if os.path.exists(file_path): + main.new_page(file_path, show=file_path_to_show == file_path) + if not main.current_page: + main.new_page() # ensure that at least a blank page exists + + main.btwin.search_entry.hide() + + """ + Only disable certain actions on startup. Each of these actions are + conditionally enabled in _handle_action, so disable them first. + - FLOW_GRAPH_UNDO/REDO are set in gui/StateCache.py + - XML_PARSER_ERRORS_DISPLAY is set in RELOAD_BLOCKS + + TODO: These 4 should probably be included, but they are not currently + enabled anywhere else: + - PORT_CONTROLLER_DEC, PORT_CONTROLLER_INC + - BLOCK_INC_TYPE, BLOCK_DEC_TYPE + + TODO: These should be handled better. They are set in + update_exec_stop(), but not anywhere else + - FLOW_GRAPH_GEN, FLOW_GRAPH_EXEC, FLOW_GRAPH_KILL + """ + for action in ( + Actions.ERRORS_WINDOW_DISPLAY, + Actions.ELEMENT_DELETE, + Actions.BLOCK_PARAM_MODIFY, + Actions.BLOCK_ROTATE_CCW, + Actions.BLOCK_ROTATE_CW, + Actions.BLOCK_VALIGN_TOP, + Actions.BLOCK_VALIGN_MIDDLE, + Actions.BLOCK_VALIGN_BOTTOM, + Actions.BLOCK_HALIGN_LEFT, + Actions.BLOCK_HALIGN_CENTER, + Actions.BLOCK_HALIGN_RIGHT, + Actions.BLOCK_CUT, + Actions.BLOCK_COPY, + Actions.BLOCK_PASTE, + Actions.BLOCK_ENABLE, + Actions.BLOCK_DISABLE, + Actions.BLOCK_BYPASS, + Actions.BLOCK_CREATE_HIER, + Actions.OPEN_HIER, + Actions.BUSSIFY_SOURCES, + Actions.BUSSIFY_SINKS, + Actions.FLOW_GRAPH_SAVE, + Actions.FLOW_GRAPH_UNDO, + Actions.FLOW_GRAPH_REDO, + Actions.XML_PARSER_ERRORS_DISPLAY + ): + action.disable() + + # Load preferences + for action in ( + Actions.TOGGLE_BLOCKS_WINDOW, + Actions.TOGGLE_CONSOLE_WINDOW, + Actions.TOGGLE_HIDE_DISABLED_BLOCKS, + Actions.TOGGLE_SCROLL_LOCK, + Actions.TOGGLE_AUTO_HIDE_PORT_LABELS, + Actions.TOGGLE_SNAP_TO_GRID, + Actions.TOGGLE_SHOW_BLOCK_COMMENTS, + Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB, + Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR, + Actions.TOGGLE_HIDE_VARIABLES, + ): + action.set_enabled(True) + if hasattr(action, 'load_from_preferences'): + action.load_from_preferences() + + # Hide the panels *IF* it's saved in preferences + main.update_panel_visibility(main.BLOCKS, Actions.TOGGLE_BLOCKS_WINDOW.get_active()) + main.update_panel_visibility(main.CONSOLE, Actions.TOGGLE_CONSOLE_WINDOW.get_active()) + main.update_panel_visibility(main.VARIABLES, Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR.get_active()) + + if ParseXML.xml_failures: + Messages.send_xml_errors_if_any(ParseXML.xml_failures) + Actions.XML_PARSER_ERRORS_DISPLAY.set_enabled(True) + + # Force an update on the current page to match loaded preferences. + # In the future, change the __init__ order to load preferences first + page = main.current_page + if page: + page.flow_graph.update() + + self.init = True + elif action == Actions.APPLICATION_QUIT: + if main.close_pages(): + Gtk.main_quit() + exit(0) + ################################################## + # Selections + ################################################## + elif action == Actions.ELEMENT_SELECT: + pass #do nothing, update routines below + elif action == Actions.NOTHING_SELECT: + flow_graph.unselect() + elif action == Actions.SELECT_ALL: + flow_graph.select_all() + ################################################## + # Enable/Disable + ################################################## + elif action in (Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_BYPASS): + changed = flow_graph.change_state_selected(new_state={ + Actions.BLOCK_ENABLE: 'enabled', + Actions.BLOCK_DISABLE: 'disabled', + Actions.BLOCK_BYPASS: 'bypassed', + }[action]) + if changed: + flow_graph_update() + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False + ################################################## + # Cut/Copy/Paste + ################################################## + elif action == Actions.BLOCK_CUT: + Actions.BLOCK_COPY() + Actions.ELEMENT_DELETE() + elif action == Actions.BLOCK_COPY: + self.clipboard = flow_graph.copy_to_clipboard() + elif action == Actions.BLOCK_PASTE: + if self.clipboard: + flow_graph.paste_from_clipboard(self.clipboard) + flow_graph_update() + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False + ################################################## + # Create heir block + ################################################## + elif action == Actions.BLOCK_CREATE_HIER: + + # keeping track of coordinates for pasting later + coords = flow_graph.selected_blocks()[0].coordinate + x,y = coords + x_min = x + y_min = y + + pads = []; + params = []; + + # Save the state of the leaf blocks + for block in flow_graph.selected_blocks(): + + # Check for string variables within the blocks + for param in block.params.values(): + for variable in flow_graph.get_variables(): + # If a block parameter exists that is a variable, create a parameter for it + if param.get_value() == variable.get_id(): + params.append(param.get_value()) + for flow_param in flow_graph.get_parameters(): + # If a block parameter exists that is a parameter, create a parameter for it + if param.get_value() == flow_param.get_id(): + params.append(param.get_value()) + + + # keep track of x,y mins for pasting later + (x,y) = block.coordinate + if x < x_min: + x_min = x + if y < y_min: + y_min = y + + for connection in block.connections: + + # Get id of connected blocks + source_id = connection.source_block.get_id() + sink_id = connection.sink_block.get_id() + + # If connected block is not in the list of selected blocks create a pad for it + if flow_graph.get_block(source_id) not in flow_graph.selected_blocks(): + pads.append({'key': connection.sink_port.key, 'coord': connection.source_port.coordinate, 'block_id' : block.get_id(), 'direction': 'source'}) + + if flow_graph.get_block(sink_id) not in flow_graph.selected_blocks(): + pads.append({'key': connection.source_port.key, 'coord': connection.sink_port.coordinate, 'block_id' : block.get_id(), 'direction': 'sink'}) + + + # Copy the selected blocks and paste them into a new page + # then move the flowgraph to a reasonable position + Actions.BLOCK_COPY() + main.new_page() + Actions.BLOCK_PASTE() + coords = (x_min,y_min) + flow_graph.move_selected(coords) + + + # Set flow graph to heir block type + top_block = flow_graph.get_block("top_block") + top_block.get_param('generate_options').set_value('hb') + + # this needs to be a unique name + top_block.get_param('id').set_value('new_heir') + + # Remove the default samp_rate variable block that is created + remove_me = flow_graph.get_block("samp_rate") + flow_graph.remove_element(remove_me) + + + # Add the param blocks along the top of the window + x_pos = 150 + for param in params: + param_id = flow_graph.add_new_block('parameter',(x_pos,10)) + param_block = flow_graph.get_block(param_id) + param_block.get_param('id').set_value(param) + x_pos = x_pos + 100 + + for pad in pads: + # Add the pad sources and sinks within the new heir block + if pad['direction'] == 'sink': + + # Add new PAD_SINK block to the canvas + pad_id = flow_graph.add_new_block('pad_sink', pad['coord']) + + # setup the references to the sink and source + pad_block = flow_graph.get_block(pad_id) + pad_sink = pad_block.sinks[0] + + source_block = flow_graph.get_block(pad['block_id']) + source = source_block.get_source(pad['key']) + + # Ensure the port types match + while pad_sink.get_type() != source.get_type(): + + # Special case for some blocks that have non-standard type names, e.g. uhd + if pad_sink.get_type() == 'complex' and source.get_type() == 'fc32': + break; + pad_block.type_controller_modify(1) + + # Connect the pad to the proper sinks + new_connection = flow_graph.connect(source,pad_sink) + + elif pad['direction'] == 'source': + pad_id = flow_graph.add_new_block('pad_source', pad['coord']) + + # setup the references to the sink and source + pad_block = flow_graph.get_block(pad_id) + pad_source = pad_block.sources[0] + + sink_block = flow_graph.get_block(pad['block_id']) + sink = sink_block.get_sink(pad['key']) + + # Ensure the port types match + while sink.get_type() != pad_source.get_type(): + # Special case for some blocks that have non-standard type names, e.g. uhd + if pad_source.get_type() == 'complex' and sink.get_type() == 'fc32': + break; + pad_block.type_controller_modify(1) + + # Connect the pad to the proper sinks + new_connection = flow_graph.connect(pad_source,sink) + + # update the new heir block flow graph + flow_graph_update() + + + ################################################## + # Move/Rotate/Delete/Create + ################################################## + elif action == Actions.BLOCK_MOVE: + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False + elif action in Actions.BLOCK_ALIGNMENTS: + if flow_graph.align_selected(action): + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False + elif action == Actions.BLOCK_ROTATE_CCW: + if flow_graph.rotate_selected(90): + flow_graph_update() + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False + elif action == Actions.BLOCK_ROTATE_CW: + if flow_graph.rotate_selected(-90): + flow_graph_update() + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False + elif action == Actions.ELEMENT_DELETE: + if flow_graph.remove_selected(): + flow_graph_update() + page.state_cache.save_new_state(flow_graph.export_data()) + Actions.NOTHING_SELECT() + page.saved = False + elif action == Actions.ELEMENT_CREATE: + flow_graph_update() + page.state_cache.save_new_state(flow_graph.export_data()) + Actions.NOTHING_SELECT() + page.saved = False + elif action == Actions.BLOCK_INC_TYPE: + if flow_graph.type_controller_modify_selected(1): + flow_graph_update() + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False + elif action == Actions.BLOCK_DEC_TYPE: + if flow_graph.type_controller_modify_selected(-1): + flow_graph_update() + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False + elif action == Actions.PORT_CONTROLLER_INC: + if flow_graph.port_controller_modify_selected(1): + flow_graph_update() + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False + elif action == Actions.PORT_CONTROLLER_DEC: + if flow_graph.port_controller_modify_selected(-1): + flow_graph_update() + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False + ################################################## + # Window stuff + ################################################## + elif action == Actions.ABOUT_WINDOW_DISPLAY: + Dialogs.show_about(main, self.platform.config) + elif action == Actions.HELP_WINDOW_DISPLAY: + Dialogs.show_help(main) + elif action == Actions.TYPES_WINDOW_DISPLAY: + Dialogs.show_types(main) + elif action == Actions.ERRORS_WINDOW_DISPLAY: + Dialogs.ErrorsDialog(main, flow_graph).run_and_destroy() + elif action == Actions.TOGGLE_CONSOLE_WINDOW: + action.set_active(not action.get_active()) + main.update_panel_visibility(main.CONSOLE, action.get_active()) + action.save_to_preferences() + elif action == Actions.TOGGLE_BLOCKS_WINDOW: + # This would be better matched to a Gio.PropertyAction, but to do + # this, actions would have to be defined in the window not globally + action.set_active(not action.get_active()) + main.update_panel_visibility(main.BLOCKS, action.get_active()) + action.save_to_preferences() + elif action == Actions.TOGGLE_SCROLL_LOCK: + action.set_active(not action.get_active()) + active = action.get_active() + main.console.text_display.scroll_lock = active + if active: + main.console.text_display.scroll_to_end() + action.save_to_preferences() + elif action == Actions.CLEAR_CONSOLE: + main.console.text_display.clear() + elif action == Actions.SAVE_CONSOLE: + file_path = FileDialogs.SaveConsole(main, page.file_path).run() + if file_path is not None: + main.console.text_display.save(file_path) + elif action == Actions.TOGGLE_HIDE_DISABLED_BLOCKS: + action.set_active(not action.get_active()) + Actions.NOTHING_SELECT() + elif action == Actions.TOGGLE_AUTO_HIDE_PORT_LABELS: + action.set_active(not action.get_active()) + action.save_to_preferences() + for page in main.get_pages(): + page.flow_graph.create_shapes() + elif action in (Actions.TOGGLE_SNAP_TO_GRID, + Actions.TOGGLE_SHOW_BLOCK_COMMENTS, + Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB): + action.set_active(not action.get_active()) + action.save_to_preferences() + elif action == Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY: + action.set_active(not action.get_active()) + action.save_to_preferences() + for page in main.get_pages(): + flow_graph_update(page.flow_graph) + elif action == Actions.TOGGLE_HIDE_VARIABLES: + action.set_active(not action.get_active()) + active = action.get_active() + # Either way, triggering this should simply trigger the variable editor + # to be visible. + varedit = Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR + if active: + log.debug("Variables are hidden. Forcing the variable panel to be visible.") + varedit.disable() + else: + varedit.enable() + # Just force it to show. + varedit.set_active(True) + main.update_panel_visibility(main.VARIABLES) + Actions.NOTHING_SELECT() + action.save_to_preferences() + varedit.save_to_preferences() + flow_graph_update() + elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR: + # TODO: There may be issues at startup since these aren't triggered + # the same was as Gtk.Actions when loading preferences. + action.set_active(not action.get_active()) + # Just assume this was triggered because it was enabled. + main.update_panel_visibility(main.VARIABLES, action.get_active()) + action.save_to_preferences() + + # Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR.set_enabled(action.get_active()) + action.save_to_preferences() + elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR: + action.set_active(not action.get_active()) + if self.init: + Dialogs.MessageDialogWrapper( + main, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, + markup="Moving the variable editor requires a restart of GRC." + ).run_and_destroy() + action.save_to_preferences() + ################################################## + # Param Modifications + ################################################## + elif action == Actions.BLOCK_PARAM_MODIFY: + selected_block = args[0] if args[0] else flow_graph.selected_block + if selected_block: + self.dialog = PropsDialog(self.main_window, selected_block) + response = Gtk.ResponseType.APPLY + while response == Gtk.ResponseType.APPLY: # rerun the dialog if Apply was hit + response = self.dialog.run() + if response in (Gtk.ResponseType.APPLY, Gtk.ResponseType.ACCEPT): + flow_graph_update() + page.state_cache.save_new_state(flow_graph.export_data()) + page.saved = False + else: # restore the current state + n = page.state_cache.get_current_state() + flow_graph.import_data(n) + flow_graph_update() + if response == Gtk.ResponseType.APPLY: + # null action, that updates the main window + Actions.ELEMENT_SELECT() + self.dialog.destroy() + self.dialog = None + elif action == Actions.EXTERNAL_UPDATE: + page.state_cache.save_new_state(flow_graph.export_data()) + flow_graph_update() + if self.dialog is not None: + self.dialog.update_gui(force=True) + page.saved = False + elif action == Actions.VARIABLE_EDITOR_UPDATE: + page.state_cache.save_new_state(flow_graph.export_data()) + flow_graph_update() + page.saved = False + ################################################## + # View Parser Errors + ################################################## + elif action == Actions.XML_PARSER_ERRORS_DISPLAY: + ParserErrorsDialog(ParseXML.xml_failures).run() + ################################################## + # Undo/Redo + ################################################## + elif action == Actions.FLOW_GRAPH_UNDO: + n = page.state_cache.get_prev_state() + if n: + flow_graph.unselect() + flow_graph.import_data(n) + flow_graph_update() + page.saved = False + elif action == Actions.FLOW_GRAPH_REDO: + n = page.state_cache.get_next_state() + if n: + flow_graph.unselect() + flow_graph.import_data(n) + flow_graph_update() + page.saved = False + ################################################## + # New/Open/Save/Close + ################################################## + elif action == Actions.FLOW_GRAPH_NEW: + main.new_page() + if args: + flow_graph = main.current_page.flow_graph + flow_graph._options_block.get_param('generate_options').set_value(args[0]) + flow_graph_update(flow_graph) + elif action == Actions.FLOW_GRAPH_OPEN: + file_paths = args[0] if args[0] else FileDialogs.OpenFlowGraph(main, page.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): + main.new_page(file_path, show=(i==0)) + self.config.add_recent_file(file_path) + main.tool_bar.refresh_submenus() + #main.menu_bar.refresh_submenus() + elif action == Actions.FLOW_GRAPH_OPEN_QSS_THEME: + file_paths = FileDialogs.OpenQSS(main, self.platform.config.install_prefix + + '/share/gnuradio/themes/').run() + if file_paths: + self.platform.config.default_qss_theme = file_paths[0] + elif action == Actions.FLOW_GRAPH_CLOSE: + main.close_page() + elif action == Actions.FLOW_GRAPH_SAVE: + #read-only or undefined file path, do save-as + if page.get_read_only() or not page.file_path: + Actions.FLOW_GRAPH_SAVE_AS() + #otherwise try to save + else: + try: + ParseXML.to_file(flow_graph.export_data(), page.file_path) + flow_graph.grc_file_path = page.file_path + page.saved = True + except IOError: + Messages.send_fail_save(page.file_path) + page.saved = False + elif action == Actions.FLOW_GRAPH_SAVE_AS: + file_path = FileDialogs.SaveFlowGraph(main, page.file_path).run() + if file_path is not None: + page.file_path = os.path.abspath(file_path) + Actions.FLOW_GRAPH_SAVE() + self.config.add_recent_file(file_path) + main.tool_bar.refresh_submenus() + #TODO + #main.menu_bar.refresh_submenus() + elif action == Actions.FLOW_GRAPH_SAVE_COPY: + try: + if not page.file_path: + # Make sure the current flowgraph has been saved + Actions.FLOW_GRAPH_SAVE_AS() + else: + dup_file_path = page.file_path + dup_file_name = '.'.join(dup_file_path.split('.')[:-1]) + "_copy" # Assuming .grc extension at the end of file_path + dup_file_path_temp = dup_file_name+'.grc' + count = 1 + while os.path.exists(dup_file_path_temp): + dup_file_path_temp = dup_file_name+'('+str(count)+').grc' + count += 1 + dup_file_path_user = FileDialogs.SaveFlowGraph(main, dup_file_path_temp).run() + if dup_file_path_user is not None: + ParseXML.to_file(flow_graph.export_data(), dup_file_path_user) + Messages.send('Saved Copy to: "' + dup_file_path_user + '"\n') + except IOError: + Messages.send_fail_save("Can not create a copy of the flowgraph\n") + elif action == Actions.FLOW_GRAPH_DUPLICATE: + previous = flow_graph + # Create a new page + main.new_page() + page = main.current_page + new_flow_graph = page.flow_graph + # Import the old data and mark the current as not saved + new_flow_graph.import_data(previous.export_data()) + flow_graph_update(new_flow_graph) + page.saved = False + elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE: + file_path, background_transparent = FileDialogs.SaveScreenShot(main, page.file_path).run() + if file_path is not None: + try: + Utils.make_screenshot(flow_graph, file_path, background_transparent) + except ValueError: + Messages.send('Failed to generate screen shot\n') + ################################################## + # Gen/Exec/Stop + ################################################## + elif action == Actions.FLOW_GRAPH_GEN: + if not page.process: + if not page.saved or not page.file_path: + Actions.FLOW_GRAPH_SAVE() # only save if file path missing or not saved + if page.saved and page.file_path: + generator = page.get_generator() + try: + Messages.send_start_gen(generator.file_path) + generator.write() + except Exception as e: + Messages.send_fail_gen(e) + else: + self.generator = None + elif action == Actions.FLOW_GRAPH_EXEC: + if not page.process: + Actions.FLOW_GRAPH_GEN() + xterm = self.platform.config.xterm_executable + Dialogs.show_missing_xterm(main, xterm) + if self.config.xterm_missing() != xterm: + if not os.path.exists(xterm): + Dialogs.show_missing_xterm(main, xterm) + self.config.xterm_missing(xterm) + if page.saved and page.file_path: + Executor.ExecFlowGraphThread( + flow_graph_page=page, + xterm_executable=xterm, + callback=self.update_exec_stop + ) + elif action == Actions.FLOW_GRAPH_KILL: + if page.process: + try: + page.process.kill() + except: + print("could not kill process: %d" % page.process.pid) + elif action == Actions.PAGE_CHANGE: # pass and run the global actions + pass + elif action == Actions.RELOAD_BLOCKS: + self.platform.build_block_library() + main.btwin.repopulate() + Actions.XML_PARSER_ERRORS_DISPLAY.set_enabled(bool( + ParseXML.xml_failures)) + Messages.send_xml_errors_if_any(ParseXML.xml_failures) + # Force a redraw of the graph, by getting the current state and re-importing it + main.update_pages() + + elif action == Actions.FIND_BLOCKS: + main.update_panel_visibility(main.BLOCKS, True) + main.btwin.search_entry.show() + main.btwin.search_entry.grab_focus() + elif action == Actions.OPEN_HIER: + for b in flow_graph.selected_blocks(): + if b._grc_source: + main.new_page(b._grc_source, show=True) + elif action == Actions.BUSSIFY_SOURCES: + for b in flow_graph.selected_blocks(): + b.bussify('source') + flow_graph._old_selected_port = None + flow_graph._new_selected_port = None + Actions.ELEMENT_CREATE() + + elif action == Actions.BUSSIFY_SINKS: + for b in flow_graph.selected_blocks(): + b.bussify('sink') + flow_graph._old_selected_port = None + flow_graph._new_selected_port = None + Actions.ELEMENT_CREATE() + + elif action == Actions.TOOLS_RUN_FDESIGN: + subprocess.Popen('gr_filter_design', + shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + else: + log.warning('!!! Action "%s" not handled !!!' % action) + ################################################## + # Global Actions for all States + ################################################## + page = main.current_page # page and flow graph might have changed + flow_graph = page.flow_graph if page else None + + selected_blocks = list(flow_graph.selected_blocks()) + selected_block = selected_blocks[0] if selected_blocks else None + + #update general buttons + Actions.ERRORS_WINDOW_DISPLAY.set_enabled(not flow_graph.is_valid()) + Actions.ELEMENT_DELETE.set_enabled(bool(flow_graph.selected_elements)) + Actions.BLOCK_PARAM_MODIFY.set_enabled(bool(selected_block)) + Actions.BLOCK_ROTATE_CCW.set_enabled(bool(selected_blocks)) + Actions.BLOCK_ROTATE_CW.set_enabled(bool(selected_blocks)) + #update alignment options + for act in Actions.BLOCK_ALIGNMENTS: + if act: + act.set_enabled(len(selected_blocks) > 1) + #update cut/copy/paste + Actions.BLOCK_CUT.set_enabled(bool(selected_blocks)) + Actions.BLOCK_COPY.set_enabled(bool(selected_blocks)) + Actions.BLOCK_PASTE.set_enabled(bool(self.clipboard)) + #update enable/disable/bypass + can_enable = any(block.state != 'enabled' + for block in selected_blocks) + can_disable = any(block.state != 'disabled' + for block in selected_blocks) + can_bypass_all = ( + all(block.can_bypass() for block in selected_blocks) and + any(not block.get_bypassed() for block in selected_blocks) + ) + Actions.BLOCK_ENABLE.set_enabled(can_enable) + Actions.BLOCK_DISABLE.set_enabled(can_disable) + Actions.BLOCK_BYPASS.set_enabled(can_bypass_all) + + Actions.BLOCK_CREATE_HIER.set_enabled(bool(selected_blocks)) + Actions.OPEN_HIER.set_enabled(bool(selected_blocks)) + Actions.BUSSIFY_SOURCES.set_enabled(bool(selected_blocks)) + Actions.BUSSIFY_SINKS.set_enabled(bool(selected_blocks)) + Actions.RELOAD_BLOCKS.enable() + Actions.FIND_BLOCKS.enable() + + self.update_exec_stop() + + Actions.FLOW_GRAPH_SAVE.set_enabled(not page.saved) + main.update() + + flow_graph.update_selected() + page.drawing_area.queue_draw() + + return True # Action was handled + + def update_exec_stop(self): + """ + Update the exec and stop buttons. + Lock and unlock the mutex for race conditions with exec flow graph threads. + """ + page = self.main_window.current_page + sensitive = page.flow_graph.is_valid() and not page.process + Actions.FLOW_GRAPH_GEN.set_enabled(sensitive) + Actions.FLOW_GRAPH_EXEC.set_enabled(sensitive) + Actions.FLOW_GRAPH_KILL.set_enabled(page.process is not None) |