From 94c4606edd30dc8b1278580782f2809b69f04641 Mon Sep 17 00:00:00 2001 From: Sebastian Koslowski <koslowski@kit.edu> Date: Fri, 3 Jun 2016 10:02:36 +0200 Subject: grc: py3k compat using python-modernize --- grc/gui/StateCache.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'grc/gui/StateCache.py') diff --git a/grc/gui/StateCache.py b/grc/gui/StateCache.py index 3cdb5f30ce..b109a1281b 100644 --- a/grc/gui/StateCache.py +++ b/grc/gui/StateCache.py @@ -17,8 +17,9 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -import Actions -from Constants import STATE_CACHE_SIZE +from __future__ import absolute_import +from . import Actions +from .Constants import STATE_CACHE_SIZE class StateCache(object): """ -- cgit v1.2.3 From 486e0a9d06e43f3b8669471bef13a5eeedbda4c6 Mon Sep 17 00:00:00 2001 From: Seth Hitefield <sdhitefield@gmail.com> Date: Wed, 3 May 2017 07:06:54 -0700 Subject: grc: gtk3: Converted actions to Gio.Action instead of Gtk.Action --- grc/core/Block.py | 2 +- grc/core/ParseXML.py | 2 +- grc/core/utils/expr_utils.py | 3 +- grc/gui/Actions.py | 633 ++++++++++++++++++++++--------------------- grc/gui/Application.py | 281 ++++++++++++------- grc/gui/Bars.py | 519 +++++++++++++++++------------------ grc/gui/Config.py | 2 +- grc/gui/Console.py | 6 + grc/gui/DrawingArea.py | 44 +++ grc/gui/MainWindow.py | 25 +- grc/gui/Notebook.py | 44 ++- grc/gui/ParamWidgets.py | 2 +- grc/gui/StateCache.py | 4 +- grc/gui/Utils.py | 6 +- grc/gui/VariableEditor.py | 4 +- grc/gui/canvas/param.py | 2 +- grc/main.py | 16 +- 17 files changed, 889 insertions(+), 706 deletions(-) (limited to 'grc/gui/StateCache.py') diff --git a/grc/core/Block.py b/grc/core/Block.py index 8350828092..087815b941 100644 --- a/grc/core/Block.py +++ b/grc/core/Block.py @@ -403,7 +403,7 @@ class Block(Element): return itertools.chain(self.active_sources, self.active_sinks) def get_children(self): - return self.get_ports() + self.params.values() + return self.get_ports() + list(self.params.values()) def get_children_gui(self): return self.get_ports_gui() + self.params.values() diff --git a/grc/core/ParseXML.py b/grc/core/ParseXML.py index 163289ba06..430ba5b474 100644 --- a/grc/core/ParseXML.py +++ b/grc/core/ParseXML.py @@ -156,7 +156,7 @@ def to_file(nested_data, xml_file): ), xml_declaration=True, pretty_print=True, encoding='utf-8') xml_data += etree.tostring(_to_file(nested_data)[0], pretty_print=True, encoding='utf-8') - with open(xml_file, 'w') as fp: + with open(xml_file, 'wb') as fp: fp.write(xml_data) diff --git a/grc/core/utils/expr_utils.py b/grc/core/utils/expr_utils.py index 555bd709b1..cc03e9cb1c 100644 --- a/grc/core/utils/expr_utils.py +++ b/grc/core/utils/expr_utils.py @@ -23,7 +23,7 @@ import string import six -VAR_CHARS = string.letters + string.digits + '_' +VAR_CHARS = string.ascii_letters + string.digits + '_' class graph(object): @@ -194,4 +194,3 @@ def sort_objects(objects, get_id, get_expr): sorted_ids = sort_variables(id2expr) # Return list of sorted objects return [id2obj[id] for id in sorted_ids] - diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 97162065a6..d214f28049 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -20,286 +20,323 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import import six +import logging -from gi.repository import Gtk, Gdk, GObject +from gi.repository import Gtk, Gdk, Gio, GLib, GObject -NO_MODS_MASK = 0 +log = logging.getLogger(__name__) -######################################################################## -# Actions API -######################################################################## -_actions_keypress_dict = dict() -_keymap = Gdk.Keymap.get_default() -_used_mods_mask = NO_MODS_MASK - - -def handle_key_press(event): - """ - Call the action associated with the key press event. - Both the key value and the mask must have a match. - - Args: - event: a gtk key press event - - Returns: - true if handled - """ - # extract the key value and the consumed modifiers - _unused, keyval, egroup, level, consumed = _keymap.translate_keyboard_state( - event.hardware_keycode, event.get_state(), event.group) - # get the modifier mask and ignore irrelevant modifiers - mod_mask = event.get_state() & ~consumed & _used_mods_mask - # look up the keypress and call the action - try: - _actions_keypress_dict[(keyval, mod_mask)]() - except KeyError: - return False # not handled - else: - return True # handled here - -_all_actions_list = list() - - -def get_all_actions(): - return _all_actions_list - -_accel_group = Gtk.AccelGroup() - - -def get_accel_group(): - return _accel_group - - -class _ActionBase(object): - """ - Base class for Action and ToggleAction - Register actions and keypresses with this module. - """ - def __init__(self, label, keypresses): - global _used_mods_mask - - _all_actions_list.append(self) - for i in range(len(keypresses)/2): - keyval, mod_mask = keypresses[i*2:(i+1)*2] - # register this keypress - if (keyval, mod_mask) in _actions_keypress_dict: - raise KeyError('keyval/mod_mask pair already registered "%s"' % str((keyval, mod_mask))) - _actions_keypress_dict[(keyval, mod_mask)] = self - _used_mods_mask |= mod_mask - # set the accelerator group, and accelerator path - # register the key name and mod mask with the accelerator path - if label is None: - continue # dont register accel - accel_path = '<main>/' + self.get_name() - self.set_accel_group(get_accel_group()) - self.set_accel_path(accel_path) - Gtk.AccelMap.add_entry(accel_path, keyval, mod_mask) - self.args = None + +def filter_from_dict(vars): + return filter(lambda x: isinstance(x[1], Action), vars.items()) + + +class Namespace(object): + + def __init__(self): + self._actions = {} + + def add(self, action): + key = action.props.name + self._actions[key] = action + + def connect(self, name, handler): + #log.debug("Connecting action <{}> to handler <{}>".format(name, handler.__name__)) + self._actions[name].connect('activate', handler) + + def register(self, name, parameter=None, handler=None, label=None, tooltip=None, + icon_name=None, keypresses=None, preference_name=None, default=None): + # Check types + if not isinstance(name, str): + raise TypeError("Cannot register fuction: 'name' must be a str") + if parameter and not isinstance(parameter, str): + raise TypeError("Cannot register fuction: 'parameter' must be a str") + if handler and not callable(handler): + raise TypeError("Cannot register fuction: 'handler' must be callable") + + # Check if the name has a prefix. + prefix = None + if name.startswith("app.") or name.startswith("win."): + # Set a prefix for later and remove it + prefix = name[0:3] + name = name[4:] + + if handler: + log.debug("Register action [{}, prefix={}, param={}, handler={}]".format( + name, prefix, parameter, handler.__name__)) + else: + log.debug("Register action [{}, prefix={}, param={}, handler=None]".format( + name, prefix, parameter)) + + action = Action(name, parameter, label=label, tooltip=tooltip, + icon_name=icon_name, keypresses=keypresses, prefix=prefix, + preference_name=preference_name, default=default) + if handler: + action.connect('activate', handler) + + key = name + if prefix: + key = "{}.{}".format(prefix, name) + if prefix == "app": + pass + #self.app.add_action(action) + elif prefix == "win": + pass + #self.win.add_action(action) + + #log.debug("Registering action as '{}'".format(key)) + self._actions[key] = action + return action + + + # If the actions namespace is called, trigger an action + def __call__(self, name): + # Try to parse the action string. + valid, action_name, target_value = Action.parse_detailed_name(name) + if not valid: + raise Exception("Invalid action string: '{}'".format(name)) + if action_name not in self._actions: + raise Exception("Action '{}' is not registered!".format(action_name)) + + if target_value: + self._actions[action_name].activate(target_value) + else: + self._actions[action_name].activate() + + def __getitem__(self, key): + return self._actions[key] + + def __iter__(self): + return self._actions.itervalues() + + def __repr__(self): + return str(self) + + def get_actions(self): + return self._actions def __str__(self): - """ - The string representation should be the name of the action id. - Try to find the action id for this action by searching this module. - """ - for name, value in six.iteritems(globals()): - if value == self: - return name - return self.get_name() - - def __repr__(self): return str(self) - - def __call__(self, *args): - """ - Emit the activate signal when called with (). - """ - self.args = args - self.emit('activate') - - -class Action(Gtk.Action, _ActionBase): - """ - A custom Action class based on Gtk.Action. - Pass additional arguments such as keypresses. - """ - - def __init__(self, keypresses=(), name=None, label=None, tooltip=None, - stock_id=None): - """ - Create a new Action instance. - - Args: - key_presses: a tuple of (keyval1, mod_mask1, keyval2, mod_mask2, ...) - the: regular Gtk.Action parameters (defaults to None) - """ - if name is None: - name = label - GObject.GObject.__init__(self, name=name, label=label, tooltip=tooltip, - stock_id=stock_id) - _ActionBase.__init__(self, label, keypresses) - - -class ToggleAction(Gtk.ToggleAction, _ActionBase): - """ - A custom Action class based on Gtk.ToggleAction. - Pass additional arguments such as keypresses. - """ - - def __init__(self, keypresses=(), name=None, label=None, tooltip=None, - stock_id=None, preference_name=None, default=True): - """ - Create a new ToggleAction instance. - - Args: - key_presses: a tuple of (keyval1, mod_mask1, keyval2, mod_mask2, ...) - the: regular Gtk.Action parameters (defaults to None) - """ - if name is None: - name = label - GObject.GObject.__init__(self, name=name, label=label, - tooltip=tooltip, stock_id=stock_id) - _ActionBase.__init__(self, label, keypresses) + s = "{Actions:" + for key in self._actions: + s += " {},".format(key) + s = s.rstrip(",") + "}" + return s + + +class Action(Gio.SimpleAction): + + # Change these to normal python properties. + #prefs_name + + def __init__(self, name, parameter=None, label=None, tooltip=None, + icon_name=None, keypresses=None, prefix=None, + preference_name=None, default=None): + self.name = name + self.label = label + self.tooltip = tooltip + self.icon_name = icon_name + self.keypresses = keypresses + self.prefix = prefix self.preference_name = preference_name self.default = default - def load_from_preferences(self): + # Don't worry about checking types here, since it's done in register() + # Save the parameter type to use for converting in __call__ + self.type = None + + variant = None + state = None + if parameter: + variant = GLib.VariantType.new(parameter) + if preference_name: + state = GLib.Variant.new_boolean(True) + Gio.SimpleAction.__init__(self, name=name, parameter_type=variant, state=state) + + def enable(self): + self.props.enabled = True + + def disable(self): + self.props.enabled = False + + def set_enabled(self, state): + if not isinstance(state, bool): + raise TypeError("State must be True/False.") + self.props.enabled = state + + def __str__(self): + return self.props.name + + def __repr__(self): + return str(self) + + def get_active(self): + if self.props.state: + return self.props.state.get_boolean() + return False + + def set_active(self, state): + if not isinstance(state, bool): + raise TypeError("State must be True/False.") + self.change_state(GLib.Variant.new_boolean(state)) + + # Allows actions to be directly called. + def __call__(self, parameter=None): + if self.type and parameter: + # Try to convert it to the correct type. + try: + param = GLib.Variant(self.type, parameter) + self.activate(param) + except TypeError: + raise TypeError("Invalid parameter type for action '{}'. Expected: '{}'".format(self.get_name(), self.type)) + else: + self.activate() + + def load_from_preferences(self, *args): + log.debug("load_from_preferences({})".format(args)) if self.preference_name is not None: config = Gtk.Application.get_default().config - self.set_active(config.entry( - self.preference_name, default=bool(self.default))) + self.set_active(config.entry(self.preference_name, default=bool(self.default))) - def save_to_preferences(self): + def save_to_preferences(self, *args): + log.debug("save_to_preferences({})".format(args)) if self.preference_name is not None: config = Gtk.Application.get_default().config config.entry(self.preference_name, value=self.get_active()) + +actions = Namespace() + + +def get_actions(): + return actions.get_actions() + + +def connect(action, handler=None): + return actions.connect(action, handler=handler) + + ######################################################################## -# Actions +# Old Actions ######################################################################## -PAGE_CHANGE = Action() -EXTERNAL_UPDATE = Action() -VARIABLE_EDITOR_UPDATE = Action() -FLOW_GRAPH_NEW = Action( +PAGE_CHANGE = actions.register("win.page_change") +EXTERNAL_UPDATE = actions.register("app.external_update") +VARIABLE_EDITOR_UPDATE = actions.register("app.variable_editor_update") +FLOW_GRAPH_NEW = actions.register("app.flowgraph.new", label='_New', tooltip='Create a new flow graph', - stock_id=Gtk.STOCK_NEW, - keypresses=(Gdk.KEY_n, Gdk.ModifierType.CONTROL_MASK), + icon_name='document-new', + keypresses=["<Ctrl>n"], + parameter="s", ) -FLOW_GRAPH_OPEN = Action( +FLOW_GRAPH_OPEN = actions.register("app.flowgraph.open", label='_Open', tooltip='Open an existing flow graph', - stock_id=Gtk.STOCK_OPEN, - keypresses=(Gdk.KEY_o, Gdk.ModifierType.CONTROL_MASK), + icon_name='document-open', + keypresses=["<Ctrl>o"], ) -FLOW_GRAPH_OPEN_RECENT = Action( +FLOW_GRAPH_OPEN_RECENT = actions.register("app.flowgraph.open_recent", label='Open _Recent', tooltip='Open a recently used flow graph', - stock_id=Gtk.STOCK_OPEN, + icon_name='document-open-recent', + parameter="s", ) -FLOW_GRAPH_SAVE = Action( +FLOW_GRAPH_CLEAR_RECENT = actions.register("app.flowgraph.clear_recent") +FLOW_GRAPH_SAVE = actions.register("app.flowgraph.save", label='_Save', tooltip='Save the current flow graph', - stock_id=Gtk.STOCK_SAVE, - keypresses=(Gdk.KEY_s, Gdk.ModifierType.CONTROL_MASK), + icon_name='document-save', + keypresses=["<Ctrl>s"], ) -FLOW_GRAPH_SAVE_AS = Action( +FLOW_GRAPH_SAVE_AS = actions.register("app.flowgraph.save_as", label='Save _As', tooltip='Save the current flow graph as...', - stock_id=Gtk.STOCK_SAVE_AS, - keypresses=(Gdk.KEY_s, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK), + icon_name='document-save-as', + keypresses=["<Ctrl><Shift>s"], ) -FLOW_GRAPH_SAVE_A_COPY = Action( - label='Save A Copy', - tooltip='Save the copy of current flow graph', +FLOW_GRAPH_SAVE_COPY = actions.register("app.flowgraph.save_copy", + label='Save Copy', + tooltip='Save a copy of current flow graph', ) -FLOW_GRAPH_DUPLICATE = Action( +FLOW_GRAPH_DUPLICATE = actions.register("app.flowgraph.duplicate", label='_Duplicate', tooltip='Create a duplicate of current flow graph', - stock_id=Gtk.STOCK_COPY, - keypresses=(Gdk.KEY_d, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK), + #stock_id=Gtk.STOCK_COPY, + keypresses=["<Ctrl><Shift>d"], ) -FLOW_GRAPH_CLOSE = Action( +FLOW_GRAPH_CLOSE = actions.register("app.flowgraph.close", label='_Close', tooltip='Close the current flow graph', - stock_id=Gtk.STOCK_CLOSE, - keypresses=(Gdk.KEY_w, Gdk.ModifierType.CONTROL_MASK), + icon_name='window-close', + keypresses=["<Ctrl>w"], ) -APPLICATION_INITIALIZE = Action() -APPLICATION_QUIT = Action( +APPLICATION_INITIALIZE = actions.register("app.initialize") +APPLICATION_QUIT = actions.register("app.quit", label='_Quit', tooltip='Quit program', - stock_id=Gtk.STOCK_QUIT, - keypresses=(Gdk.KEY_q, Gdk.ModifierType.CONTROL_MASK), + icon_name='application-exit', + keypresses=["<Ctrl>q"], ) -FLOW_GRAPH_UNDO = Action( +FLOW_GRAPH_UNDO = actions.register("win.undo", label='_Undo', tooltip='Undo a change to the flow graph', - stock_id=Gtk.STOCK_UNDO, - keypresses=(Gdk.KEY_z, Gdk.ModifierType.CONTROL_MASK), + icon_name='edit-undo', + keypresses=["<Ctrl>z"], ) -FLOW_GRAPH_REDO = Action( +FLOW_GRAPH_REDO = actions.register("win.redo", label='_Redo', tooltip='Redo a change to the flow graph', - stock_id=Gtk.STOCK_REDO, - keypresses=(Gdk.KEY_y, Gdk.ModifierType.CONTROL_MASK), + icon_name='edit-redo', + keypresses=["<Ctrl>y"], ) -NOTHING_SELECT = Action() -SELECT_ALL = Action( +NOTHING_SELECT = actions.register("win.unselect") +SELECT_ALL = actions.register("win.select_all", label='Select _All', tooltip='Select all blocks and connections in the flow graph', - stock_id=Gtk.STOCK_SELECT_ALL, - keypresses=(Gdk.KEY_a, Gdk.ModifierType.CONTROL_MASK), + icon_name='edit-select-all', + keypresses=["<Ctrl>a"], ) -ELEMENT_SELECT = Action() -ELEMENT_CREATE = Action() -ELEMENT_DELETE = Action( +ELEMENT_SELECT = actions.register("win.select") +ELEMENT_CREATE = actions.register("win.add") +ELEMENT_DELETE = actions.register("win.delete", label='_Delete', tooltip='Delete the selected blocks', - stock_id=Gtk.STOCK_DELETE, - keypresses=(Gdk.KEY_Delete, NO_MODS_MASK), + icon_name='edit-delete', ) -BLOCK_MOVE = Action() -BLOCK_ROTATE_CCW = Action( +BLOCK_MOVE = actions.register("win.block_move") +BLOCK_ROTATE_CCW = actions.register("win.block_rotate_ccw", label='Rotate Counterclockwise', tooltip='Rotate the selected blocks 90 degrees to the left', - stock_id=Gtk.STOCK_GO_BACK, - keypresses=(Gdk.KEY_Left, NO_MODS_MASK), + icon_name='object-rotate-left', ) -BLOCK_ROTATE_CW = Action( +BLOCK_ROTATE_CW = actions.register("win.block_rotate", label='Rotate Clockwise', tooltip='Rotate the selected blocks 90 degrees to the right', - stock_id=Gtk.STOCK_GO_FORWARD, - keypresses=(Gdk.KEY_Right, NO_MODS_MASK), + icon_name='object-rotate-right', ) -BLOCK_VALIGN_TOP = Action( +BLOCK_VALIGN_TOP = actions.register("win.block_align_top", label='Vertical Align Top', tooltip='Align tops of selected blocks', - keypresses=(Gdk.KEY_t, Gdk.ModifierType.SHIFT_MASK), ) -BLOCK_VALIGN_MIDDLE = Action( +BLOCK_VALIGN_MIDDLE = actions.register("win.block_align_middle", label='Vertical Align Middle', tooltip='Align centers of selected blocks vertically', - keypresses=(Gdk.KEY_m, Gdk.ModifierType.SHIFT_MASK), ) -BLOCK_VALIGN_BOTTOM = Action( +BLOCK_VALIGN_BOTTOM = actions.register("win.block_align_bottom", label='Vertical Align Bottom', tooltip='Align bottoms of selected blocks', - keypresses=(Gdk.KEY_b, Gdk.ModifierType.SHIFT_MASK), ) -BLOCK_HALIGN_LEFT = Action( +BLOCK_HALIGN_LEFT = actions.register("win.block_align_left", label='Horizontal Align Left', tooltip='Align left edges of blocks selected blocks', - keypresses=(Gdk.KEY_l, Gdk.ModifierType.SHIFT_MASK), ) -BLOCK_HALIGN_CENTER = Action( +BLOCK_HALIGN_CENTER = actions.register("win.block_align_center", label='Horizontal Align Center', tooltip='Align centers of selected blocks horizontally', - keypresses=(Gdk.KEY_c, Gdk.ModifierType.SHIFT_MASK), ) -BLOCK_HALIGN_RIGHT = Action( +BLOCK_HALIGN_RIGHT = actions.register("win.block_align_right", label='Horizontal Align Right', tooltip='Align right edges of selected blocks', - keypresses=(Gdk.KEY_r, Gdk.ModifierType.SHIFT_MASK), ) BLOCK_ALIGNMENTS = [ BLOCK_VALIGN_TOP, @@ -310,234 +347,222 @@ BLOCK_ALIGNMENTS = [ BLOCK_HALIGN_CENTER, BLOCK_HALIGN_RIGHT, ] -BLOCK_PARAM_MODIFY = Action( +BLOCK_PARAM_MODIFY = actions.register("win.block_modify", label='_Properties', tooltip='Modify params for the selected block', - stock_id=Gtk.STOCK_PROPERTIES, - keypresses=(Gdk.KEY_Return, NO_MODS_MASK), + icon_name='document-properties', ) -BLOCK_ENABLE = Action( +BLOCK_ENABLE = actions.register("win.block_enable", label='E_nable', tooltip='Enable the selected blocks', - stock_id=Gtk.STOCK_CONNECT, - keypresses=(Gdk.KEY_e, NO_MODS_MASK), + icon_name='network-wired', ) -BLOCK_DISABLE = Action( +BLOCK_DISABLE = actions.register("win.block_disable", label='D_isable', tooltip='Disable the selected blocks', - stock_id=Gtk.STOCK_DISCONNECT, - keypresses=(Gdk.KEY_d, NO_MODS_MASK), + icon_name='network-wired-disconnected', ) -BLOCK_BYPASS = Action( +BLOCK_BYPASS = actions.register("win.block_bypass", label='_Bypass', tooltip='Bypass the selected block', - stock_id=Gtk.STOCK_MEDIA_FORWARD, - keypresses=(Gdk.KEY_b, NO_MODS_MASK), + icon_name='media-seek-forward', ) -TOGGLE_SNAP_TO_GRID = ToggleAction( +TOGGLE_SNAP_TO_GRID = actions.register("win.snap_to_grid", label='_Snap to grid', tooltip='Snap blocks to a grid for an easier connection alignment', - preference_name='snap_to_grid' + preference_name='snap_to_grid', ) -TOGGLE_HIDE_DISABLED_BLOCKS = ToggleAction( +TOGGLE_HIDE_DISABLED_BLOCKS = actions.register("win.hide_disabled", label='Hide _Disabled Blocks', tooltip='Toggle visibility of disabled blocks and connections', - stock_id=Gtk.STOCK_MISSING_IMAGE, - keypresses=(Gdk.KEY_d, Gdk.ModifierType.CONTROL_MASK), + icon_name='image-missing', + keypresses=["<Ctrl>d"], + preference_name='hide_disabled', ) -TOGGLE_HIDE_VARIABLES = ToggleAction( +TOGGLE_HIDE_VARIABLES = actions.register("win.hide_variables", label='Hide Variables', tooltip='Hide all variable blocks', preference_name='hide_variables', default=False, ) -TOGGLE_FLOW_GRAPH_VAR_EDITOR = ToggleAction( +TOGGLE_FLOW_GRAPH_VAR_EDITOR = actions.register("win.toggle_variable_editor", label='Show _Variable Editor', tooltip='Show the variable editor. Modify variables and imports in this flow graph', - stock_id=Gtk.STOCK_EDIT, + icon_name='accessories-text-editor', default=True, - keypresses=(Gdk.KEY_e, Gdk.ModifierType.CONTROL_MASK), + keypresses=["<Ctrl>e"], preference_name='variable_editor_visable', ) -TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR = ToggleAction( +TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR = actions.register("win.toggle_variable_editor_sidebar", label='Move the 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( +TOGGLE_AUTO_HIDE_PORT_LABELS = actions.register("win.auto_hide_port_labels", label='Auto-Hide _Port Labels', tooltip='Automatically hide port labels', preference_name='auto_hide_port_labels' ) -TOGGLE_SHOW_BLOCK_COMMENTS = ToggleAction( +TOGGLE_SHOW_BLOCK_COMMENTS = actions.register("win.show_block_comments", label='Show Block Comments', tooltip="Show comment beneath each block", preference_name='show_block_comments' ) -TOGGLE_SHOW_CODE_PREVIEW_TAB = ToggleAction( +TOGGLE_SHOW_CODE_PREVIEW_TAB = actions.register("win.toggle_code_preview", label='Generated Code Preview', tooltip="Show a preview of the code generated for each Block in its " "Properties Dialog", preference_name='show_generated_code_tab', default=False, ) -TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY = ToggleAction( +TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY = actions.register("win.show_flowgraph_complexity", label='Show Flowgraph Complexity', tooltip="How many Balints is the flowgraph...", preference_name='show_flowgraph_complexity', default=False, ) -BLOCK_CREATE_HIER = Action( +BLOCK_CREATE_HIER = actions.register("win.block_create_hier", label='C_reate Hier', tooltip='Create hier block from selected blocks', - stock_id=Gtk.STOCK_CONNECT, -# keypresses=(Gdk.KEY_c, NO_MODS_MASK), + icon_name='document-new', ) -BLOCK_CUT = Action( +BLOCK_CUT = actions.register("win.block_cut", label='Cu_t', tooltip='Cut', - stock_id=Gtk.STOCK_CUT, - keypresses=(Gdk.KEY_x, Gdk.ModifierType.CONTROL_MASK), + icon_name='edit-cut', + keypresses=["<Ctrl>x"], ) -BLOCK_COPY = Action( +BLOCK_COPY = actions.register("win.block_copy", label='_Copy', tooltip='Copy', - stock_id=Gtk.STOCK_COPY, - keypresses=(Gdk.KEY_c, Gdk.ModifierType.CONTROL_MASK), + icon_name='edit-copy', + keypresses=["<Ctrl>c"], ) -BLOCK_PASTE = Action( +BLOCK_PASTE = actions.register("win.block_paste", label='_Paste', tooltip='Paste', - stock_id=Gtk.STOCK_PASTE, - keypresses=(Gdk.KEY_v, Gdk.ModifierType.CONTROL_MASK), + icon_name='edit-paste', + keypresses=["<Ctrl>v"], ) -ERRORS_WINDOW_DISPLAY = Action( +ERRORS_WINDOW_DISPLAY = actions.register("app.errors", label='Flowgraph _Errors', tooltip='View flow graph errors', - stock_id=Gtk.STOCK_DIALOG_ERROR, + icon_name='dialog-error', ) -TOGGLE_CONSOLE_WINDOW = ToggleAction( +TOGGLE_CONSOLE_WINDOW = actions.register("win.toggle_console_window", label='Show _Console Panel', tooltip='Toggle visibility of the console', - keypresses=(Gdk.KEY_r, Gdk.ModifierType.CONTROL_MASK), + keypresses=["<Ctrl>r"], preference_name='console_window_visible' ) -TOGGLE_BLOCKS_WINDOW = ToggleAction( +# TODO: Might be able to convert this to a Gio.PropertyAction eventually. +# actions would need to be defined in the correct class and not globally +TOGGLE_BLOCKS_WINDOW = actions.register("win.toggle_blocks_window", label='Show _Block Tree Panel', tooltip='Toggle visibility of the block tree widget', - keypresses=(Gdk.KEY_b, Gdk.ModifierType.CONTROL_MASK), + keypresses=["<Ctrl>b"], preference_name='blocks_window_visible' ) -TOGGLE_SCROLL_LOCK = ToggleAction( +TOGGLE_SCROLL_LOCK = actions.register("win.console.scroll_lock", label='Console Scroll _Lock', tooltip='Toggle scroll lock for the console window', preference_name='scroll_lock' ) -ABOUT_WINDOW_DISPLAY = Action( +ABOUT_WINDOW_DISPLAY = actions.register("app.about", label='_About', tooltip='About this program', - stock_id=Gtk.STOCK_ABOUT, + icon_name='help-about', ) -HELP_WINDOW_DISPLAY = Action( +HELP_WINDOW_DISPLAY = actions.register("app.help", label='_Help', tooltip='Usage tips', - stock_id=Gtk.STOCK_HELP, - keypresses=(Gdk.KEY_F1, NO_MODS_MASK), + icon_name='help-contents', + keypresses=["F1"], ) -TYPES_WINDOW_DISPLAY = Action( +TYPES_WINDOW_DISPLAY = actions.register("app.types", label='_Types', tooltip='Types color mapping', - stock_id=Gtk.STOCK_DIALOG_INFO, + icon_name='dialog-information', ) -FLOW_GRAPH_GEN = Action( +FLOW_GRAPH_GEN = actions.register("app.flowgraph.generate", label='_Generate', tooltip='Generate the flow graph', - stock_id=Gtk.STOCK_CONVERT, - keypresses=(Gdk.KEY_F5, NO_MODS_MASK), + icon_name='insert-object', + keypresses=["F5"], ) -FLOW_GRAPH_EXEC = Action( +FLOW_GRAPH_EXEC = actions.register("app.flowgraph.execute", label='_Execute', tooltip='Execute the flow graph', - stock_id=Gtk.STOCK_MEDIA_PLAY, - keypresses=(Gdk.KEY_F6, NO_MODS_MASK), + icon_name='media-playback-start', + keypresses=["F6"], ) -FLOW_GRAPH_KILL = Action( +FLOW_GRAPH_KILL = actions.register("app.flowgraph.kill", label='_Kill', tooltip='Kill the flow graph', - stock_id=Gtk.STOCK_STOP, - keypresses=(Gdk.KEY_F7, NO_MODS_MASK), + icon_name='media-playback-stop', + keypresses=["F7"], ) -FLOW_GRAPH_SCREEN_CAPTURE = Action( +FLOW_GRAPH_SCREEN_CAPTURE = actions.register("app.flowgraph.screen_capture", label='Screen Ca_pture', tooltip='Create a screen capture of the flow graph', - stock_id=Gtk.STOCK_PRINT, - keypresses=(Gdk.KEY_p, Gdk.ModifierType.CONTROL_MASK), -) -PORT_CONTROLLER_DEC = Action( - keypresses=(Gdk.KEY_minus, NO_MODS_MASK, Gdk.KEY_KP_Subtract, NO_MODS_MASK), -) -PORT_CONTROLLER_INC = Action( - keypresses=(Gdk.KEY_plus, NO_MODS_MASK, Gdk.KEY_KP_Add, NO_MODS_MASK), -) -BLOCK_INC_TYPE = Action( - keypresses=(Gdk.KEY_Down, NO_MODS_MASK), -) -BLOCK_DEC_TYPE = Action( - keypresses=(Gdk.KEY_Up, NO_MODS_MASK), -) -RELOAD_BLOCKS = Action( + icon_name='printer', + keypresses=["<Ctrl>p"], +) +PORT_CONTROLLER_DEC = actions.register("win.port_controller_dec") +PORT_CONTROLLER_INC = actions.register("win.port_controller_inc") +BLOCK_INC_TYPE = actions.register("win.block_inc_type") +BLOCK_DEC_TYPE = actions.register("win.block_dec_type") +RELOAD_BLOCKS = actions.register("app.reload_blocks", label='Reload _Blocks', tooltip='Reload Blocks', - stock_id=Gtk.STOCK_REFRESH + icon_name='view-refresh' ) -FIND_BLOCKS = Action( +FIND_BLOCKS = actions.register("win.find_blocks", label='_Find Blocks', tooltip='Search for a block by name (and key)', - stock_id=Gtk.STOCK_FIND, - keypresses=(Gdk.KEY_f, Gdk.ModifierType.CONTROL_MASK, - Gdk.KEY_slash, NO_MODS_MASK), + icon_name='edit-find', + keypresses=["<Ctrl>f", "slash"], ) -CLEAR_CONSOLE = Action( +CLEAR_CONSOLE = actions.register("win.console.clear", label='_Clear Console', tooltip='Clear Console', - stock_id=Gtk.STOCK_CLEAR, + icon_name='edit-clear', ) -SAVE_CONSOLE = Action( +SAVE_CONSOLE = actions.register("win.console.save", label='_Save Console', tooltip='Save Console', - stock_id=Gtk.STOCK_SAVE, + icon_name='edit-save', ) -OPEN_HIER = Action( +OPEN_HIER = actions.register("win.open_hier", label='Open H_ier', tooltip='Open the source of the selected hierarchical block', - stock_id=Gtk.STOCK_JUMP_TO, + icon_name='go-jump', ) -BUSSIFY_SOURCES = Action( +BUSSIFY_SOURCES = actions.register("win.bussify_sources", label='Toggle So_urce Bus', tooltip='Gang source ports into a single bus port', - stock_id=Gtk.STOCK_JUMP_TO, + icon_name='go-jump', ) -BUSSIFY_SINKS = Action( +BUSSIFY_SINKS = actions.register("win.bussify_sinks", label='Toggle S_ink Bus', tooltip='Gang sink ports into a single bus port', - stock_id=Gtk.STOCK_JUMP_TO, + icon_name='go-jump', ) -XML_PARSER_ERRORS_DISPLAY = Action( +XML_PARSER_ERRORS_DISPLAY = actions.register("app.xml_errors", label='_Parser Errors', tooltip='View errors that occurred while parsing XML files', - stock_id=Gtk.STOCK_DIALOG_ERROR, + icon_name='dialog-error', ) -FLOW_GRAPH_OPEN_QSS_THEME = Action( +FLOW_GRAPH_OPEN_QSS_THEME = actions.register("app.open_qss_theme", label='Set Default QT GUI _Theme', tooltip='Set a default QT Style Sheet file to use for QT GUI', - stock_id=Gtk.STOCK_OPEN, + icon_name='document-open', ) -TOOLS_RUN_FDESIGN = Action( +TOOLS_RUN_FDESIGN = actions.register("app.filter_design", label='Filter Design Tool', tooltip='Execute gr_filter_design', - stock_id=Gtk.STOCK_EXECUTE, -) -TOOLS_MORE_TO_COME = Action( - label='More to come', + icon_name='media-playback-start', ) +POST_HANDLER = actions.register("app.post_handler") +READY = actions.register("app.ready") diff --git a/grc/gui/Application.py b/grc/gui/Application.py index 9e89009dc9..c1456c3a8d 100644 --- a/grc/gui/Application.py +++ b/grc/gui/Application.py @@ -24,9 +24,9 @@ import os import subprocess import logging -from gi.repository import Gtk, GObject +from gi.repository import Gtk, Gio, GLib, GObject -from . import Dialogs, Actions, Executor, FileDialogs, Utils +from . import Dialogs, Actions, Executor, FileDialogs, Utils, Bars from .MainWindow import MainWindow from .ParserErrorsDialog import ParserErrorsDialog from .PropsDialog import PropsDialog @@ -56,54 +56,58 @@ class Application(Gtk.Application): """ self.clipboard = None self.dialog = None - for action in Actions.get_all_actions(): action.connect('activate', self._handle_action) - #setup the main window + + # Setup the main window self.platform = platform self.config = platform.config - log.debug("__init__()") - - #initialize + 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("do_startup()") + 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("do_activate()") + log.debug("Application.do_activate()") - self.main_window = MainWindow(self, self.platform, self._handle_action) + self.main_window = MainWindow(self, self.platform) self.main_window.connect('delete-event', self._quit) - self.main_window.connect('key-press-event', self._handle_key_press) 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 _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. - - Returns: - false to let gtk handle the key action - """ - # prevent key event stealing while the search box is active - # .has_focus() only in newer versions 2.17+? - # .is_focus() seems to work, but exactly the same - if self.main_window.btwin.search_entry.has_focus(): - return False - return Actions.handle_key_press(event) - def _quit(self, window, event): """ Handle the delete event from the main window. @@ -117,7 +121,7 @@ class Application(Gtk.Application): return True def _handle_action(self, action, *args): - #print action + 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 @@ -130,6 +134,7 @@ class Application(Gtk.Application): # 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): @@ -139,35 +144,84 @@ class Application(Gtk.Application): main.btwin.search_entry.hide() - # Disable all actions, then re-enable a few - for action in Actions.get_all_actions(): - action.set_sensitive(False) # set all actions disabled + """ + 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.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW, - Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS, - Actions.FLOW_GRAPH_DUPLICATE, Actions.FLOW_GRAPH_SAVE_A_COPY, - Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY, - Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY, - Actions.TYPES_WINDOW_DISPLAY, Actions.TOGGLE_BLOCKS_WINDOW, - Actions.TOGGLE_CONSOLE_WINDOW, Actions.TOGGLE_HIDE_DISABLED_BLOCKS, - Actions.TOOLS_RUN_FDESIGN, Actions.TOGGLE_SCROLL_LOCK, - Actions.CLEAR_CONSOLE, Actions.SAVE_CONSOLE, - Actions.TOGGLE_AUTO_HIDE_PORT_LABELS, Actions.TOGGLE_SNAP_TO_GRID, + 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.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, ): - action.set_sensitive(True) + 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_sensitive(True) + 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(): @@ -400,12 +454,17 @@ class Application(Gtk.Application): 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: @@ -418,41 +477,53 @@ class Application(Gtk.Application): 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: - # Call the variable editor TOGGLE in case it needs to be showing - Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR() + 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: - # 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 action.get_sensitive(): - main.update_panel_visibility(main.VARIABLES, action.get_active()) - else: # This is occurring after variables are un-hidden - # Leave it enabled - action.set_sensitive(True) - action.set_active(True) - # Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR.set_sensitive(action.get_active()) + # 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, @@ -463,7 +534,7 @@ class Application(Gtk.Application): # Param Modifications ################################################## elif action == Actions.BLOCK_PARAM_MODIFY: - selected_block = action.args[0] if action.args else flow_graph.selected_block + 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 @@ -520,17 +591,17 @@ class Application(Gtk.Application): elif action == Actions.FLOW_GRAPH_NEW: main.new_page() if args: - flow_graph = main.get_flow_graph() + 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 if args else FileDialogs.OpenFlowGraph(main, page.file_path).run() + 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() + #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() @@ -558,33 +629,37 @@ class Application(Gtk.Application): Actions.FLOW_GRAPH_SAVE() self.config.add_recent_file(file_path) main.tool_bar.refresh_submenus() - main.menu_bar.refresh_submenus() - elif action == Actions.FLOW_GRAPH_SAVE_A_COPY: + #TODO + #main.menu_bar.refresh_submenus() + elif action == Actions.FLOW_GRAPH_SAVE_COPY: try: - if not page.get_file_path(): + if not page.file_path: + # Make sure the current flowgraph has been saved Actions.FLOW_GRAPH_SAVE_AS() else: - dup_file_path = page.get_file_path() + 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 = SaveFlowGraphFileDialog(dup_file_path_temp).run() + 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: - flow_graph = main.get_flow_graph() + previous = flow_graph + # Create a new page main.new_page() - curr_page = main.get_page() - new_flow_graph = main.get_flow_graph() - new_flow_graph.import_data(flow_graph.export_data()) + 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) - curr_page.set_saved(False) + 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: @@ -634,7 +709,7 @@ class Application(Gtk.Application): elif action == Actions.RELOAD_BLOCKS: self.platform.build_block_library() main.btwin.repopulate() - Actions.XML_PARSER_ERRORS_DISPLAY.set_sensitive(bool( + 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 @@ -667,7 +742,7 @@ class Application(Gtk.Application): shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) else: - print('!!! Action "%s" not handled !!!' % action) + log.warning('!!! Action "%s" not handled !!!' % action) ################################################## # Global Actions for all States ################################################## @@ -678,19 +753,19 @@ class Application(Gtk.Application): selected_block = selected_blocks[0] if selected_blocks else None #update general buttons - Actions.ERRORS_WINDOW_DISPLAY.set_sensitive(not flow_graph.is_valid()) - Actions.ELEMENT_DELETE.set_sensitive(bool(flow_graph.selected_elements)) - Actions.BLOCK_PARAM_MODIFY.set_sensitive(bool(selected_block)) - Actions.BLOCK_ROTATE_CCW.set_sensitive(bool(selected_blocks)) - Actions.BLOCK_ROTATE_CW.set_sensitive(bool(selected_blocks)) + 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_sensitive(len(selected_blocks) > 1) + act.set_enabled(len(selected_blocks) > 1) #update cut/copy/paste - Actions.BLOCK_CUT.set_sensitive(bool(selected_blocks)) - Actions.BLOCK_COPY.set_sensitive(bool(selected_blocks)) - Actions.BLOCK_PASTE.set_sensitive(bool(self.clipboard)) + 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) @@ -700,26 +775,26 @@ class Application(Gtk.Application): all(block.can_bypass() for block in selected_blocks) and any(not block.get_bypassed() for block in selected_blocks) ) - Actions.BLOCK_ENABLE.set_sensitive(can_enable) - Actions.BLOCK_DISABLE.set_sensitive(can_disable) - Actions.BLOCK_BYPASS.set_sensitive(can_bypass_all) + 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_sensitive(bool(selected_blocks)) - Actions.OPEN_HIER.set_sensitive(bool(selected_blocks)) - Actions.BUSSIFY_SOURCES.set_sensitive(bool(selected_blocks)) - Actions.BUSSIFY_SINKS.set_sensitive(bool(selected_blocks)) - Actions.RELOAD_BLOCKS.set_sensitive(True) - Actions.FIND_BLOCKS.set_sensitive(True) + 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_sensitive(not page.saved) + 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 + return True # Action was handled def update_exec_stop(self): """ @@ -728,6 +803,6 @@ class Application(Gtk.Application): """ page = self.main_window.current_page sensitive = page.flow_graph.is_valid() and not page.process - Actions.FLOW_GRAPH_GEN.set_sensitive(sensitive) - Actions.FLOW_GRAPH_EXEC.set_sensitive(sensitive) - Actions.FLOW_GRAPH_KILL.set_sensitive(page.process is not None) + 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) diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index 1510e109d2..2a8040f5d5 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -1,5 +1,5 @@ """ -Copyright 2007, 2008, 2009, 2015 Free Software Foundation, Inc. +Copyright 2007, 2008, 2009, 2015, 2016 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -19,306 +19,299 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import -from gi.repository import Gtk, GObject +import logging + +from gi.repository import Gtk, GObject, Gio, GLib from . import Actions +log = logging.getLogger(__name__) + + +''' +# Menu/Toolbar Lists: +# +# Sub items can be 1 of 3 types +# - List Creates a section within the current menu +# - Tuple Creates a submenu using a string or action as the parent. The child +# can be another menu list or an identifier used to call a helper function. +# - Action Appends a new menu item to the current menu +# + +LIST_NAME = [ + [Action1, Action2], # New section + (Action3, [Action4, Action5]), # Submenu with action as parent + ("Label", [Action6, Action7]), # Submenu with string as parent + ("Label2", "helper") # Submenu with helper function. Calls 'create_helper()' +] +''' + + # The list of actions for the toolbar. -TOOLBAR_LIST = ( - (Actions.FLOW_GRAPH_NEW, 'flow_graph_new'), - (Actions.FLOW_GRAPH_OPEN, 'flow_graph_recent'), - Actions.FLOW_GRAPH_SAVE, - Actions.FLOW_GRAPH_CLOSE, - None, - Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, - Actions.FLOW_GRAPH_SCREEN_CAPTURE, - None, - Actions.BLOCK_CUT, - Actions.BLOCK_COPY, - Actions.BLOCK_PASTE, - Actions.ELEMENT_DELETE, - None, - Actions.FLOW_GRAPH_UNDO, - Actions.FLOW_GRAPH_REDO, - None, - Actions.ERRORS_WINDOW_DISPLAY, - Actions.FLOW_GRAPH_GEN, - Actions.FLOW_GRAPH_EXEC, - Actions.FLOW_GRAPH_KILL, - None, - Actions.BLOCK_ROTATE_CCW, - Actions.BLOCK_ROTATE_CW, - None, - Actions.BLOCK_ENABLE, - Actions.BLOCK_DISABLE, - Actions.BLOCK_BYPASS, - Actions.TOGGLE_HIDE_DISABLED_BLOCKS, - None, - Actions.FIND_BLOCKS, - Actions.RELOAD_BLOCKS, - Actions.OPEN_HIER, -) +TOOLBAR_LIST = [ + [(Actions.FLOW_GRAPH_NEW, 'flow_graph_new'), Actions.FLOW_GRAPH_OPEN, + (Actions.FLOW_GRAPH_OPEN_RECENT, 'flow_graph_recent'), Actions.FLOW_GRAPH_SAVE, Actions.FLOW_GRAPH_CLOSE], + [Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, Actions.FLOW_GRAPH_SCREEN_CAPTURE], + [Actions.BLOCK_CUT, Actions.BLOCK_COPY, Actions.BLOCK_PASTE, Actions.ELEMENT_DELETE], + [Actions.FLOW_GRAPH_UNDO, Actions.FLOW_GRAPH_REDO], + [Actions.ERRORS_WINDOW_DISPLAY, Actions.FLOW_GRAPH_GEN, Actions.FLOW_GRAPH_EXEC, Actions.FLOW_GRAPH_KILL], + [Actions.BLOCK_ROTATE_CCW, Actions.BLOCK_ROTATE_CW], + [Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_BYPASS, Actions.TOGGLE_HIDE_DISABLED_BLOCKS], + [Actions.FIND_BLOCKS, Actions.RELOAD_BLOCKS, Actions.OPEN_HIER] +] # The list of actions and categories for the menu bar. -MENU_BAR_LIST = ( - (Gtk.Action(name='File', label='_File'), [ - 'flow_graph_new', - Actions.FLOW_GRAPH_DUPLICATE, - Actions.FLOW_GRAPH_OPEN, - 'flow_graph_recent', - None, - Actions.FLOW_GRAPH_SAVE, - Actions.FLOW_GRAPH_SAVE_AS, - Actions.FLOW_GRAPH_SAVE_A_COPY, - None, - Actions.FLOW_GRAPH_SCREEN_CAPTURE, - None, - Actions.FLOW_GRAPH_CLOSE, - Actions.APPLICATION_QUIT, - ]), - (Gtk.Action(name='Edit', label='_Edit'), [ - Actions.FLOW_GRAPH_UNDO, - Actions.FLOW_GRAPH_REDO, - None, - Actions.BLOCK_CUT, - Actions.BLOCK_COPY, - Actions.BLOCK_PASTE, - Actions.ELEMENT_DELETE, - Actions.SELECT_ALL, - None, - Actions.BLOCK_ROTATE_CCW, - Actions.BLOCK_ROTATE_CW, - (Gtk.Action(name='Align', label='_Align', tooltip=None, stock_id=None), Actions.BLOCK_ALIGNMENTS), - None, - Actions.BLOCK_ENABLE, - Actions.BLOCK_DISABLE, - Actions.BLOCK_BYPASS, - None, - Actions.BLOCK_PARAM_MODIFY, - ]), - (Gtk.Action(name='View', label='_View'), [ - Actions.TOGGLE_BLOCKS_WINDOW, - None, - Actions.TOGGLE_CONSOLE_WINDOW, - Actions.TOGGLE_SCROLL_LOCK, - Actions.SAVE_CONSOLE, - Actions.CLEAR_CONSOLE, - 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, - Actions.TOGGLE_SHOW_BLOCK_COMMENTS, - None, - Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB, - None, - Actions.ERRORS_WINDOW_DISPLAY, - Actions.FIND_BLOCKS, - ]), - (Gtk.Action(name='Run', label='_Run'), [ - Actions.FLOW_GRAPH_GEN, - Actions.FLOW_GRAPH_EXEC, - Actions.FLOW_GRAPH_KILL, - ]), - (Gtk.Action(name='Tools', label='_Tools'), [ - Actions.TOOLS_RUN_FDESIGN, - Actions.FLOW_GRAPH_OPEN_QSS_THEME, - None, - Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY, - None, - Actions.TOOLS_MORE_TO_COME, - ]), - (Gtk.Action(name='Help', label='_Help'), [ - Actions.HELP_WINDOW_DISPLAY, - Actions.TYPES_WINDOW_DISPLAY, - Actions.XML_PARSER_ERRORS_DISPLAY, - None, - Actions.ABOUT_WINDOW_DISPLAY, - ]), -) +MENU_BAR_LIST = [ + ('_File', [ + [(Actions.FLOW_GRAPH_NEW, 'flow_graph_new'), Actions.FLOW_GRAPH_DUPLICATE, + Actions.FLOW_GRAPH_OPEN, (Actions.FLOW_GRAPH_OPEN_RECENT, 'flow_graph_recent')], + [Actions.FLOW_GRAPH_SAVE, Actions.FLOW_GRAPH_SAVE_AS, Actions.FLOW_GRAPH_SAVE_COPY], + [Actions.FLOW_GRAPH_SCREEN_CAPTURE], + [Actions.FLOW_GRAPH_CLOSE, Actions.APPLICATION_QUIT] + ]), + ('_Edit', [ + [Actions.FLOW_GRAPH_UNDO, Actions.FLOW_GRAPH_REDO], + [Actions.BLOCK_CUT, Actions.BLOCK_COPY, Actions.BLOCK_PASTE, Actions.ELEMENT_DELETE, Actions.SELECT_ALL], + [Actions.BLOCK_ROTATE_CCW, Actions.BLOCK_ROTATE_CW, ('_Align', Actions.BLOCK_ALIGNMENTS)], + [Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_BYPASS], + [Actions.BLOCK_PARAM_MODIFY] + ]), + ('_View', [ + [Actions.TOGGLE_BLOCKS_WINDOW], + [Actions.TOGGLE_CONSOLE_WINDOW, Actions.TOGGLE_SCROLL_LOCK, Actions.SAVE_CONSOLE, Actions.CLEAR_CONSOLE], + [Actions.TOGGLE_HIDE_VARIABLES, Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR], + [Actions.TOGGLE_HIDE_DISABLED_BLOCKS, Actions.TOGGLE_AUTO_HIDE_PORT_LABELS, Actions.TOGGLE_SNAP_TO_GRID, Actions.TOGGLE_SHOW_BLOCK_COMMENTS], + [Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB], + [Actions.ERRORS_WINDOW_DISPLAY, Actions.FIND_BLOCKS], + ]), + ('_Run', [ + Actions.FLOW_GRAPH_GEN, Actions.FLOW_GRAPH_EXEC, Actions.FLOW_GRAPH_KILL + ]), + ('_Tools', [ + [Actions.TOOLS_RUN_FDESIGN, Actions.FLOW_GRAPH_OPEN_QSS_THEME], + [Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY] + ]), + ('_Help', [ + [Actions.HELP_WINDOW_DISPLAY, Actions.TYPES_WINDOW_DISPLAY, Actions.XML_PARSER_ERRORS_DISPLAY], + [Actions.ABOUT_WINDOW_DISPLAY] + ])] # The list of actions for the context menu. CONTEXT_MENU_LIST = [ - Actions.BLOCK_CUT, - Actions.BLOCK_COPY, - Actions.BLOCK_PASTE, - Actions.ELEMENT_DELETE, - None, - Actions.BLOCK_ROTATE_CCW, - Actions.BLOCK_ROTATE_CW, - Actions.BLOCK_ENABLE, - Actions.BLOCK_DISABLE, - Actions.BLOCK_BYPASS, - None, - (Gtk.Action(name='More', label='_More'), [ - Actions.BLOCK_CREATE_HIER, - Actions.OPEN_HIER, - None, - Actions.BUSSIFY_SOURCES, - Actions.BUSSIFY_SINKS, - ]), - Actions.BLOCK_PARAM_MODIFY + [Actions.BLOCK_CUT, Actions.BLOCK_COPY, Actions.BLOCK_PASTE, Actions.ELEMENT_DELETE], + [Actions.BLOCK_ROTATE_CCW, Actions.BLOCK_ROTATE_CW, Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_BYPASS], + [("_More", [ + [Actions.BLOCK_CREATE_HIER, Actions.OPEN_HIER], + [Actions.BUSSIFY_SOURCES, Actions.BUSSIFY_SINKS]] + )], + [Actions.BLOCK_PARAM_MODIFY], ] -class SubMenuCreator(object): +class SubMenuHelper(object): + ''' Generates custom submenus for the main menu or toolbar. ''' - def __init__(self, generate_modes, action_handler_callback): - self.generate_modes = generate_modes - self.action_handler_callback = action_handler_callback - self.submenus = [] + def __init__(self): + self.submenus = {} - def create_submenu(self, action_tuple, item): - func = getattr(self, '_fill_' + action_tuple[1] + "_submenu") - self.submenus.append((action_tuple[0], func, item)) - self.refresh_submenus() + def build_submenu(self, name, obj, set_func): + # Get the correct helper function + create_func = getattr(self, "create_{}".format(name)) + # Save the helper functions for rebuilding the menu later + self.submenus[name] = (create_func, obj, set_func) + # Actually build the menu + set_func(obj, create_func()) def refresh_submenus(self): - for action, func, item in self.submenus: - try: - item.set_property("menu", func(action)) - except TypeError: - item.set_property("submenu", func(action)) - item.set_property('sensitive', True) - - def callback_adaptor(self, item, action_key): - action, key = action_key - self.action_handler_callback(action, key) - - def _fill_flow_graph_new_submenu(self, action): - """Sub menu to create flow-graph with pre-set generate mode""" - menu = Gtk.Menu() - for key, name, default in self.generate_modes: - if default: - item = Actions.FLOW_GRAPH_NEW.create_menu_item() - item.set_label(name) - else: - item = Gtk.MenuItem(name=name, use_underline=False) - item.connect('activate', self.callback_adaptor, (action, key)) - menu.append(item) - menu.show_all() + for name in self.submenus: + create_func, obj, set_func = self.submenus[name] + print ("refresh", create_func, obj, set_func) + set_func(obj, create_func()) + + def create_flow_graph_new(self): + """ Different flowgraph types """ + menu = Gio.Menu() + platform = Gtk.Application.get_default().platform + generate_modes = platform.get_generate_options() + for key, name, default in generate_modes: + target = "app.flowgraph.new::{}".format(key) + menu.append(name, target) return menu - def _fill_flow_graph_recent_submenu(self, action): - """menu showing recent flow-graphs""" - menu = Gtk.Menu() + def create_flow_graph_recent(self): + """ Recent flow graphs """ + config = Gtk.Application.get_default().config recent_files = config.get_recent_files() + menu = Gio.Menu() if len(recent_files) > 0: + files = Gio.Menu() for i, file_name in enumerate(recent_files): - item = Gtk.MenuItem(name="%d. %s" % (i+1, file_name), use_underline=False) - item.connect('activate', self.callback_adaptor, - (action, file_name)) - menu.append(item) - menu.show_all() - return menu - return None + target = "app.flowgraph.open_recent::{}".format(file_name) + files.append(file_name, target) + menu.append_section(None, files) + #clear = Gio.Menu() + #clear.append("Clear recent files", "app.flowgraph.clear_recent") + #menu.append_section(None, clear) + else: + # Show an empty menu + menuitem = Gio.MenuItem.new("No items found", "app.none") + menu.append_item(menuitem) + return menu -class Toolbar(Gtk.Toolbar, SubMenuCreator): - """The gtk toolbar with actions added from the toolbar list.""" +class MenuHelper(SubMenuHelper): + """ + Recursively builds a menu from a given list of actions. - def __init__(self, generate_modes, action_handler_callback): - """ - Parse the list of action names in the toolbar list. - Look up the action for each name in the action list and add it to the - toolbar. - """ - GObject.GObject.__init__(self) - self.set_style(Gtk.ToolbarStyle.ICONS) - SubMenuCreator.__init__(self, generate_modes, action_handler_callback) - - for action in TOOLBAR_LIST: - if isinstance(action, tuple) and isinstance(action[1], str): - # create a button with a sub-menu - # TODO: Fix later - #action[0].set_tool_item_type(Gtk.MenuToolButton) - item = action[0].create_tool_item() - #self.create_submenu(action, item) - #self.refresh_submenus() - - elif action is None: - item = Gtk.SeparatorToolItem() - - else: - #TODO: Fix later - #action.set_tool_item_type(Gtk.ToolButton) - item = action.create_tool_item() - # this reset of the tooltip property is required - # (after creating the tool item) for the tooltip to show - action.set_property('tooltip', action.get_property('tooltip')) - self.add(item) - - -class MenuHelperMixin(object): - """Mixin class to help build menus from the above action lists""" - - def _fill_menu(self, actions, menu=None): - """Create a menu from list of actions""" - menu = menu or Gtk.Menu() + Args: + - actions: List of actions to build the menu + - menu: Current menu being built + + Notes: + - Tuple: Create a new submenu from the parent (1st) and child (2nd) elements + - Action: Append to current menu + - List: Start a new section + """ + + def __init__(self): + SubMenuHelper.__init__(self) + + def build_menu(self, actions, menu): for item in actions: if isinstance(item, tuple): - menu_item = self._make_sub_menu(*item) - elif isinstance(item, str): - menu_item = getattr(self, 'create_' + item)() - elif item is None: - menu_item = Gtk.SeparatorMenuItem() - else: - menu_item = item.create_menu_item() - menu.append(menu_item) - menu.show_all() - return menu - - def _make_sub_menu(self, main, actions): - """Create a submenu from a main action and a list of actions""" - main = main.create_menu_item() - main.set_submenu(self._fill_menu(actions)) - return main + # Create a new submenu + parent, child = (item[0], item[1]) + + # Create the parent + label, target = (parent, None) + if isinstance(parent, Actions.Action): + label = parent.label + target = "{}.{}".format(parent.prefix, parent.name) + menuitem = Gio.MenuItem.new(label, None) + if hasattr(parent, "icon_name"): + menuitem.set_icon(Gio.Icon.new_for_string(parent.icon_name)) + + # Create the new submenu + if isinstance(child, list): + submenu = Gio.Menu() + self.build_menu(child, submenu) + menuitem.set_submenu(submenu) + elif isinstance(child, str): + # Child is the name of the submenu to create + def set_func(obj, menu): + obj.set_submenu(menu) + self.build_submenu(child, menuitem, set_func) + menu.append_item(menuitem) + + elif isinstance(item, list): + # Create a new section + section = Gio.Menu() + self.build_menu(item, section) + menu.append_section(None, section) + + elif isinstance(item, Actions.Action): + # Append a new menuitem + target = "{}.{}".format(item.prefix, item.name) + menuitem = Gio.MenuItem.new(item.label, target) + if item.icon_name: + menuitem.set_icon(Gio.Icon.new_for_string(item.icon_name)) + menu.append_item(menuitem) + + +class ToolbarHelper(SubMenuHelper): + """ + Builds a toolbar from a given list of actions. + + Args: + - actions: List of actions to build the menu + - item: Current menu being built + + Notes: + - Tuple: Create a new submenu from the parent (1st) and child (2nd) elements + - Action: Append to current menu + - List: Start a new section + """ + def __init__(self): + SubMenuHelper.__init__(self) -class MenuBar(Gtk.MenuBar, MenuHelperMixin, SubMenuCreator): - """The gtk menu bar with actions added from the menu bar list.""" + def build_toolbar(self, actions, current): + for item in actions: + if isinstance(item, list): + # Toolbar's don't have sections like menus, so call this function + # recursively with the "section" and just append a separator. + self.build_toolbar(item, self) + current.insert(Gtk.SeparatorToolItem.new(), -1) + + elif isinstance(item, tuple): + parent, child = (item[0], item[1]) + # Create an item with a submenu + # Generate the submenu and add to the item. + # Add the item to the toolbar + button = Gtk.MenuToolButton.new() + # The tuple should be made up of an Action and something. + button.set_label(parent.label) + button.set_tooltip_text(parent.tooltip) + button.set_icon_name(parent.icon_name) + + target = "{}.{}".format(parent.prefix, parent.name) + button.set_action_name(target) + + def set_func(obj, menu): + obj.set_menu(Gtk.Menu.new_from_model(menu)) + + self.build_submenu(child, button, set_func) + current.insert(button, -1) + + elif isinstance(item, Actions.Action): + button = Gtk.ToolButton.new() + button.set_label(item.label) + button.set_tooltip_text(item.tooltip) + button.set_icon_name(item.icon_name) + target = "{}.{}".format(item.prefix, item.name) + button.set_action_name(target) + current.insert(button, -1) + + +class Menu(Gio.Menu, MenuHelper): + """ Main Menu """ - def __init__(self, generate_modes, action_handler_callback): - """ - Parse the list of submenus from the menubar list. - For each submenu, get a list of action names. - Look up the action for each name in the action list and add it to the - submenu. Add the submenu to the menu bar. - """ + def __init__(self): GObject.GObject.__init__(self) - SubMenuCreator.__init__(self, generate_modes, action_handler_callback) - for main_action, actions in MENU_BAR_LIST: - self.append(self._make_sub_menu(main_action, actions)) + MenuHelper.__init__(self) - def create_flow_graph_new(self): - main = Gtk.ImageMenuItem(label=Gtk.STOCK_NEW) - main.set_label(Actions.FLOW_GRAPH_NEW.get_label()) - func = self._fill_flow_graph_new_submenu - self.submenus.append((Actions.FLOW_GRAPH_NEW, func, main)) - self.refresh_submenus() - return main + log.debug("Building the main menu") + self.build_menu(MENU_BAR_LIST, self) - def create_flow_graph_recent(self): - main = Gtk.ImageMenuItem(label=Gtk.STOCK_OPEN) - main.set_label(Actions.FLOW_GRAPH_OPEN_RECENT.get_label()) - func = self._fill_flow_graph_recent_submenu - self.submenus.append((Actions.FLOW_GRAPH_OPEN, func, main)) - self.refresh_submenus() - if main.get_submenu() is None: - main.set_property('sensitive', False) - return main +class ContextMenu(Gio.Menu, MenuHelper): + """ Context menu for the drawing area """ + + def __init__(self): + GObject.GObject.__init__(self) + + log.debug("Building the context menu") + self.build_menu(CONTEXT_MENU_LIST, self) -class ContextMenu(Gtk.Menu, MenuHelperMixin): - """The gtk menu with actions added from the context menu list.""" + +class Toolbar(Gtk.Toolbar, ToolbarHelper): + """ The gtk toolbar with actions added from the toolbar list. """ def __init__(self): + """ + Parse the list of action names in the toolbar list. + Look up the action for each name in the action list and add it to the + toolbar. + """ GObject.GObject.__init__(self) - self._fill_menu(CONTEXT_MENU_LIST, self) + ToolbarHelper.__init__(self) + + self.set_style(Gtk.ToolbarStyle.ICONS) + #self.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR) + + #SubMenuCreator.__init__(self) + self.build_toolbar(TOOLBAR_LIST, self) diff --git a/grc/gui/Config.py b/grc/gui/Config.py index c4d395c0b3..6135296660 100644 --- a/grc/gui/Config.py +++ b/grc/gui/Config.py @@ -48,7 +48,7 @@ class Config(CoreConfig): self.install_prefix = install_prefix Constants.update_font_size(self.font_size) - self.parser = configparser.SafeConfigParser() + self.parser = configparser.ConfigParser() for section in ['main', 'files_open', 'files_recent']: try: self.parser.add_section(section) diff --git a/grc/gui/Console.py b/grc/gui/Console.py index d40f300a8a..0ae862493d 100644 --- a/grc/gui/Console.py +++ b/grc/gui/Console.py @@ -20,6 +20,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import import os +import logging import gi gi.require_version('Gtk', '3.0') @@ -31,9 +32,14 @@ from .Dialogs import TextDisplay, MessageDialogWrapper from ..core import Messages +log = logging.getLogger(__name__) + + class Console(Gtk.ScrolledWindow): def __init__(self): Gtk.ScrolledWindow.__init__(self) + log.debug("console()") + self.app = Gtk.Application.get_default() self.text_display = TextDisplay() diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py index 94dfcf1370..2403fa2844 100644 --- a/grc/gui/DrawingArea.py +++ b/grc/gui/DrawingArea.py @@ -23,6 +23,7 @@ from gi.repository import Gtk, Gdk from .canvas.colors import FLOWGRAPH_BACKGROUND_COLOR from . import Constants +from . import Actions class DrawingArea(Gtk.DrawingArea): @@ -42,6 +43,7 @@ class DrawingArea(Gtk.DrawingArea): Gtk.DrawingArea.__init__(self) self._flow_graph = flow_graph + self.set_property('can_focus', True) self.zoom_factor = 1.0 self._update_after_zoom = False @@ -66,6 +68,11 @@ class DrawingArea(Gtk.DrawingArea): # Gdk.EventMask.FOCUS_CHANGE_MASK ) + # This may not be the correct place to be handling the user events + # Should this be in the page instead? + # Or should more of the page functionality move here? + self.connect('key_press_event', self._handle_key_press) + # setup drag and drop self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.connect('drag-data-received', self._handle_drag_data_received) @@ -78,12 +85,14 @@ class DrawingArea(Gtk.DrawingArea): def _handle_notify_event(widget, event, focus_flag): self._focus_flag = focus_flag + self.connect('leave-notify-event', _handle_notify_event, False) self.connect('enter-notify-event', _handle_notify_event, True) # todo: fix # self.set_flags(Gtk.CAN_FOCUS) # self.set_can_focus(True) # self.connect('focus-out-event', self._handle_focus_lost_event) + ########################################################################## # Handlers ########################################################################## @@ -155,6 +164,41 @@ class DrawingArea(Gtk.DrawingArea): coordinate=self._translate_event_coords(event), ) + def _handle_key_press(self, widget, event): + """ + Handle specific keypresses when the drawing area has focus that + triggers actions by the user. + """ + key = event.keyval + mod = event.state + + # Setup a map of the accelerator keys to the action to trigger + accels = { + Gtk.accelerator_parse('d'): Actions.BLOCK_DISABLE, + Gtk.accelerator_parse('e'): Actions.BLOCK_ENABLE, + Gtk.accelerator_parse('b'): Actions.BLOCK_BYPASS, + Gtk.accelerator_parse('c'): Actions.BLOCK_CREATE_HIER, + Gtk.accelerator_parse('Up'): Actions.BLOCK_DEC_TYPE, + Gtk.accelerator_parse('Down'): Actions.BLOCK_INC_TYPE, + Gtk.accelerator_parse('Left'): Actions.BLOCK_ROTATE_CCW, + Gtk.accelerator_parse('Right'): Actions.BLOCK_ROTATE_CW, + Gtk.accelerator_parse('minus'): Actions.PORT_CONTROLLER_DEC, + Gtk.accelerator_parse('plus'): Actions.PORT_CONTROLLER_INC, + Gtk.accelerator_parse('Add'): Actions.PORT_CONTROLLER_INC, + Gtk.accelerator_parse('Subtract'): Actions.PORT_CONTROLLER_DEC, + Gtk.accelerator_parse('Return'): Actions.BLOCK_PARAM_MODIFY, + Gtk.accelerator_parse('<Shift>t'): Actions.BLOCK_VALIGN_TOP, + Gtk.accelerator_parse('<Shift>m'): Actions.BLOCK_VALIGN_MIDDLE, + Gtk.accelerator_parse('<Shift>b'): Actions.BLOCK_VALIGN_BOTTOM, + Gtk.accelerator_parse('<Shift>l'): Actions.BLOCK_HALIGN_LEFT, + Gtk.accelerator_parse('<Shift>c'): Actions.BLOCK_HALIGN_CENTER, + Gtk.accelerator_parse('<Shift>r'): Actions.BLOCK_HALIGN_RIGHT, + } + # Not sold on this. + if (key, mod) in accels: + accels[(key, mod)]() + return True + def _update_size(self): w, h = self._flow_graph.get_extents()[2:] self.set_size_request(w * self.zoom_factor + 100, h * self.zoom_factor + 100) diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index 5bb4c52a07..c13a59a16d 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -35,6 +35,7 @@ from .Notebook import Notebook, Page from ..core import Messages + log = logging.getLogger(__name__) @@ -49,7 +50,7 @@ class MainWindow(Gtk.ApplicationWindow): CONSOLE = 1 VARIABLES = 2 - def __init__(self, app, platform, action_handler_callback): + def __init__(self, app, platform): """ MainWindow constructor Setup the menu, toolbar, flow graph editor notebook, block selection window... @@ -58,18 +59,29 @@ class MainWindow(Gtk.ApplicationWindow): log.debug("__init__()") self._platform = platform + self.app = app self.config = platform.config + # Add all "win" actions to the local + win_actions = filter(lambda x: x.startswith("win."), Actions.get_actions()) + map(lambda x: self.add_action(Actions.actions[x]), win_actions) + # Setup window vbox = Gtk.VBox() self.add(vbox) # Create the menu bar and toolbar generate_modes = platform.get_generate_options() - self.add_accel_group(Actions.get_accel_group()) - self.menu_bar = Bars.MenuBar(generate_modes, action_handler_callback) + + # This needs to be replaced + # Have an option for either the application menu or this menu + self.menu_bar = Gtk.MenuBar.new_from_model(Bars.Menu()) vbox.pack_start(self.menu_bar, False, False, 0) - self.tool_bar = Bars.Toolbar(generate_modes, action_handler_callback) + + self.tool_bar = Bars.Toolbar() + self.tool_bar.set_hexpand(True) + # Show the toolbar + self.tool_bar.show() vbox.pack_start(self.tool_bar, False, False, 0) # Main parent container for the different panels @@ -126,9 +138,7 @@ class MainWindow(Gtk.ApplicationWindow): self.left_subpanel.set_position(self.config.variable_editor_position()) self.show_all() - self.console.hide() - self.vars.hide() - self.btwin.hide() + log.debug("Main window ready") ############################################################ # Event Handlers @@ -206,7 +216,6 @@ class MainWindow(Gtk.ApplicationWindow): def current_page(self, page): self.notebook.current_page = page - def add_console_line(self, line): """ Place line at the end of the text buffer, then scroll its window all the way down. diff --git a/grc/gui/Notebook.py b/grc/gui/Notebook.py index e78b748326..ef08961036 100644 --- a/grc/gui/Notebook.py +++ b/grc/gui/Notebook.py @@ -19,6 +19,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import import os +import logging from gi.repository import Gtk, Gdk, GObject @@ -28,15 +29,24 @@ from .Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT from .DrawingArea import DrawingArea +log = logging.getLogger(__name__) + + class Notebook(Gtk.Notebook): def __init__(self): Gtk.Notebook.__init__(self) - + log.debug("notebook()") + self.app = Gtk.Application.get_default() self.current_page = None + self.set_show_border(False) self.set_scrollable(True) self.connect('switch-page', self._handle_page_change) + self.add_events(Gdk.EventMask.SCROLL_MASK) + self.connect('scroll-event', self._handle_scroll) + self._ignore_consecutive_scrolls = 0 + def _handle_page_change(self, notebook, page, page_num): """ Handle a page change. When the user clicks on a new tab, @@ -51,6 +61,26 @@ class Notebook(Gtk.Notebook): self.current_page = self.get_nth_page(page_num) Actions.PAGE_CHANGE() + def _handle_scroll(self, widget, event): + # Not sure how to handle this at the moment. + natural = True + # Slow it down + if self._ignore_consecutive_scrolls == 0: + if event.direction in (Gdk.ScrollDirection.UP, Gdk.ScrollDirection.LEFT): + if natural: + self.prev_page() + else: + self.next_page() + elif event.direction in (Gdk.ScrollDirection.DOWN, Gdk.ScrollDirection.RIGHT): + if natural: + self.next_page() + else: + self.prev_page() + self._ignore_consecutive_scrolls = 3 + else: + self._ignore_consecutive_scrolls -= 1 + return False + class Page(Gtk.HBox): """A page in the notebook.""" @@ -99,20 +129,12 @@ class Page(Gtk.HBox): self.scrolled_window = Gtk.ScrolledWindow() self.scrolled_window.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT) self.scrolled_window.set_policy(Gtk.PolicyType.ALWAYS, Gtk.PolicyType.ALWAYS) - self.scrolled_window.connect('key-press-event', self._handle_scroll_window_key_press) + self.scrolled_window.add(self.drawing_area) self.pack_start(self.scrolled_window, True, True, 0) - self.show_all() - def _handle_scroll_window_key_press(self, widget, event): - """forward Ctrl-PgUp/Down to NotebookPage (switch fg instead of horiz. scroll""" - is_ctrl_pg = ( - event.state & Gdk.ModifierType.CONTROL_MASK and - event.keyval in (Gdk.KEY_Page_Up, Gdk.KEY_Page_Down) - ) - if is_ctrl_pg: - return self.get_parent().event(event) + def get_generator(self): """ diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py index b56eace402..71cb1b7a7d 100644 --- a/grc/gui/ParamWidgets.py +++ b/grc/gui/ParamWidgets.py @@ -25,7 +25,7 @@ from . import Utils style_provider = Gtk.CssProvider() -style_provider.load_from_data(""" +style_provider.load_from_data(b""" #dtype_complex { background-color: #3399FF; } #dtype_real { background-color: #FF8C69; } #dtype_float { background-color: #FF8C69; } diff --git a/grc/gui/StateCache.py b/grc/gui/StateCache.py index b109a1281b..ef260d6091 100644 --- a/grc/gui/StateCache.py +++ b/grc/gui/StateCache.py @@ -99,5 +99,5 @@ class StateCache(object): """ Update the undo and redo actions based on the number of next and prev states. """ - Actions.FLOW_GRAPH_REDO.set_sensitive(self.num_next_states != 0) - Actions.FLOW_GRAPH_UNDO.set_sensitive(self.num_prev_states != 0) + Actions.FLOW_GRAPH_REDO.set_enabled(self.num_next_states != 0) + Actions.FLOW_GRAPH_UNDO.set_enabled(self.num_prev_states != 0) diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py index 3fffe6dd20..969f3759f2 100644 --- a/grc/gui/Utils.py +++ b/grc/gui/Utils.py @@ -21,6 +21,7 @@ from __future__ import absolute_import from gi.repository import GLib import cairo +import six from .canvas.colors import FLOWGRAPH_BACKGROUND_COLOR from . import Constants @@ -107,7 +108,10 @@ def encode(value): Older versions of glib seg fault if the last byte starts a multi-byte character. """ - valid_utf8 = value.decode('utf-8', errors='replace').encode('utf-8') + if six.PY2: + valid_utf8 = value.decode('utf-8', errors='replace').encode('utf-8') + else: + valid_utf8 = value return GLib.markup_escape_text(valid_utf8) diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py index 484395be8c..e310676420 100644 --- a/grc/gui/VariableEditor.py +++ b/grc/gui/VariableEditor.py @@ -254,7 +254,9 @@ class VariableEditor(Gtk.VBox): elif key == self.ADD_VARIABLE: self.emit('create_new_block', 'variable') elif key == self.OPEN_PROPERTIES: - Actions.BLOCK_PARAM_MODIFY(self._block) + # TODO: This probably isn't working because the action doesn't expect a parameter + #Actions.BLOCK_PARAM_MODIFY() + pass elif key == self.DELETE_BLOCK: self.emit('remove_block', self._block.get_id()) elif key == self.DELETE_CONFIRM: diff --git a/grc/gui/canvas/param.py b/grc/gui/canvas/param.py index 2ec99e70d8..b027b7653a 100644 --- a/grc/gui/canvas/param.py +++ b/grc/gui/canvas/param.py @@ -105,7 +105,7 @@ class Param(CoreParam): if style < 0: # Front truncate string = '...' + string[3-max_len:] elif style == 0: # Center truncate - string = string[:max_len/2 - 3] + '...' + string[-max_len/2:] + string = string[:max_len//2 - 3] + '...' + string[-max_len//2:] elif style > 0: # Rear truncate string = string[:max_len-3] + '...' return string diff --git a/grc/main.py b/grc/main.py index 305e8b8f78..224a9b11e8 100755 --- a/grc/main.py +++ b/grc/main.py @@ -22,10 +22,6 @@ gi.require_version('Gtk', '3.0') gi.require_version('PangoCairo', '1.0') from gi.repository import Gtk -from gnuradio import gr -from .gui.Platform import Platform -from .gui.Application import Application - VERSION_AND_DISCLAIMER_TEMPLATE = """\ GNU Radio Companion %s @@ -45,6 +41,7 @@ LOG_LEVELS = { def main(): + from gnuradio import gr parser = argparse.ArgumentParser( description=VERSION_AND_DISCLAIMER_TEMPLATE % gr.version()) parser.add_argument('flow_graphs', nargs='*') @@ -65,7 +62,8 @@ def main(): console = logging.StreamHandler() console.setLevel(LOG_LEVELS[args.log]) - msg_format = '[%(asctime)s - %(levelname)8s] --- %(message)s (%(filename)s:%(lineno)s)' + #msg_format = '[%(asctime)s - %(levelname)8s] --- %(message)s (%(filename)s:%(lineno)s)' + msg_format = '[%(levelname)s] %(message)s (%(filename)s:%(lineno)s)' date_format = '%I:%M' formatter = logging.Formatter(msg_format, datefmt=date_format) @@ -73,8 +71,14 @@ def main(): console.setFormatter(formatter) log.addHandler(console) - log.debug("Loading platform") + log.debug("Running main") + # Delay importing until the logging is setup + # Otherwise, the decorators could not use logging. + from .gui.Platform import Platform + from .gui.Application import Application + + log.debug("Loading platform") platform = Platform( version=gr.version(), version_parts=(gr.major_version(), gr.api_version(), gr.minor_version()), -- cgit v1.2.3