Repos / mcross / 526da28ed3
commit 526da28ed3db85ceb5664dacba584876a3cdab5c
Author: Bùi Thành Nhân <hi@imnhan.com>
Date:   Sun May 31 16:40:36 2020 +0700

    implement alt-shortcuts for buttons

diff --git a/src/mcross/gui/controller.py b/src/mcross/gui/controller.py
index dfaccea..7a6662c 100644
--- a/src/mcross/gui/controller.py
+++ b/src/mcross/gui/controller.py
@@ -21,6 +21,7 @@
 class Controller:
     def __init__(self):
         self.root = Tk()
+        self.root.alt_shortcuts = set()
         self.model = Model()
         self.view = View(self.root, self.model)
         self.root.title("McRoss Browser")
diff --git a/src/mcross/gui/view.py b/src/mcross/gui/view.py
index d4a8a5a..0bc57da 100644
--- a/src/mcross/gui/view.py
+++ b/src/mcross/gui/view.py
@@ -13,7 +13,7 @@
     TextNode,
 )
 from .model import Model
-from .widgets import McEntry, ReadOnlyText
+from .widgets import AltButton, McEntry, ReadOnlyText
 
 # OS-specific values
 if sys.platform == "win32":
@@ -58,9 +58,9 @@ def emit(self, record):
 class View:
     model: Model
     address_bar: ttk.Entry
-    go_btn: ttk.Button
-    back_btn: ttk.Button
-    forward_btn: ttk.Button
+    go_btn: AltButton
+    back_btn: AltButton
+    forward_btn: AltButton
     text: Text
     status_bar: ttk.Label
 
@@ -90,11 +90,23 @@ def __init__(self, root: Tk, model: Model):
         register_status_bar_log_handler(status_bar)
 
         # Back/Forward buttons
-        back_btn = ttk.Button(
-            row1, text="◀", width=3, command=lambda: self.back_callback()
+        back_btn = AltButton(
+            row1,
+            text="◀",
+            width=3,
+            command=lambda: self.back_callback(),
+            root=root,
+            alt_char_index=0,
+            alt_key="Left",
         )
-        forward_btn = ttk.Button(
-            row1, text="▶", width=3, command=lambda: self.forward_callback()
+        forward_btn = AltButton(
+            row1,
+            text="▶",
+            width=3,
+            command=lambda: self.forward_callback(),
+            root=root,
+            alt_char_index=0,
+            alt_key="Right",
         )
         back_btn.pack(side="left", padx=2)
         forward_btn.pack(side="left", padx=2)
@@ -108,7 +120,7 @@ def __init__(self, root: Tk, model: Model):
         # Address bar
         address_bar = McEntry(row1)
         self.address_bar = address_bar
-        address_bar.pack(side="left", fill="both", expand=True, padx=3, pady=3)
+        address_bar.pack(side="left", fill="both", expand=True, pady=3)
         address_bar.bind("<Return>", self._on_go)
         address_bar.bind("<KP_Enter>", self._on_go)
         address_bar.focus_set()
@@ -120,9 +132,11 @@ def on_ctrl_l(ev):
         root.bind("<Control-l>", on_ctrl_l)
 
         # Go button
-        go_btn = ttk.Button(row1, text="三三ᕕ( ᐛ )ᕗ", command=self._on_go, width=10)
+        go_btn = AltButton(
+            row1, text="Go", root=root, alt_char_index=0, command=self._on_go, width=5
+        )
         self.go_btn = go_btn
-        go_btn.pack(side="left", pady=3)
+        go_btn.pack(side="left", padx=2, pady=3)
 
         # Main viewport implemented as a Text widget.
         text = ReadOnlyText(row2, wrap="word")
diff --git a/src/mcross/gui/widgets.py b/src/mcross/gui/widgets.py
index 846bd54..e04cbc6 100644
--- a/src/mcross/gui/widgets.py
+++ b/src/mcross/gui/widgets.py
@@ -34,3 +34,43 @@ def select_all(self, ev=None):
         self.select_range(0, "end")
         self.icursor("end")
         return "break"
+
+
+class AltButton(ttk.Button):
+    """
+    Like Button but also supports Alt-<Key> shortcut (like Qt's Accelerator Keys).
+    Accepts 3 extra args:
+    - root: The root tk instance.
+    - alt_char_index: Character index to be underlined when Alt is held down.
+    - alt_key (optional): Explicit key name to pass to bind().
+      If alt_key is not provided then it will be inferred from alt_char_index.
+    """
+
+    def __init__(self, *args, root, alt_char_index, alt_key=None, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.root = root
+
+        alt_key = alt_key or self["text"][alt_char_index].lower()
+        assert alt_key not in root.alt_shortcuts, f"Duplicate shortcut for {alt_key}"
+        root.alt_shortcuts.add(alt_key)
+
+        root.bind("<Alt_L>", self._alt_down, add="+")
+        root.bind("<KeyRelease-Alt_L>", self._alt_up, add="+")
+        root.bind(f"<Alt-{alt_key}>", self._alt_button_down)
+        root.bind(f"<Alt-KeyRelease-{alt_key}>", self._alt_button_up)
+
+        self.alt_char_index = alt_char_index
+        self.alt_key = alt_key
+
+    def _alt_down(self, event):
+        self.config(underline=self.alt_char_index)
+
+    def _alt_up(self, event):
+        self.config(underline=-1)
+
+    def _alt_button_down(self, event):
+        self.state(["pressed"])
+
+    def _alt_button_up(self, event):
+        self.invoke()
+        self.state(["!pressed"])