Repository: beeware/toga

Issue:
Keyboard shortcut in tutorial 2 is not working
**Describe the bug**
I was expecting Ctrl+k on Windows 10 to trigger action 3, but it didn't seem to do anything.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to https://toga.readthedocs.io/en/latest/tutorial/tutorial-2.html
2. Run the code
3. Press `<MOD_1>+k`
4. Notice how the terminal doesn't print anything

**Expected behavior**
Expected `action 3` to br printed.

**Environment:**
 - Operating System: Windows 10
 - Python version: CPython 3.8.3
 - Software versions:
   - Briefcase:
   - Toga: 0.3.0dev22
   - ...

Keyboard shortcut in tutorial 2 is not working
**Describe the bug**
I was expecting Ctrl+k on Windows 10 to trigger action 3, but it didn't seem to do anything.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to https://toga.readthedocs.io/en/latest/tutorial/tutorial-2.html
2. Run the code
3. Press `<MOD_1>+k`
4. Notice how the terminal doesn't print anything

**Expected behavior**
Expected `action 3` to br printed.

**Environment:**
 - Operating System: Windows 10
 - Python version: CPython 3.8.3
 - Software versions:
   - Briefcase:
   - Toga: 0.3.0dev22
   - ...



Discussion:
Thanks for the report. Not sure what is going on here, but it's definitely a problem.
Thanks for the report. Not sure what is going on here, but it's definitely a problem.

Resolving patch:
diff --git a/src/winforms/toga_winforms/app.py b/src/winforms/toga_winforms/app.py
--- a/src/winforms/toga_winforms/app.py
+++ b/src/winforms/toga_winforms/app.py
@@ -4,7 +4,9 @@
 import traceback
 
 import toga
+from toga import Key
 from toga.handlers import wrapped_handler
+from .keys import toga_to_winforms_key
 
 from .libs import Threading, WinForms, shcore, user32, win_version
 from .libs.proactor import WinformsProactorEventLoop
@@ -55,8 +57,13 @@ def create(self):
             toga.Command(None, 'About ' + self.interface.name, group=toga.Group.HELP),
             toga.Command(None, 'Preferences', group=toga.Group.FILE),
             # Quit should always be the last item, in a section on it's own
-            toga.Command(lambda s: self.exit(), 'Exit ' + self.interface.name, shortcut='q', group=toga.Group.FILE,
-                         section=sys.maxsize),
+            toga.Command(
+                lambda s: self.exit(),
+                'Exit ' + self.interface.name,
+                shortcut=Key.MOD_1 + 'q',
+                group=toga.Group.FILE,
+                section=sys.maxsize
+            ),
             toga.Command(None, 'Visit homepage', group=toga.Group.HELP)
         )
         self._create_app_commands()
@@ -89,6 +96,10 @@ def create_menus(self):
                         item.Click += cmd._impl.as_handler()
                     else:
                         item.Enabled = False
+                    if cmd.shortcut is not None:
+                        shortcut_keys = toga_to_winforms_key(cmd.shortcut)
+                        item.ShortcutKeys = shortcut_keys
+                        item.ShowShortcutKeys = True
                     cmd._impl.native.append(item)
                     self._menu_items[item] = cmd
                     submenu.DropDownItems.Add(item)
@@ -196,7 +207,7 @@ def _create_app_commands(self):
             toga.Command(
                 lambda w: self.open_file,
                 label='Open...',
-                shortcut='o',
+                shortcut=Key.MOD_1 + 'o',
                 group=toga.Group.FILE,
                 section=0
             ),
diff --git a/src/winforms/toga_winforms/keys.py b/src/winforms/toga_winforms/keys.py
new file mode 100644
--- /dev/null
+++ b/src/winforms/toga_winforms/keys.py
@@ -0,0 +1,48 @@
+from toga.keys import Key
+from .libs import WinForms
+
+
+def toga_to_winforms_key(key):
+    code = 0
+    for modifier, modifier_code in WINFORMS_MODIFIERS_MAP.items():
+        if modifier.value in key:
+            code |= modifier_code
+            key = key.replace(modifier.value, "")
+    key_code = WINFORMS_KEYS_MAP.get(key, None)
+    if key_code is not None:
+        code |= key_code
+    return code
+
+
+WINFORMS_MODIFIERS_MAP = {
+    Key.MOD_1: WinForms.Keys.Control,
+    Key.MOD_2: WinForms.Keys.Alt,
+}
+WINFORMS_KEYS_MAP = {
+    "a": WinForms.Keys.A,
+    "b": WinForms.Keys.B,
+    "c": WinForms.Keys.C,
+    "d": WinForms.Keys.D,
+    "e": WinForms.Keys.E,
+    "f": WinForms.Keys.F,
+    "g": WinForms.Keys.G,
+    "h": WinForms.Keys.H,
+    "i": WinForms.Keys.I,
+    "j": WinForms.Keys.J,
+    "k": WinForms.Keys.K,
+    "l": WinForms.Keys.L,
+    "m": WinForms.Keys.M,
+    "n": WinForms.Keys.N,
+    "o": WinForms.Keys.O,
+    "p": WinForms.Keys.P,
+    "q": WinForms.Keys.Q,
+    "r": WinForms.Keys.R,
+    "s": WinForms.Keys.S,
+    "t": WinForms.Keys.T,
+    "u": WinForms.Keys.U,
+    "v": WinForms.Keys.V,
+    "w": WinForms.Keys.W,
+    "x": WinForms.Keys.X,
+    "y": WinForms.Keys.Y,
+    "z": WinForms.Keys.Z,
+}


Test patch: