diff options
-rw-r--r-- | grc/gui/ActionHandler.py | 108 | ||||
-rw-r--r-- | grc/gui/Actions.py | 15 | ||||
-rw-r--r-- | grc/gui/Bars.py | 4 | ||||
-rw-r--r-- | grc/gui/MainWindow.py | 120 | ||||
-rw-r--r-- | grc/gui/Param.py | 19 | ||||
-rw-r--r-- | grc/gui/Preferences.py | 17 | ||||
-rw-r--r-- | grc/gui/VariableEditor.py | 283 |
7 files changed, 507 insertions, 59 deletions
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index e5f14bc5e8..d0410e0e0a 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -67,6 +67,7 @@ class ActionHandler: Messages.send_init(platform) #initialize self.init_file_paths = file_paths + self.init = False Actions.APPLICATION_INITIALIZE() def _handle_key_press(self, widget, event): @@ -103,10 +104,13 @@ class ActionHandler: def _handle_action(self, action, *args): #print action - page = self.main_window.get_page() + main = self.main_window + page = main.get_page() flow_graph = page.get_flow_graph() if page else None + def flow_graph_update(fg=flow_graph): + main.vars.update_gui() fg.update() ################################################## @@ -117,13 +121,13 @@ class ActionHandler: self.init_file_paths = filter(os.path.exists, Preferences.get_open_files()) 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 file_path: main.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) + main.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 + main.new_page() # ensure that at least a blank page exists - self.main_window.btwin.search_entry.hide() + main.btwin.search_entry.hide() # Disable all actions, then re-enable a few for action in Actions.get_all_actions(): @@ -142,6 +146,8 @@ class ActionHandler: Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB, Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY, Actions.FLOW_GRAPH_OPEN_QSS_THEME, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR, Actions.TOGGLE_HIDE_VARIABLES, Actions.SELECT_ALL, ): @@ -151,9 +157,9 @@ class ActionHandler: if ParseXML.xml_failures: Messages.send_xml_errors_if_any(ParseXML.xml_failures) Actions.XML_PARSER_ERRORS_DISPLAY.set_sensitive(True) - + self.init = True elif action == Actions.APPLICATION_QUIT: - if self.main_window.close_pages(): + if main.close_pages(): gtk.main_quit() exit(0) ################################################## @@ -250,7 +256,7 @@ class ActionHandler: # Copy the selected blocks and paste them into a new page # then move the flowgraph to a reasonable position Actions.BLOCK_COPY() - self.main_window.new_page() + main.new_page() Actions.BLOCK_PASTE() coords = (x_min,y_min) flow_graph.move_selected(coords) @@ -390,33 +396,33 @@ class ActionHandler: Dialogs.ErrorsDialog(flow_graph) elif action == Actions.TOGGLE_REPORTS_WINDOW: if action.get_active(): - self.main_window.reports_scrolled_window.show() + main.update_panel_visibility(main.REPORTS, True) else: - self.main_window.reports_scrolled_window.hide() + main.update_panel_visibility(main.REPORTS, False) action.save_to_preferences() elif action == Actions.TOGGLE_BLOCKS_WINDOW: if action.get_active(): - self.main_window.btwin.show() + main.update_panel_visibility(main.BLOCKS, True) else: - self.main_window.btwin.hide() + main.update_panel_visibility(main.BLOCKS, False) action.save_to_preferences() elif action == Actions.TOGGLE_SCROLL_LOCK: active = action.get_active() - self.main_window.text_display.scroll_lock = active + main.text_display.scroll_lock = active if active: - self.main_window.text_display.scroll_to_end() + main.text_display.scroll_to_end() action.save_to_preferences() elif action == Actions.CLEAR_REPORTS: - self.main_window.text_display.clear() + main.text_display.clear() elif action == Actions.SAVE_REPORTS: file_path = SaveReportsFileDialog(page.get_file_path()).run() if file_path is not None: - self.main_window.text_display.save(file_path) + main.text_display.save(file_path) elif action == Actions.TOGGLE_HIDE_DISABLED_BLOCKS: Actions.NOTHING_SELECT() elif action == Actions.TOGGLE_AUTO_HIDE_PORT_LABELS: action.save_to_preferences() - for page in self.main_window.get_pages(): + for page in main.get_pages(): page.get_flow_graph().create_shapes() elif action in (Actions.TOGGLE_SNAP_TO_GRID, Actions.TOGGLE_SHOW_BLOCK_COMMENTS, @@ -424,11 +430,40 @@ class ActionHandler: action.save_to_preferences() elif action == Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY: action.save_to_preferences() - for page in self.main_window.get_pages(): + for page in main.get_pages(): flow_graph_update(page.get_flow_graph()) elif action == Actions.TOGGLE_HIDE_VARIABLES: + # Call the variable editor TOGGLE in case it needs to be showing + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR() Actions.NOTHING_SELECT() action.save_to_preferences() + elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR: + # See if the variables are hidden + if Actions.TOGGLE_HIDE_VARIABLES.get_active(): + # Force this to be shown + main.update_panel_visibility(main.VARIABLES, True) + action.set_active(True) + action.set_sensitive(False) + else: + if not action.get_sensitive(): + # This is occurring after variables are un-hidden + # Leave it enabled + action.set_sensitive(True) + action.set_active(True) + elif action.get_active(): + main.update_panel_visibility(main.VARIABLES, True) + else: + main.update_panel_visibility(main.VARIABLES, False) + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR.set_sensitive(action.get_active()) + action.save_to_preferences() + elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR: + if self.init: + md = gtk.MessageDialog(main, + gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, + gtk.BUTTONS_CLOSE, "Moving the variable editor requires a restart of GRC.") + md.run() + md.destroy() + action.save_to_preferences() ################################################## # Param Modifications ################################################## @@ -461,6 +496,10 @@ class ActionHandler: if self.dialog is not None: self.dialog.update_gui(force=True) page.set_saved(False) + elif action == Actions.VARIABLE_EDITOR_UPDATE: + page.get_state_cache().save_new_state(flow_graph.export_data()) + flow_graph_update() + page.set_saved(False) ################################################## # View Parser Errors ################################################## @@ -487,7 +526,7 @@ class ActionHandler: # New/Open/Save/Close ################################################## elif action == Actions.FLOW_GRAPH_NEW: - self.main_window.new_page() + main.new_page() if args: flow_graph._options_block.get_param('generate_options').set_value(args[0]) flow_graph_update() @@ -495,10 +534,11 @@ class ActionHandler: file_paths = args if args else OpenFlowGraphFileDialog(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)) + main.new_page(file_path, show=(i==0)) Preferences.add_recent_file(file_path) - self.main_window.tool_bar.refresh_submenus() - self.main_window.menu_bar.refresh_submenus() + main.tool_bar.refresh_submenus() + main.menu_bar.refresh_submenus() + main.vars.update_gui() elif action == Actions.FLOW_GRAPH_OPEN_QSS_THEME: file_paths = OpenQSSFileDialog(self.platform.config.install_prefix + @@ -511,7 +551,7 @@ class ActionHandler: except Exception as e: Messages.send("Failed to save QSS preference: " + str(e)) elif action == Actions.FLOW_GRAPH_CLOSE: - self.main_window.close_page() + 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.get_file_path(): @@ -531,8 +571,8 @@ class ActionHandler: page.set_file_path(file_path) Actions.FLOW_GRAPH_SAVE() Preferences.add_recent_file(file_path) - self.main_window.tool_bar.refresh_submenus() - self.main_window.menu_bar.refresh_submenus() + main.tool_bar.refresh_submenus() + main.menu_bar.refresh_submenus() elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE: file_path, background_transparent = SaveScreenShotDialog(page.get_file_path()).run() if file_path is not None: @@ -572,22 +612,22 @@ class ActionHandler: pass elif action == Actions.RELOAD_BLOCKS: self.platform.load_blocks() - self.main_window.btwin.clear() - self.platform.load_block_tree(self.main_window.btwin) + main.btwin.clear() + self.platform.load_block_tree(main.btwin) Actions.XML_PARSER_ERRORS_DISPLAY.set_sensitive(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 - self.main_window.update_pages() + main.update_pages() elif action == Actions.FIND_BLOCKS: - self.main_window.btwin.show() - self.main_window.btwin.search_entry.show() - self.main_window.btwin.search_entry.grab_focus() + 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.get_selected_blocks(): if b._grc_source: - self.main_window.new_page(b._grc_source, show=True) + main.new_page(b._grc_source, show=True) elif action == Actions.BUSSIFY_SOURCES: n = {'name':'bus', 'type':'bus'} for b in flow_graph.get_selected_blocks(): @@ -613,7 +653,7 @@ class ActionHandler: ################################################## # Global Actions for all States ################################################## - page = self.main_window.get_page() # page and flowgraph might have changed + page = main.get_page() # page and flowgraph might have changed flow_graph = page.get_flow_graph() if page else None selected_blocks = flow_graph.get_selected_blocks() @@ -654,7 +694,7 @@ class ActionHandler: self.update_exec_stop() #saved status Actions.FLOW_GRAPH_SAVE.set_sensitive(not page.get_saved()) - self.main_window.update() + main.update() try: #set the size of the flow graph area (if changed) new_size = (flow_graph.get_option('window_size') or self.platform.config.default_canvas_size) diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index e2670292b5..d96e80cee3 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -173,6 +173,7 @@ class ToggleAction(gtk.ToggleAction, _ActionBase): ######################################################################## PAGE_CHANGE = Action() EXTERNAL_UPDATE = Action() +VARIABLE_EDITOR_UPDATE = Action() FLOW_GRAPH_NEW = Action( label='_New', tooltip='Create a new flow graph', @@ -335,6 +336,20 @@ TOGGLE_HIDE_VARIABLES = ToggleAction( preference_name='hide_variables', default=False, ) +TOGGLE_FLOW_GRAPH_VAR_EDITOR = ToggleAction( + label='Show _Variable Editor', + tooltip='Show the variable editor. Modify variables and imports in this flow graph', + stock_id=gtk.STOCK_EDIT, + default=True, + keypresses=(gtk.keysyms.e, gtk.gdk.CONTROL_MASK), + preference_name='variable_editor_visable', +) +TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR = ToggleAction( + label='Move Variable Editor to the Sidebar', + tooltip='Move the variable editor to the sidebar', + default=False, + preference_name='variable_editor_sidebar', +) TOGGLE_AUTO_HIDE_PORT_LABELS = ToggleAction( label='Auto-Hide _Port Labels', tooltip='Automatically hide port labels', diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index c0c4beef75..5bbbdc0d77 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -31,6 +31,7 @@ TOOLBAR_LIST = ( Actions.FLOW_GRAPH_SAVE, Actions.FLOW_GRAPH_CLOSE, None, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, Actions.FLOW_GRAPH_SCREEN_CAPTURE, None, Actions.BLOCK_CUT, @@ -103,6 +104,9 @@ MENU_BAR_LIST = ( Actions.CLEAR_REPORTS, None, Actions.TOGGLE_HIDE_VARIABLES, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR, + None, Actions.TOGGLE_HIDE_DISABLED_BLOCKS, Actions.TOGGLE_AUTO_HIDE_PORT_LABELS, Actions.TOGGLE_SNAP_TO_GRID, diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index ff7705837f..0b6bb7498f 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -23,6 +23,7 @@ import gtk from . import Bars, Actions, Preferences, Utils from .BlockTreeWindow import BlockTreeWindow +from .VariableEditor import VariableEditor from .Constants import \ NEW_FLOGRAPH_TITLE, DEFAULT_REPORTS_WINDOW_WIDTH from .Dialogs import TextDisplay, MessageDialogHelper @@ -56,6 +57,7 @@ PAGE_TITLE_MARKUP_TMPL = """\ #end if """ + ############################################################ # Main window ############################################################ @@ -63,6 +65,11 @@ PAGE_TITLE_MARKUP_TMPL = """\ class MainWindow(gtk.Window): """The topmost window with menus, the tool bar, and other major windows.""" + # Constants the action handler can use to indicate which panel visibility to change. + BLOCKS = 0 + REPORTS = 1 + VARIABLES = 2 + def __init__(self, platform, action_handler_callback): """ MainWindow contructor @@ -76,48 +83,80 @@ class MainWindow(gtk.Window): (o.get_key(), o.get_name(), o.get_key() == generate_mode_default) for o in gen_opts.get_options()] - # load preferences + # Load preferences Preferences.load(platform) - #setup window + + # Setup window gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) vbox = gtk.VBox() - self.hpaned = gtk.HPaned() self.add(vbox) - #create the menu bar and toolbar + + # Create the menu bar and toolbar self.add_accel_group(Actions.get_accel_group()) self.menu_bar = Bars.MenuBar(generate_modes, action_handler_callback) vbox.pack_start(self.menu_bar, False) - self.tool_bar = Bars.Toolbar(generate_modes, action_handler_callback ) + self.tool_bar = Bars.Toolbar(generate_modes, action_handler_callback) vbox.pack_start(self.tool_bar, False) - vbox.pack_start(self.hpaned) - #create the notebook + + # Main parent container for the different panels + self.container = gtk.HPaned() + vbox.pack_start(self.container) + + # Create the notebook self.notebook = gtk.Notebook() self.page_to_be_closed = None self.current_page = None self.notebook.set_show_border(False) self.notebook.set_scrollable(True) #scroll arrows for page tabs self.notebook.connect('switch-page', self._handle_page_change) - #setup containers - self.flow_graph_vpaned = gtk.VPaned() - #flow_graph_box.pack_start(self.scrolled_window) - self.flow_graph_vpaned.pack1(self.notebook) - self.hpaned.pack1(self.flow_graph_vpaned) - self.btwin = BlockTreeWindow(platform, self.get_flow_graph); - self.hpaned.pack2(self.btwin, False) #dont allow resize - #create the reports window + + # Create the console window self.text_display = TextDisplay() - #house the reports in a scrolled window self.reports_scrolled_window = gtk.ScrolledWindow() self.reports_scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.reports_scrolled_window.add(self.text_display) self.reports_scrolled_window.set_size_request(-1, DEFAULT_REPORTS_WINDOW_WIDTH) - self.flow_graph_vpaned.pack2(self.reports_scrolled_window, False) #dont allow resize + + # Create the block tree and variable panels + self.btwin = BlockTreeWindow(platform, self.get_flow_graph) + self.vars = VariableEditor(platform, self.get_flow_graph) + + # Figure out which place to put the variable editor + self.left = gtk.VPaned() + self.right = gtk.VPaned() + self.left_subpanel = gtk.HPaned() + + self.variable_panel_sidebar = Preferences.variable_editor_sidebar() + if self.variable_panel_sidebar: + self.left.pack1(self.notebook) + self.left.pack2(self.reports_scrolled_window, False) + self.right.pack1(self.btwin) + self.right.pack2(self.vars, False) + else: + # Put the variable editor in a panel with the reports + self.left.pack1(self.notebook) + self.left_subpanel.pack1(self.reports_scrolled_window, shrink=False) + self.left_subpanel.pack2(self.vars, resize=False, shrink=True) + self.left.pack2(self.left_subpanel, False) + + # Create the right panel + self.right.pack1(self.btwin) + + self.container.pack1(self.left) + self.container.pack2(self.right, False) + #load preferences and show the main window self.resize(*Preferences.main_window_size()) - self.flow_graph_vpaned.set_position(Preferences.reports_window_position()) - self.hpaned.set_position(Preferences.blocks_window_position()) + self.container.set_position(Preferences.blocks_window_position()) + self.left.set_position(Preferences.reports_window_position()) + if self.variable_panel_sidebar: + self.right.set_position(Preferences.variable_editor_position(sidebar=True)) + else: + self.left_subpanel.set_position(Preferences.variable_editor_position()) + self.show_all() self.reports_scrolled_window.hide() + self.vars.hide() self.btwin.hide() ############################################################ @@ -150,6 +189,38 @@ class MainWindow(gtk.Window): self.current_page = self.notebook.get_nth_page(page_num) Actions.PAGE_CHANGE() + def update_panel_visibility(self, panel, visibility=True): + """ + Handles changing visibility of panels. + """ + # Set the visibility for the requested panel, then update the containers if they need + # to be hidden as well. + + if panel == self.BLOCKS: + self.btwin.set_visible(visibility) + elif panel == self.REPORTS: + self.reports_scrolled_window.set_visible(visibility) + elif panel == self.VARIABLES: + self.vars.set_visible(visibility) + else: + return + + if self.variable_panel_sidebar: + # If both the variable editor and block panels are hidden, hide the right container + if not self.btwin.get_visible() and not self.vars.get_visible(): + self.right.hide() + else: + self.right.show() + else: + if not self.btwin.get_visible(): + self.right.hide() + else: + self.right.show() + if not self.vars.get_visible() and not self.reports_scrolled_window.get_visible(): + self.left_subpanel.hide() + else: + self.left_subpanel.show() + ############################################################ # Report Window ############################################################ @@ -226,8 +297,12 @@ class MainWindow(gtk.Window): Preferences.set_open_files(open_files) Preferences.file_open(open_file) Preferences.main_window_size(self.get_size()) - Preferences.reports_window_position(self.flow_graph_vpaned.get_position()) - Preferences.blocks_window_position(self.hpaned.get_position()) + Preferences.reports_window_position(self.left.get_position()) + Preferences.blocks_window_position(self.container.get_position()) + if self.variable_panel_sidebar: + Preferences.variable_editor_position(self.right.get_position(), sidebar=True) + else: + Preferences.variable_editor_position(self.left_subpanel.get_position()) Preferences.save() return True @@ -297,6 +372,9 @@ class MainWindow(gtk.Window): #show/hide notebook tabs self.notebook.set_show_tabs(len(self.get_pages()) > 1) + # Need to update the variable window when changing + self.vars.update_gui() + def update_pages(self): """ Forces a reload of all the pages in this notebook. diff --git a/grc/gui/Param.py b/grc/gui/Param.py index bf0a59b96b..4b5a3c294a 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -84,7 +84,7 @@ class InputParam(gtk.HBox): self._have_pending_changes = True self._update_gui() if self._editing_callback: - self._editing_callback() + self._editing_callback(self, None) def _apply_change(self, *args): """ @@ -95,7 +95,7 @@ class InputParam(gtk.HBox): self.param.set_value(self.get_text()) #call the callback if self._changed_callback: - self._changed_callback(*args) + self._changed_callback(self, None) else: self.param.validate() #gui update @@ -129,8 +129,19 @@ class EntryParam(InputParam): return self._input.get_text() def set_color(self, color): - self._input.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) - self._input.modify_text(gtk.STATE_NORMAL, Colors.PARAM_ENTRY_TEXT_COLOR) + need_status_color = self.label not in self.get_children() + text_color = ( + Colors.PARAM_ENTRY_TEXT_COLOR if not need_status_color else + gtk.gdk.color_parse('blue') if self._have_pending_changes else + gtk.gdk.color_parse('red') if not self.param.is_valid() else + Colors.PARAM_ENTRY_TEXT_COLOR) + base_color = ( + Colors.BLOCK_DISABLED_COLOR + if need_status_color and not self.param.get_parent().get_enabled() + else gtk.gdk.color_parse(color) + ) + self._input.modify_base(gtk.STATE_NORMAL, base_color) + self._input.modify_text(gtk.STATE_NORMAL, text_color) def set_tooltip_text(self, text): try: diff --git a/grc/gui/Preferences.py b/grc/gui/Preferences.py index 1a194fd8c5..f74550cd77 100644 --- a/grc/gui/Preferences.py +++ b/grc/gui/Preferences.py @@ -148,6 +148,23 @@ def blocks_window_position(pos=None): return entry('blocks_window_position', pos, default=-1) or 1 +def variable_editor_position(pos=None, sidebar=False): + # Figure out default + if sidebar: + w, h = main_window_size() + return entry('variable_editor_sidebar_position', pos, default=int(h*0.7)) + else: + return entry('variable_editor_position', pos, default=int(blocks_window_position()*0.5)) + + +def variable_editor_sidebar(pos=None): + return entry('variable_editor_sidebar', pos, default=False) + + +def variable_editor_confirm_delete(pos=None): + return entry('variable_editor_confirm_delete', pos, default=True) + + def xterm_missing(cmd=None): return entry('xterm_missing', cmd, default='INVALID_XTERM_SETTING') diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py new file mode 100644 index 0000000000..fcb1f4d2e9 --- /dev/null +++ b/grc/gui/VariableEditor.py @@ -0,0 +1,283 @@ +""" +Copyright 2015 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 itertools import chain +from operator import attrgetter + +import pygtk +pygtk.require('2.0') +import gtk +import gobject + +from . import Actions +from . import Preferences +from .Constants import DEFAULT_BLOCKS_WINDOW_WIDTH + +BLOCK_INDEX = 0 +ID_INDEX = 1 + + +class VariableEditor(gtk.VBox): + + # Actions that are handled by the editor + ADD_IMPORT = 0 + ADD_VARIABLE = 1 + OPEN_PROPERTIES = 2 + DELETE_BLOCK = 3 + DELETE_CONFIRM = 4 + ENABLE_BLOCK = 5 + DISABLE_BLOCK = 6 + + def __init__(self, platform, get_flow_graph): + gtk.VBox.__init__(self) + self.platform = platform + self.get_flow_graph = get_flow_graph + self._block = None + + # Only use the model to store the block reference and name. + # Generate everything else dynamically + self.treestore = gtk.TreeStore(gobject.TYPE_PYOBJECT, # Block reference + gobject.TYPE_STRING) # Category and block name + self.treeview = gtk.TreeView(self.treestore) + self.treeview.set_enable_search(True) + self.treeview.set_search_column(ID_INDEX) + self.treeview.get_selection().set_mode('single') + self.treeview.set_headers_visible(True) + self.treeview.connect('button-press-event', self._handle_mouse_button_press) + self.treeview.connect('key-press-event', self._handle_key_button_press) + + # Block Name or Category + self.id_cell = gtk.CellRendererText() + self.id_cell.connect('edited', self._handle_name_edited_cb) + id_column = gtk.TreeViewColumn("Id", self.id_cell, text=ID_INDEX) + id_column.set_name("id") + id_column.set_resizable(True) + id_column.set_max_width(300) + id_column.set_min_width(80) + id_column.set_fixed_width(100) + id_column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + id_column.set_cell_data_func(self.id_cell, self.set_properties) + self.treeview.append_column(id_column) + self.treestore.set_sort_column_id(ID_INDEX, gtk.SORT_ASCENDING) + + # Block Value + self.value_cell = gtk.CellRendererText() + self.value_cell.connect('edited', self._handle_value_edited_cb) + value_column = gtk.TreeViewColumn("Value", self.value_cell) + value_column.set_name("value") + value_column.set_resizable(False) + value_column.set_expand(True) + value_column.set_min_width(100) + value_column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) + value_column.set_cell_data_func(self.value_cell, self.set_value) + self.treeview.append_column(value_column) + + # Block Actions (Add, Remove) + self.action_cell = gtk.CellRendererPixbuf() + value_column.pack_start(self.action_cell, False) + value_column.set_cell_data_func(self.action_cell, self.set_icon) + + # Make the scrolled window to hold the tree view + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolled_window.add_with_viewport(self.treeview) + scrolled_window.set_size_request(DEFAULT_BLOCKS_WINDOW_WIDTH, -1) + self.pack_start(scrolled_window) + + # Context menus + self._confirm_delete = Preferences.variable_editor_confirm_delete() + + # Sets cell contents + def set_icon(self, col, cell, model, iter): + block = model.get_value(iter, BLOCK_INDEX) + if block: + pb = self.treeview.render_icon(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU, None) + else: + pb = self.treeview.render_icon(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU, None) + cell.set_property('pixbuf', pb) + + def set_value(self, col, cell, model, iter): + sp = cell.set_property + block = model.get_value(iter, BLOCK_INDEX) + + # Set the default properties for this column first. + # Some set in set_properties() may be overridden (editable for advanced variable blocks) + self.set_properties(col, cell, model, iter) + + # Set defaults + value = None + evaluated = None + self.set_tooltip_text(None) + + # Block specific values + if block: + if block.get_key() == 'import': + value = block.get_param('import').get_value() + else: + if block.get_key() != "variable": + value = "<Open Properties>" + sp('editable', False) + else: + value = block.get_param('value').get_value() + + # Check if there are errors in the blocks. + # Show the block error as a tooltip + error_message = block.get_error_messages() + if len(error_message) > 0: + # Set the error message to the last error in the list. + # This should be the first message generated + self.set_tooltip_text(error_message[-1]) + else: + # Evaluate and show the value (if it is a variable) + if block.get_key() == "variable": + evaluated = str(block.get_param('value').evaluate()) + self.set_tooltip_text(evaluated) + # Always set the text value. + sp('text', value) + + def set_properties(self, col, cell, model, iter): + sp = cell.set_property + block = model.get_value(iter, BLOCK_INDEX) + # Set defaults + sp('sensitive', True) + sp('editable', False) + sp('foreground', None) + + # Block specific changes + if block: + if not block.get_enabled(): + # Disabled block. But, this should still be editable + sp('editable', True) + sp('foreground', 'gray') + else: + sp('editable', True) + if block.get_error_messages(): + sp('foreground', 'red') + + def update_gui(self): + if not self.get_flow_graph(): + return + self._update_blocks() + self._rebuild() + self.treeview.expand_all() + + def _update_blocks(self): + self._imports = filter(attrgetter('is_import'), + self.get_flow_graph().blocks) + self._variables = filter(attrgetter('is_variable'), + self.get_flow_graph().blocks) + + def _rebuild(self, *args): + self.treestore.clear() + imports = self.treestore.append(None, [None, 'Imports']) + variables = self.treestore.append(None, [None, 'Variables']) + for block in self._imports: + self.treestore.append(imports, [block, block.get_param('id').get_value()]) + for block in sorted(self._variables, key=lambda v: v.get_id()): + self.treestore.append(variables, [block, block.get_param('id').get_value()]) + + def _handle_name_edited_cb(self, cell, path, new_text): + block = self.treestore[path][BLOCK_INDEX] + block.get_param('id').set_value(new_text) + Actions.VARIABLE_EDITOR_UPDATE() + + def _handle_value_edited_cb(self, cell, path, new_text): + block = self.treestore[path][BLOCK_INDEX] + if block.is_import: + block.get_param('import').set_value(new_text) + else: + block.get_param('value').set_value(new_text) + Actions.VARIABLE_EDITOR_UPDATE() + + def _handle_action(self, item, key, event=None): + """ + Single handler for the different actions that can be triggered by the context menu, + key presses or mouse clicks. Also triggers an update of the flowgraph and editor. + """ + if key == self.ADD_IMPORT: + self.get_flow_graph().add_new_block('import') + elif key == self.ADD_VARIABLE: + self.get_flow_graph().add_new_block('variable') + elif key == self.OPEN_PROPERTIES: + Actions.BLOCK_PARAM_MODIFY(self._block) + elif key == self.DELETE_BLOCK: + self.get_flow_graph().remove_element(self._block) + elif key == self.DELETE_CONFIRM: + if self._confirm_delete: + # Create a context menu to confirm the delete operation + confirmation_menu = gtk.Menu() + block_id = self._block.get_param('id').get_value().replace("_", "__") + confirm = gtk.MenuItem("Delete {}".format(block_id)) + confirm.connect('activate', self._handle_action, self.DELETE_BLOCK) + confirmation_menu.add(confirm) + confirmation_menu.show_all() + confirmation_menu.popup(None, None, None, event.button, event.time) + else: + self._handle_action(None, self.DELETE_BLOCK, None) + elif key == self.ENABLE_BLOCK: + self._block.set_enabled(True) + elif key == self.DISABLE_BLOCK: + self._block.set_enabled(False) + Actions.VARIABLE_EDITOR_UPDATE() + + def _handle_mouse_button_press(self, widget, event): + """ + Handles mouse button for several different events: + - Doublc Click to open properties for advanced blocks + - Click to add/remove blocks + """ + path = widget.get_path_at_pos(int(event.x), int(event.y)) + if path: + # If there is a valid path, then get the row, column and block selected. + row = self.treestore[path[0]] + col = path[1] + self._block = row[BLOCK_INDEX] + + if event.button == 1 and col.get_name() == "value": + # Make sure this has a block (not the import/variable rows) + if self._block and event.type == gtk.gdk._2BUTTON_PRESS: + # Open the advanced dialog if it is a gui variable + if self._block.get_key() not in ("variable", "import"): + self._handle_action(None, self.OPEN_PROPERTIES, event=event) + return True + if event.type == gtk.gdk.BUTTON_PRESS: + # User is adding/removing blocks + # Make sure this is the action cell (Add/Remove Icons) + if path[2] > col.cell_get_position(self.action_cell)[0]: + if row[1] == "Imports": + # Add a new import block. + self._handle_action(None, self.ADD_IMPORT, event=event) + elif row[1] == "Variables": + # Add a new variable block + self._handle_action(None, self.ADD_VARIABLE, event=event) + else: + self._handle_action(None, self.DELETE_CONFIRM, event=event) + return True + return False + + def _handle_key_button_press(self, widget, event): + model, path = self.treeview.get_selection().get_selected_rows() + if path and self._block: + if self._block.get_enabled() and event.string == "d": + self._handle_action(None, self.DISABLE_BLOCK, None) + return True + elif not self._block.get_enabled() and event.string == "e": + self._handle_action(None, self.ENABLE_BLOCK, None) + return True + return False |