From 2cf388e6748cd272684302b35ebe96a58d3e8fe3 Mon Sep 17 00:00:00 2001 From: TheFGFSEagle Date: Sun, 8 Jan 2023 15:35:46 +0100 Subject: [PATCH] Added real keyboard shortcut support for menu items --- Nasal/canvas/api/element.nas | 25 ++++++++++++++++ Nasal/canvas/gui/Menu.nas | 38 +++++++++++++++++------- Nasal/canvas/gui/Widget.nas | 8 +++++ Nasal/canvas/gui/styles/DefaultStyle.nas | 2 +- Nasal/canvas/gui/widgets/LineEdit.nas | 30 +++++++------------ Nasal/keyboard.nas | 9 +++--- 6 files changed, 77 insertions(+), 35 deletions(-) diff --git a/Nasal/canvas/api/element.nas b/Nasal/canvas/api/element.nas index e1bb2c738..ef43f0e5d 100644 --- a/Nasal/canvas/api/element.nas +++ b/Nasal/canvas/api/element.nas @@ -16,10 +16,35 @@ var Element = { var obj = { parents: [Element, PropertyElement, ghost], _node: props.wrapNode(ghost._node_ghost), + _bindings: [], }; return obj; }, + 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)); + if (size(me._bindings) == 1) { + obj.addEventListener("keydown", func(e) obj.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); + } + } + }, + getType: func () { return me._node.getName(); }, diff --git a/Nasal/canvas/gui/Menu.nas b/Nasal/canvas/gui/Menu.nas index 67f78cff9..21e6fd002 100644 --- a/Nasal/canvas/gui/Menu.nas +++ b/Nasal/canvas/gui/Menu.nas @@ -16,7 +16,7 @@ gui.MenuItem = { # @description Create a new menu item widget # @cfg_field text: str Text of the new menu item - # @cfg_field shortcut: str String representation of the keyboard shortcut for the item + # @cfg_field shortcut: str String representation of the keyboard shortcut for the item # @cfg_field cb: callable Function / method to call when the item is clicked # @cfg_field icon: str Path of an icon to be displayed (relative to the path in `canvas.style._dir_widgets`) # @cfg_field enabled: bool Initial state of the menu item: enabled (1) or disabled (0) @@ -24,7 +24,7 @@ gui.MenuItem = { var cfg = Config.new(cfg); var m = gui.Widget.new(gui.MenuItem); m._text = cfg.get("text", "Menu item"); - m._shortcut = cfg.get("shortcut", ""); + m._shortcut = cfg.get("shortcut", nil); m._cb = cfg.get("cb", nil); m._icon = cfg.get("icon", nil); m._enabled = cfg.get("enabled", 1); @@ -38,9 +38,9 @@ gui.MenuItem = { m.setLayoutSizeHint([64, 24]); m.setLayoutMaximumSize([1024, 24]); - m._view.setText(m, m._text); - m._view.setIcon(m._icon); - m._view.setShortcut(m, m._shortcut); + m.setText(m._text); + m.setIcon(m._icon); + m.setShortcut(m._shortcut); return m; }, @@ -110,11 +110,21 @@ gui.MenuItem = { }, setShortcut: func(shortcut) { - me._shortcut = shortcut; - me._view.setShortcut(me, 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); + } + me._view.setShortcut(me, me._shortcut); return me.update(); }, + _setParentMenu: func(m) { + me._parent_menu = m; + 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); + } + }, + setIcon: func(icon) { me._icon = icon; me._view.setIcon(icon); @@ -153,16 +163,24 @@ gui.Menu = { m._root = m._canvas.createGroup(); m._layout = VBoxLayout.new(); m._layout.setSpacing(0); + m._canvas_item = nil; m.setLayout(m._layout); m.hide(); return m; }, + setCanvasItem: func(item) { + me._canvas_item = item; + for (var i = 0; i < me._layout.count(); i += 1) { + me._layout.itemAt(i)._setParentMenu(me); + } + }, + # @description Add the given menu item to the menu (normally a `canvas.gui.MenuItem`, but can be any `canvas.gui.Widget` in theory) # @return canvas.gui.Menu Return me to enable method chaining addItem: func(item) { - item._parent_menu = me; + item._setParentMenu(me); me._layout.addItem(item); me.setSize(me._layout.minimumSize()[0], me._layout.minimumSize()[1]); return me; @@ -175,7 +193,7 @@ gui.Menu = { # @param icon: str optional Path to the icon (relative to canvas.style._dir_widgets) or nil if none should be displayed # @param enabled: bool optional Whether the item should be enabled (1) or disabled (0) # @return canvas.gui.MenuItem The item that was created - createItem: func(text = nil, cb = nil, shortcut = "", icon = nil, enabled = 1) { + createItem: func(text = nil, cb = nil, shortcut = nil, icon = nil, enabled = 1) { if (text == nil) { die("cannot create a menu item without text"); } @@ -198,7 +216,7 @@ gui.Menu = { if (menu == nil) { die("cannot create a submenu item without submenu"); } - var item = gui.MenuItem.new(me._root, me.style, {text: text, cb: nil, shortcut: "", icon: icon, enabled: enabled}); + var item = gui.MenuItem.new(me._root, me.style, {text: text, cb: nil, shortcut: nil, icon: icon, enabled: enabled}); item.setMenu(menu); me.addItem(item); return item; diff --git a/Nasal/canvas/gui/Widget.nas b/Nasal/canvas/gui/Widget.nas index 1157368d6..bb8564b87 100644 --- a/Nasal/canvas/gui/Widget.nas +++ b/Nasal/canvas/gui/Widget.nas @@ -141,6 +141,9 @@ gui.Widget = { { me._view._root.setVisible(visible); }, + bindShortcut: func(s, f) { + me._view._root.bindShortcut(s, f); + }, _setView: func(view) { me._view = view; @@ -170,6 +173,11 @@ gui.Widget = { me._trigger("mouse-leave"); me._onStateChange(); }); + root.addEventListener("keypress", func(e) { + if (me._focused) { + root.onKeyPressed(e); + } + }); }, _trigger: func(type, data = nil) { diff --git a/Nasal/canvas/gui/styles/DefaultStyle.nas b/Nasal/canvas/gui/styles/DefaultStyle.nas index ebc1b0251..8a819fe1a 100644 --- a/Nasal/canvas/gui/styles/DefaultStyle.nas +++ b/Nasal/canvas/gui/styles/DefaultStyle.nas @@ -900,7 +900,7 @@ DefaultStyle.widgets["menu-item"] = { }, setShortcut: func(model, shortcut) { - me._shortcut.setText(shortcut); + me._shortcut.setText(shortcut.repr()); return me._updateLayoutSizes(model); }, diff --git a/Nasal/canvas/gui/widgets/LineEdit.nas b/Nasal/canvas/gui/widgets/LineEdit.nas index 6ae36c5ad..3aeb3c401 100644 --- a/Nasal/canvas/gui/widgets/LineEdit.nas +++ b/Nasal/canvas/gui/widgets/LineEdit.nas @@ -16,11 +16,12 @@ gui.widgets.LineEdit = { m._selection_end = 0; m.context_menu = gui.Menu.new(); - m.context_menu.createItem(text: "Copy", cb: func() { m.copy(); }, shortcut: "Ctrl+C"); - m.context_menu.createItem(text: "Cut", cb: func() { m.cut(); }, shortcut: "Ctrl+X"); - m.context_menu.createItem(text: "Paste", cb: func() { m.paste(); }, shortcut: "Ctrl+V"); - m.context_menu.createItem(text: "Clear", cb: func() { m.clear(); }, shortcut: "Ctrl+D"); - m.context_menu.createItem(text: "Select all", cb: func() { m.selectAll(); }, shortcut: "Ctrl+A"); + m.context_menu.createItem(text: "Copy", cb: func() { m.copy(); }, shortcut: "+C"); + m.context_menu.createItem(text: "Cut", cb: func() { m.cut(); }, shortcut: "+X"); + m.context_menu.createItem(text: "Paste", cb: func() { m.paste(); }, shortcut: "+V"); + m.context_menu.createItem(text: "Clear", cb: func() { m.clear(); }, shortcut: "+D"); + m.context_menu.createItem(text: "Select all", cb: func() { m.selectAll(); }, shortcut: "+A"); + m.context_menu.setCanvasItem(m); return m; }, @@ -182,7 +183,11 @@ gui.widgets.LineEdit = { call(gui.Widget._setView, [view], me); var el = view._root; - el.addEventListener("keypress", func (e) me.insert(e.key)); + el.addEventListener("keypress", func (e) { + if (!e.ctrlKey and !e.altKey and !e.metaKey) { + me.insert(e.key); + } + }); el.addEventListener("keydown", func (e) { if( me._view == nil ) @@ -202,19 +207,6 @@ gui.widgets.LineEdit = { me.home(); else if( e.key == "End" ) me.end(); - else if (e.ctrlKey) { - if (e.keyCode == `c`) { - me.copy(); - } elsif (e.keyCode == `v`) { - me.paste(); - } elsif (e.keyCode == `x`) { - me.cut(); - } elsif (e.keyCode == `d`) { - me.clear(); - } elsif (e.keyCode == `a`) { - me.selectAll(); - } - } }); el.addEventListener("click", func(e) { if (e.button == 2) { diff --git a/Nasal/keyboard.nas b/Nasal/keyboard.nas index de857b5d1..10fea5ea2 100644 --- a/Nasal/keyboard.nas +++ b/Nasal/keyboard.nas @@ -357,10 +357,10 @@ var Shortcut = { return 0; } var match = 1; - match &= veccontains(me.modifiers, ModifierKeys["Shift"]) == shift; - match &= veccontains(me.modifiers, ModifierKeys["Ctrl"]) == ctrl; - match &= veccontains(me.modifiers, ModifierKeys["Alt"]) == alt; - match &= veccontains(me.modifiers, ModifierKeys["Meta"]) == meta; + 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; @@ -376,7 +376,6 @@ var Shortcut = { return ""; } var names = []; - debug.dump(me.modifiers); foreach (var mod; me.modifiers) { append(names, findKeyName(mod)); }