From cbe6c6eeadee39a6b463955c0303b41c50323a13 Mon Sep 17 00:00:00 2001 From: "Romain F. T" Date: Fri, 16 Jul 2021 23:00:41 +0200 Subject: [PATCH] Auto-generate mnemonics to activate tools (#300) --- debian/changelog | 1 + src/tools/abstract_tool.py | 14 +++--- src/tools/classic_tools/tool_experiment.py | 6 ++- src/window.py | 57 +++++++++++++++++++++- 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/debian/changelog b/debian/changelog index 9b012510..7f57fef7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,6 +2,7 @@ drawing (1.0.0) unstable; urgency=low * dynamically change the label of the "options" submenu in the menu-bar, to increase its discoverability * + * enable tools with "alt+letter" mnemonics * artificially limited framerate to avoid overloading the CPU (#162) * toggle the menubar with ctrl+f2 * diff --git a/src/tools/abstract_tool.py b/src/tools/abstract_tool.py index a41ffde0..1508d9fe 100644 --- a/src/tools/abstract_tool.py +++ b/src/tools/abstract_tool.py @@ -37,7 +37,7 @@ def __init__(self, tool_id, label, icon_name, window, **kwargs): # The tool's identity self.id = tool_id self.menu_id = 0 - self.label = label + self.label = self.mnemolabel = label self.icon_name = icon_name # The options it supports self.accept_selection = False @@ -47,7 +47,6 @@ def __init__(self, tool_id, label, icon_name, window, **kwargs): self.cursor_name = 'cell' self._ongoing_operation = False # Once everything is set, build the UI - self.build_row() self.try_build_pane() ############################################################################ @@ -114,7 +113,7 @@ def adapt_to_window_size(self, available_width): pass def add_item_to_menu(self, tools_menu): - tools_menu.append(self.label, 'win.active_tool::' + self.id) + tools_menu.append(self.mnemolabel, 'win.active_tool::' + self.id) def get_options_model(self): """Returns a Gio.MenuModel corresponding to the tool's options. It'll be @@ -144,11 +143,11 @@ def build_row(self): relief = Gtk.ReliefStyle.NONE, \ draw_indicator = False, \ valign = Gtk.Align.CENTER, \ - tooltip_text = self.label \ + tooltip_text = self.label, \ ) self.row.set_detailed_action_name('win.active_tool::' + self.id) - self.label_widget = Gtk.Label(label=self.label) + self._label_widget = Gtk.Label(use_underline=True, label=self.mnemolabel) if self.window.gsettings.get_boolean('big-icons'): size = Gtk.IconSize.LARGE_TOOLBAR else: @@ -156,12 +155,13 @@ def build_row(self): image = Gtk.Image().new_from_icon_name(self.icon_name, size) box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) box.add(image) - box.add(self.label_widget) + box.add(self._label_widget) self.row.add(box) self.row.show_all() + return self.row def set_show_label(self, label_visible): - self.label_widget.set_visible(label_visible) + self._label_widget.set_visible(label_visible) if label_visible: self.row.get_children()[0].set_halign(Gtk.Align.START) else: diff --git a/src/tools/classic_tools/tool_experiment.py b/src/tools/classic_tools/tool_experiment.py index b65ea4d5..4a3a62d2 100644 --- a/src/tools/classic_tools/tool_experiment.py +++ b/src/tools/classic_tools/tool_experiment.py @@ -10,7 +10,6 @@ class ToolExperiment(AbstractClassicTool): def __init__(self, window, **kwargs): super().__init__('experiment', _("Experiment"), 'applications-utilities-symbolic', window) - self.row.get_style_context().add_class('destructive-action') # In order to draw pressure-sensitive lines, the path is collected as # an array whose elements are dicts (keys are 'x', 'y', 'p'). An actual @@ -57,6 +56,11 @@ def __init__(self, window, **kwargs): self.add_tool_action_enum('experiment_operator', self._operator_label) self.add_tool_action_enum('experiment_mode', self._selected_mode) + def build_row(self): + super().build_row() + self.row.get_style_context().add_class('destructive-action') + return self.row + def get_edition_status(self): return "You're not supposed to use this tool (development only)." diff --git a/src/window.py b/src/window.py index a1020fab..4a5667a4 100644 --- a/src/window.py +++ b/src/window.py @@ -204,6 +204,8 @@ def _init_tools(self): # self._load_tool('skew', ToolSkew, disabled_tools, dev) self._load_tool('filters', ToolFilters, disabled_tools, dev) + self._add_auto_mnemonics() + # Side pane buttons for tools, and their menubar items if they don't # exist yet (they're defined on the application level) self._build_tool_rows() @@ -220,6 +222,59 @@ def _init_tools(self): # method, will be called later because it requires an active image, # which doesn't exist yet at this point of the window init process. + def _add_auto_mnemonics(self): + # I don't want useful tools lacking a mnemonic accelerator because a + # useless one "stole" its letters, so the mnemonics are decided in the + # following order: + sorted_tools = [ + 'pencil', + 'text', + 'rect_select', + 'crop', + 'scale', + 'rotate', + 'shape', + 'filters', + 'line', + 'highlight', + 'arc', + 'picker', + 'brush', + 'free_select', + 'eraser', + 'skew', + 'paint', + 'color_select', + 'points', + 'experiment' + ] + for tool_id in self.tools: + if tool_id not in sorted_tools: + print("Warning: " + tool_id + "will not have a mnemonic") + + underlined_chars = {} + for tool_id in sorted_tools: + if tool_id not in self.tools: + continue + letter_index = 0 + while(letter_index >= 0): + if letter_index == len(self.tools[tool_id].label): + letter_index = -1 + continue + ith_char = self.tools[tool_id].label[letter_index] + letter_index += 1 + + if ith_char.isalpha() \ + and ith_char.upper() not in underlined_chars.values() \ + and ith_char.lower() not in underlined_chars.values(): + underlined_chars[tool_id] = ith_char + letter_index = -1 + + for tool_id in underlined_chars: + c = underlined_chars[tool_id] + new_label = self.tools[tool_id].label.replace(c, "_" + c, 1) + self.tools[tool_id].mnemolabel = new_label + def _enable_first_tool(self): """Near the end of the window initialisation process, this method is called once to make sure all things related to the active tool, mostly @@ -247,7 +302,7 @@ def _build_tool_rows(self): """Adds each tool's button to the side pane.""" group = None for tool_id in self.tools: - row = self.tools[tool_id].row + row = self.tools[tool_id].build_row() if group is None: group = row else: