From 3d47bc232513b85fd5492845a23107f4337d7539 Mon Sep 17 00:00:00 2001 From: James Turner Date: Thu, 31 Aug 2023 08:59:52 +0200 Subject: [PATCH] Canvas KeyBindings support - use new built-in KeyBinding in Widget, Menu --- Nasal/{keyboard.nas => canvas/KeyBinding.nas} | 190 +++++++----------- Nasal/canvas/gui/Menu.nas | 14 +- Nasal/canvas/gui/Widget.nas | 28 --- Nasal/canvas/gui/styles/DefaultStyle.nas | 2 +- 4 files changed, 79 insertions(+), 155 deletions(-) rename Nasal/{keyboard.nas => canvas/KeyBinding.nas} (65%) diff --git a/Nasal/keyboard.nas b/Nasal/canvas/KeyBinding.nas similarity index 65% rename from Nasal/keyboard.nas rename to Nasal/canvas/KeyBinding.nas index 2bb6e8c68..e4f694582 100644 --- a/Nasal/keyboard.nas +++ b/Nasal/canvas/KeyBinding.nas @@ -281,117 +281,45 @@ ModifierKeys.Meta = (ModifierKeys.Meta_L | ModifierKeys.Meta_R); #ModifierKeys.Super = (ModifierKeys.Super_L | ModifierKeys.Super_R); #ModifierKeys.Hyper = (ModifierKeys.Hyper_L | ModifierKeys.Hyper_R); -var Shortcut = { - new: func(modifiers, keys...) { - var m = { - parents: [Shortcut], - modifiers: modifiers, - keys: keys, - }; - if (m.modifiers == nil) { - m.modifiers = []; - m.keys = []; - } elsif (isa(m.modifiers, Shortcut)) { - var shortcut = m.modifiers; - m.modifiers = shortcut.modifiers; - m.keys = shortcut.keys; - } elsif (typeof(m.modifiers) == "scalar") { - var res = m.parseString(m.modifiers); - if (typeof(res) == "scalar") { - logprint(LOG_ALERT, "keyboard.Shortcut.parseString failed parsing '" ~ m.modifiers ~ "' with the following reason:"); - logprint(LOG_ALERT, "\t" ~ res); - m.modifiers = []; - m.keys = []; - } else { - m.modifiers = res[0]; - m.keys = res[1]; - } - } - - return m; - }, +var parseShortcut = func(s) +{ + if (size(s) == 0) { + return "shortcut string is empty"; + } - parseString: func(s) { - if (size(s) == 0) { - return "shortcut string is empty"; + var baseKey = nil; + var modifiers = []; + var input = split("+", s); + foreach (var key; input) { + if (string.match(key, "<*>")) { + append(modifiers, substr(key, 1, size(key) - 2)); + } else { + baseKey = key; } - - var found = []; - var modifiers = []; - var input = split("+", s); - foreach (var key; input) { - if (string.match(key, "<*>")) { - append(modifiers, substr(key, 1, size(key) - 2)); - } else { - append(found, key); - } - } - - var newmodifiers = []; - foreach (var mod; modifiers) { - if (ModifierKeys[mod] == nil) { - return "Unknown modifier key '" ~ mod ~ "' !"; - } else { - append(newmodifiers, ModifierKeys[mod]); - } - } - - var newfound = []; - foreach (var key; found) { - if (PrintableKeys[key] == nil) { - if (FunctionKeys[key] == nil) { - return "Unknown key '" ~ key ~ "' !"; - } else { - append(newfound, FunctionKeys[key]); - } - } else { - append(newfound, PrintableKeys[key]); - } - } - - return [newmodifiers, newfound]; - }, + } - match: func(keys, shift=0, ctrl=0, alt=0, meta=0) { - if (typeof(keys) != "vector") { - keys = [keys]; + var modMask = 0; + foreach (var mod; modifiers) { + var mm = ModifierKeys[mod]; + if (mm == nil) { + logprint(LOG_ALERT, "Unknown modifier key '" ~ mod ~ "' !"); + } else { + modMask = modMask + mm; } - if (!me.modifiers and !me.keys) { - return 0; - } - var match = 1; - match &= contains(me.modifiers, ModifierKeys["Shift"]) == shift; - match &= contains(me.modifiers, ModifierKeys["Ctrl"]) == ctrl; - match &= contains(me.modifiers, ModifierKeys["Alt"]) == alt; - match &= contains(me.modifiers, ModifierKeys["Meta"]) == meta; - match &= size(keys) == size(me.keys); - if (!match) { - return 0; - } - for (var i = 0; i < size(keys); i += 1) { - match &= contains(me.keys, keys[i]); - } - return match; - }, + } - repr: func { - if (!me.modifiers and !me.keys) { - return ""; - } - var names = []; - foreach (var mod; me.modifiers) { - append(names, findKeyName(mod)); - } - foreach (var key; me.keys) { - append(names, findKeyName(key)); - } - return string.join(" + ", names); - }, - - equals: func(other) { - return me.modifiers == other.modifiers and me.keys == other.keys; - }, -}; + var keyCode = PrintableKeys[baseKey]; + if (keyCode == nil) { + keyCode = FunctionKeys[baseKey]; + } + + if (keyCode == nil) { + logprint(LOG_ALERT, "Unknown key '" ~ baseKey ~ "'"); + } + + return [modMask, keyCode]; +} + var findKeyName = func(key) { foreach (var mod; keys(ModifierKeys)) { @@ -412,7 +340,8 @@ var findKeyName = func(key) { return nil; }; -var namesToKeys = func(strings...) { +var namesToKeys = func(strings...) +{ var res = []; foreach (var s; strings) { var key = ModifierKeys[s] or PrintableKeys[s] or FunctionKeys[s]; @@ -425,22 +354,39 @@ var namesToKeys = func(strings...) { return res; }; -var Binding = { - new: func(shortcut, f) { - var m = { - parents: [Binding], - f: f, - shortcut: shortcut, - }; - return m; - }, - - fire: func(e) { - debug.dump(e); - if (isfunc(me.f)) { - me.f(e); - } +# here we're extendin the built-in KeyBinding class defined in NasalCanvas.cxx, +# with some helper functions which rely on the tables defined above. + +KeyBinding['fromShortcut'] = func(shortcutString, cb = nil) +{ + if (!isstr(shortcutString)) { + return nil; } + + var res = KeyBinding.new(); + var t = parseShortcut(shortcutString); + + res.modifiers = t[0]; + res.keyCode = t[1]; + + if (!isfunc(cb)) { + logprint(LOG_ALERT, "callback argument to KeyBinding.fromShortcut is not a function") + } else { + res.addBinding(cb); + } + + return res; }; +KeyBinding['repr'] = func(kb) +{ + var names = []; + foreach (var mod; keys(ModifierKeys)) { + if (kb.modifiers & ModifierKeys[mod]) { + append(names, "<" ~ mod ~ ">"); + } + } + append(names, findKeyName(kb.keyCode)); + return string.join(" + ", names); +}; diff --git a/Nasal/canvas/gui/Menu.nas b/Nasal/canvas/gui/Menu.nas index 0fec4de3c..fa5b9455e 100644 --- a/Nasal/canvas/gui/Menu.nas +++ b/Nasal/canvas/gui/Menu.nas @@ -167,11 +167,14 @@ gui.MenuItem = { }, setShortcut: func(shortcut) { - me._shortcut = keyboard.Shortcut.new(shortcut); - if (me._parent_menu != nil and me._parent_menu._canvas_item != nil and me._cb != nil) { - me._parent_menu._canvas_item.bindShortcut(me._shortcut, me._cb); + if (!isstr(shortcut)) { + logprint(LOG_ALERT, "Menu.setShortcut: invalid shortcut"); + return; } - me._view.setShortcut(me, me._shortcut); + + me._keyBinding = KeyBinding.fromShortcut(shortcut, me._cb); + getDesktop().addKeyBinding(me._keyBinding); + me._view.setShortcut(me, KeyBinding.repr(me._keyBinding)); return me.update(); }, @@ -198,6 +201,9 @@ gui.MenuItem = { setCallback: func(cb = nil) { me._cb = cb; + if (me._keyBinding) { + me._keyBinding.addBinding(cb); + } return me; }, diff --git a/Nasal/canvas/gui/Widget.nas b/Nasal/canvas/gui/Widget.nas index 1651be137..9ab2df633 100644 --- a/Nasal/canvas/gui/Widget.nas +++ b/Nasal/canvas/gui/Widget.nas @@ -143,34 +143,6 @@ gui.Widget = { me._view._root.setVisible(visible); }, - - bindShortcut: func(s, f) - { - if (!isa(s, keyboard.Shortcut)) { - s = keyboard.Shortcut.new(s); - } - foreach (var b; me._bindings) { - if (b.shortcut.equals(s)) { - b.f = f; - return; - } - } - append(me._bindings, keyboard.Binding.new(s, f)); - # add listener when first binding is added - # if this happens before the view is set, we instead do it in _setView below - if ((size(me._bindings) == 1) and me._view) { - me._view._root.addEventListener("keydown", func(e) me._onKeyPressed(e)); - } - }, - - _onKeyPressed: func(e) { - foreach (var b; me._bindings) { - if (b.shortcut.match(keyboard.findKeyName(e.keyCode), e.shiftKey, e.ctrlKey, e.altKey, e.metaKey)) { - b.fire(e); - } - } - }, - _setView: func(view) { me._view = view; diff --git a/Nasal/canvas/gui/styles/DefaultStyle.nas b/Nasal/canvas/gui/styles/DefaultStyle.nas index b0957a344..4683631f0 100644 --- a/Nasal/canvas/gui/styles/DefaultStyle.nas +++ b/Nasal/canvas/gui/styles/DefaultStyle.nas @@ -1397,7 +1397,7 @@ DefaultStyle.widgets["menu-item"] = { setShortcut: func(model, shortcut) { if (shortcut != nil) { - me._shortcut.setText(shortcut.repr()); + me._shortcut.setText(shortcut); } return me._updateLayoutSizes(model); },