From b7aaef8acfd200d885487d2bd9d526987e8ddd24 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Mon, 22 Mar 2021 11:34:08 +0000 Subject: [PATCH] Control Key Support on Tab and Function keys, Tab Focus Basic implementatin --- README.md | 2 +- TermTk/TTkCore/constant.py | 8 +++ TermTk/TTkCore/helper.py | 54 +++++++++++++++ TermTk/TTkCore/ttk.py | 9 +++ TermTk/TTkWidgets/lineedit.py | 2 +- TermTk/TTkWidgets/widget.py | 3 +- TermTk/libbpytop/inputkey.py | 119 ++++++++++++++++++++++++---------- docs/TODO.md | 11 ++-- 8 files changed, 166 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 7726762b..5867800b 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ and inspired by a mix of [Qt5](https://www.riverbankcomputing.com/static/Docs/Py - Only the key combinations forwarded by the terminal emulator used are detected (ALT,CTRL may not be handled) ## Try -Works better on [repl.it](https://repl.it/@EugenioP/pyTermTk) +Works better on [repl.it](https://replit.com/@EugenioP/pyTermTk?v=1) But you can try it here: (the console has some terminal size issues, better to run on the **shell** `python3 demo/demo.py -f`) diff --git a/TermTk/TTkCore/constant.py b/TermTk/TTkCore/constant.py index 09088771..2a60bf40 100644 --- a/TermTk/TTkCore/constant.py +++ b/TermTk/TTkCore/constant.py @@ -138,6 +138,14 @@ class TTkConstant: Character = 0x0001 SpecialKey = 0x0002 + NoModifier = 0x00000000 # No modifier key is pressed. + ShiftModifier = 0x02000000 # A Shift key on the keyboard is pressed. + ControlModifier = 0x04000000 # A Ctrl key on the keyboard is pressed. + AltModifier = 0x08000000 # An Alt key on the keyboard is pressed. + MetaModifier = 0x10000000 # A Meta key on the keyboard is pressed. + KeypadModifier = 0x20000000 # A keypad button is pressed. + GroupSwitchModifier = 0x40000000 # X11 only (unless activated on Windows by a command line argument). A Mode_switch key on the keyboard is pressed. + Key_Escape = 0x01000000 Key_Tab = 0x01000001 Key_Backtab = 0x01000002 diff --git a/TermTk/TTkCore/helper.py b/TermTk/TTkCore/helper.py index 16483af5..59a87d50 100644 --- a/TermTk/TTkCore/helper.py +++ b/TermTk/TTkCore/helper.py @@ -211,6 +211,60 @@ class TTkHelper: return (0, 0) return TTkHelper.absPos(widget.parentWidget()) + + def _iterWidgets(item): + for child in item.children(): + if child.layoutItemType == TTkK.WidgetItem: + if not child.widget().isVisible(): continue + yield child.widget() + for i in TTkHelper._iterWidgets(child.widget().rootLayout()): + yield i + if child.layoutItemType == TTkK.LayoutItem: + for i in TTkHelper._iterWidgets(child): + yield i + + def nextFocus(widget, prevFocus=False): + rootWidget = TTkHelper.getOverlay() + if not rootWidget: + rootWidget = TTkHelper._rootCanvas.getWidget() + if widget == rootWidget: + widget = None + first = None + for w in TTkHelper._iterWidgets(rootWidget.rootLayout()): + if not first and w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus: + first = w + TTkLog.debug(f"{w._name} {widget}") + if widget: + if w == widget: + widget=None + continue + if w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus: + w.setFocus() + w.update() + return + if first: + first.setFocus() + first.update() + + def prevFocus(widget): + rootWidget = TTkHelper.getOverlay() + if not rootWidget: + rootWidget = TTkHelper._rootCanvas.getWidget() + if widget == rootWidget: + widget = None + prev = None + for w in TTkHelper._iterWidgets(rootWidget.rootLayout()): + TTkLog.debug(f"{w._name} {widget}") + if w == widget: + widget=None + if prev: + break + if w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus: + prev = w + if prev: + prev.setFocus() + prev.update() + @staticmethod def setFocus(widget): TTkHelper._focusWidget = widget diff --git a/TermTk/TTkCore/ttk.py b/TermTk/TTkCore/ttk.py index 1fa8eea4..79b11402 100644 --- a/TermTk/TTkCore/ttk.py +++ b/TermTk/TTkCore/ttk.py @@ -47,6 +47,7 @@ class TTk(TTkWidget): def __init__(self, *args, **kwargs): TTkWidget.__init__(self, *args, **kwargs) + self._name = kwargs.get('name' , 'TTk' ) self.events = queue.Queue() self.key_events = queue.Queue() self.mouse_events = queue.Queue() @@ -130,9 +131,17 @@ class TTk(TTkWidget): self.mouseEvent(mevt) elif evt is TTkK.KEY_EVENT: kevt = self.key_events.get() + TTkLog.debug(f"Key: {kevt}") focusWidget = TTkHelper.getFocus() overlayWidget = TTkHelper.getOverlay() TTkLog.debug(f"{focusWidget}") + if kevt.key == TTkK.Key_Tab: + # TODO: Handle here if the widget accept the Tab input + if kevt.mod == TTkK.NoModifier: + TTkHelper.nextFocus(focusWidget if focusWidget else self) + if kevt.mod == TTkK.ShiftModifier: + TTkHelper.prevFocus(focusWidget if focusWidget else self) + continue if focusWidget is not None: TTkHelper.execShortcut(kevt.key,focusWidget) focusWidget.keyEvent(kevt) diff --git a/TermTk/TTkWidgets/lineedit.py b/TermTk/TTkWidgets/lineedit.py index f2762e87..39ff962b 100644 --- a/TermTk/TTkWidgets/lineedit.py +++ b/TermTk/TTkWidgets/lineedit.py @@ -56,7 +56,7 @@ class TTkLineEdit(TTkWidget): self._cursorPos = 0 self._replace=False self.setMaximumHeight(1) - self.setMinimumSize(10,1) + self.setMinimumSize(1,1) self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) @pyTTkSlot(str) diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index 6e89fdb4..e3c72422 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -289,7 +289,7 @@ class TTkWidget(TMouseEvents,TKeyEvents): mouseEvent = True wx,wy,ww,wh = widget.geometry() # Skip the mouse event if outside this widget - if x >= wx and x=wy and y') - return f"KeyEvent: {self.key} {key2str(self.key)} {code}" + return f"KeyEvent: {self.key} {key2str(self.key)} {mod2str(self.mod)} {code}" @staticmethod def parse(input_key): # from: Space except "DEL" if len(input_key) == 1 and "\040" <= input_key != "\177": - return KeyEvent(TTkK.Character, input_key, input_key) + return KeyEvent(TTkK.Character, input_key, input_key, TTkK.NoModifier) else: - key = _translate_key(input_key) + key, mod = _translate_key(input_key) if key is not None: - return KeyEvent(TTkK.SpecialKey, key, input_key) + return KeyEvent(TTkK.SpecialKey, key, input_key, mod) return None def _translate_key(key): - if key == "\177" : return TTkK.Key_Backspace - elif key == "\t" : return TTkK.Key_Tab - elif key == "\n" : return TTkK.Key_Enter - elif key == "\033[A" : return TTkK.Key_Up - elif key == "\033[B" : return TTkK.Key_Down - elif key == "\033[C" : return TTkK.Key_Right - elif key == "\033[D" : return TTkK.Key_Left - elif key == "\033[5~": return TTkK.Key_PageUp - elif key == "\033[6~": return TTkK.Key_PageDown + if key == "\177" : return TTkK.Key_Backspace , TTkK.NoModifier + elif key == "\t" : return TTkK.Key_Tab , TTkK.NoModifier + elif key == "\033[Z" : return TTkK.Key_Tab , TTkK.ShiftModifier + elif key == "\n" : return TTkK.Key_Enter , TTkK.NoModifier + elif key == "\033[A" : return TTkK.Key_Up , TTkK.NoModifier + elif key == "\033[B" : return TTkK.Key_Down , TTkK.NoModifier + elif key == "\033[C" : return TTkK.Key_Right , TTkK.NoModifier + elif key == "\033[D" : return TTkK.Key_Left , TTkK.NoModifier + elif key == "\033[5~" : return TTkK.Key_PageUp , TTkK.NoModifier + elif key == "\033[6~" : return TTkK.Key_PageDown , TTkK.NoModifier # Xterm - elif key == "\033[F" : return TTkK.Key_End - elif key == "\033[H" : return TTkK.Key_Home + elif key == "\033[F" : return TTkK.Key_End , TTkK.NoModifier + elif key == "\033[H" : return TTkK.Key_Home , TTkK.NoModifier # Terminator + tmux - elif key == "\033[4~": return TTkK.Key_End - elif key == "\033[1~": return TTkK.Key_Home - elif key == "\033[2~": return TTkK.Key_Insert - elif key == "\033[3~": return TTkK.Key_Delete - elif key == "\033": return TTkK.Key_Escape - elif key == "\033OP": return TTkK.Key_F1 - elif key == "\033OQ": return TTkK.Key_F2 - elif key == "\033OR": return TTkK.Key_F3 - elif key == "\033OS": return TTkK.Key_F4 - elif key == "\033[15~": return TTkK.Key_F5 - elif key == "\033[17~": return TTkK.Key_F6 - elif key == "\033[18~": return TTkK.Key_F7 - elif key == "\033[19~": return TTkK.Key_F8 - elif key == "\033[20~": return TTkK.Key_F9 - elif key == "\033[21~": return TTkK.Key_F10 - elif key == "\033[23~": return TTkK.Key_F11 - elif key == "\033[24~": return TTkK.Key_F12 - return None + elif key == "\033[4~" : return TTkK.Key_End , TTkK.NoModifier + elif key == "\033[1~" : return TTkK.Key_Home , TTkK.NoModifier + elif key == "\033[2~" : return TTkK.Key_Insert , TTkK.NoModifier + elif key == "\033[3~" : return TTkK.Key_Delete , TTkK.NoModifier + elif key == "\033" : return TTkK.Key_Escape , TTkK.NoModifier + # Function Key + elif key == "\033OP" : return TTkK.Key_F1 , TTkK.NoModifier + elif key == "\033OQ" : return TTkK.Key_F2 , TTkK.NoModifier + elif key == "\033OR" : return TTkK.Key_F3 , TTkK.NoModifier + elif key == "\033OS" : return TTkK.Key_F4 , TTkK.NoModifier + elif key == "\033[15~" : return TTkK.Key_F5 , TTkK.NoModifier + elif key == "\033[17~" : return TTkK.Key_F6 , TTkK.NoModifier + elif key == "\033[18~" : return TTkK.Key_F7 , TTkK.NoModifier + elif key == "\033[19~" : return TTkK.Key_F8 , TTkK.NoModifier + elif key == "\033[20~" : return TTkK.Key_F9 , TTkK.NoModifier + elif key == "\033[21~" : return TTkK.Key_F10 , TTkK.NoModifier + elif key == "\033[23~" : return TTkK.Key_F11 , TTkK.NoModifier + elif key == "\033[24~" : return TTkK.Key_F12 , TTkK.NoModifier + elif key == "\033[1;2P" : return TTkK.Key_F1 , TTkK.ShiftModifier + elif key == "\033[1;2Q" : return TTkK.Key_F2 , TTkK.ShiftModifier + elif key == "\033[1;2R" : return TTkK.Key_F3 , TTkK.ShiftModifier + elif key == "\033[1;2S" : return TTkK.Key_F4 , TTkK.ShiftModifier + elif key == "\033[15;2~": return TTkK.Key_F5 , TTkK.ShiftModifier + elif key == "\033[17;2~": return TTkK.Key_F6 , TTkK.ShiftModifier + elif key == "\033[18;2~": return TTkK.Key_F7 , TTkK.ShiftModifier + elif key == "\033[19;2~": return TTkK.Key_F8 , TTkK.ShiftModifier + elif key == "\033[20;2~": return TTkK.Key_F9 , TTkK.ShiftModifier + elif key == "\033[21;2~": return TTkK.Key_F10 , TTkK.ShiftModifier + elif key == "\033[23;2~": return TTkK.Key_F11 , TTkK.ShiftModifier + elif key == "\033[24;2~": return TTkK.Key_F12 , TTkK.ShiftModifier + elif key == "\033[1;5P" : return TTkK.Key_F1 , TTkK.ControlModifier + elif key == "\033[1;5Q" : return TTkK.Key_F2 , TTkK.ControlModifier + elif key == "\033[1;5R" : return TTkK.Key_F3 , TTkK.ControlModifier + elif key == "\033[1;5S" : return TTkK.Key_F4 , TTkK.ControlModifier + elif key == "\033[15;5~": return TTkK.Key_F5 , TTkK.ControlModifier + elif key == "\033[17;5~": return TTkK.Key_F6 , TTkK.ControlModifier + elif key == "\033[18;5~": return TTkK.Key_F7 , TTkK.ControlModifier + elif key == "\033[19;5~": return TTkK.Key_F8 , TTkK.ControlModifier + elif key == "\033[20;5~": return TTkK.Key_F9 , TTkK.ControlModifier + elif key == "\033[21;5~": return TTkK.Key_F10 , TTkK.ControlModifier + elif key == "\033[23;5~": return TTkK.Key_F11 , TTkK.ControlModifier + elif key == "\033[24;5~": return TTkK.Key_F12 , TTkK.ControlModifier + # elif key == "\033[1;3P" : return TTkK.Key_F1 , TTkK.AltModifier + # elif key == "\033[1;3Q" : return TTkK.Key_F2 , TTkK.AltModifier + elif key == "\033[1;3R" : return TTkK.Key_F3 , TTkK.AltModifier + #elif key == "\033[1;3S" : return TTkK.Key_F4 , TTkK.AltModifier + #elif key == "\033[15;3~": return TTkK.Key_F5 , TTkK.AltModifier + elif key == "\033[17;3~": return TTkK.Key_F6 , TTkK.AltModifier + #elif key == "\033[18;3~": return TTkK.Key_F7 , TTkK.AltModifier + #elif key == "\033[19;3~": return TTkK.Key_F8 , TTkK.AltModifier + elif key == "\033[20;3~": return TTkK.Key_F9 , TTkK.AltModifier + #elif key == "\033[21;3~": return TTkK.Key_F10 , TTkK.AltModifier + elif key == "\033[23;3~": return TTkK.Key_F11 , TTkK.AltModifier + elif key == "\033[24;3~": return TTkK.Key_F12 , TTkK.AltModifier + return None, None # # elif key == "\033": return TTkK.Key_Tab # if True: return None @@ -130,6 +169,16 @@ def _translate_key(key): # elif key == "\033": return TTkK.Key_Any # return TTkK.NONE +def mod2str(k): + if k == TTkK.NoModifier : return "NoModifier" + if k == TTkK.ShiftModifier : return "ShiftModifier" + if k == TTkK.ControlModifier : return "ControlModifier" + if k == TTkK.AltModifier : return "AltModifier" + if k == TTkK.MetaModifier : return "MetaModifier" + if k == TTkK.KeypadModifier : return "KeypadModifier" + if k == TTkK.GroupSwitchModifier : return "GroupSwitchModifier" + return "NONE!!!" + def key2str(k): if k == TTkK.Key_Escape : return "Key_Escape" if k == TTkK.Key_Tab : return "Key_Tab" diff --git a/docs/TODO.md b/docs/TODO.md index 631d6ac4..5faabe1f 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -10,7 +10,6 @@ https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block) - [ ] Support Hyperlink: (gnome-terminal) https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda - - [x] Process child events before parent ## Terminal Helper @@ -25,6 +24,10 @@ - [x] Handle Special Keys (UP, Down, . . .) - [ ] Handle CTRL-Mouse - [ ] Handle CTRL,ALT,SHIFT + Key (Tab, UP, Down, . . .) + - [x] Handle SHIFT + Tab + - [x] Handle Tab Focus + - [x] Handle CTRL,ALT,SHIFT + (F1 -> F12) + - [ ] Handle CTRL,ALT,SHIFT + (Up, Down, Left Right) - [ ] Events https://tkinterexamples.com/events/events.html https://www.pythontutorial.net/tkinter/tkinter-event-binding/ @@ -110,7 +113,7 @@ - [x] Themes #### Splitter widget - [x] Basic Implementation - - [ ] Snap on min/max sizes + - [x] Snap on min/max sizes - [ ] Events (Signal/Slots) - [x] Themes - [ ] Support addItem @@ -123,7 +126,7 @@ - [ ] Keyboard events #### Spin Box - [x] Basic Implementation - - [ ] Events (Signal/Slots) + - [x] Events (Signal/Slots) - [ ] Themes #### Progress Bar - [ ] Basic Implementation @@ -139,7 +142,7 @@ - [x] Themes ### Pickers -#### Color Picker +#### Color Picker~/github/Varie/pyTermTk~/github/Varie/pyTermTk - [x] Basic Implementation - [x] Events (Signal/Slots) - [ ] Themes