Browse Source

Merge branch 'TTkTerminal' into workbench

pull/162/head
Eugenio Parodi 2 years ago
parent
commit
2c923ab2d5
  1. 11
      README.md
  2. 8
      TermTk/TTkCore/TTkTerm/__init__.py
  3. 142
      TermTk/TTkCore/TTkTerm/input.py
  4. 7
      TermTk/TTkCore/TTkTerm/term.py
  5. 38
      TermTk/TTkCore/TTkTerm/term_base.py
  6. 11
      TermTk/TTkCore/canvas.py
  7. 15
      TermTk/TTkCore/constant.py
  8. 15
      TermTk/TTkCore/drivers/__init__.py
  9. 44
      TermTk/TTkCore/drivers/pyodide.py
  10. 4
      TermTk/TTkCore/drivers/term_pyodide.py
  11. 4
      TermTk/TTkCore/drivers/term_unix.py
  12. 74
      TermTk/TTkCore/drivers/term_windows.py
  13. 28
      TermTk/TTkCore/drivers/unix.py
  14. 0
      TermTk/TTkCore/drivers/unix_thread.py
  15. 368
      TermTk/TTkCore/drivers/windows.py
  16. 4
      TermTk/TTkCore/helper.py
  17. 11
      TermTk/TTkCore/string.py
  18. 49
      TermTk/TTkCore/ttk.py
  19. 2
      TermTk/TTkGui/textcursor.py
  20. 3
      TermTk/TTkTestWidgets/keypressview.py
  21. 33
      TermTk/TTkUiTools/properties/__init__.py
  22. 46
      TermTk/TTkUiTools/properties/list_.py
  23. 8
      TermTk/TTkUiTools/properties/texedit.py
  24. 37
      TermTk/TTkUiTools/uiproperties.py
  25. 2
      TermTk/TTkWidgets/TTkModelView/filetreewidgetitem.py
  26. 2
      TermTk/TTkWidgets/TTkModelView/treewidgetitem.py
  27. 2
      TermTk/TTkWidgets/TTkPickers/filepicker.py
  28. 2
      TermTk/TTkWidgets/TTkTerminal/terminal.py
  29. 123
      TermTk/TTkWidgets/TTkTerminal/terminal_screen.py
  30. 10
      TermTk/TTkWidgets/TTkTerminal/terminal_screen_CSI.py
  31. 77
      TermTk/TTkWidgets/TTkTerminal/terminalview.py
  32. 10
      TermTk/TTkWidgets/button.py
  33. 4
      TermTk/TTkWidgets/kodetab.py
  34. 38
      TermTk/TTkWidgets/lineedit.py
  35. 15
      TermTk/TTkWidgets/list_.py
  36. 172
      TermTk/TTkWidgets/listwidget.py
  37. 18
      TermTk/TTkWidgets/tabwidget.py
  38. 36
      TermTk/TTkWidgets/texedit.py
  39. 3
      TermTk/TTkWidgets/widget.py
  40. 13
      demo/showcase/_showcasehelper.py
  41. 4
      demo/showcase/formwidgets02.py
  42. 78
      demo/showcase/list.py
  43. 15
      demo/showcase/textedit.py
  44. 12
      docs/MDNotes/Resources.md
  45. 36
      docs/MDNotes/msWindows/Init Sequence.md
  46. 31
      docs/MDNotes/msWindows/Resources.md
  47. 2
      setup.ttkDesigner.py
  48. 4
      tests/pytest/conftest.py
  49. 20
      tests/pytest/mock_input.py
  50. 2
      tests/pytest/mock_term.py
  51. 4
      tests/sandbox/sandbox.html
  52. 68
      tests/test.input.curses.py
  53. 17
      tests/test.input.py
  54. 16
      tests/test.input.raw.py
  55. 393
      tests/test.input.win.01.py
  56. 436
      tests/test.input.win.02.py
  57. 89
      tests/test.pty.006.terminal.04.py
  58. 73
      tests/test.pty.006.terminal.05.py
  59. 0
      tests/test.ui.014.list.01.py
  60. 112
      tests/test.ui.014.list.03.py
  61. 112
      tests/test.ui.014.list.04.py
  62. 68
      tests/timeit/21.weakref.01.py
  63. 84
      tests/timeit/22.queue.01.py
  64. 0
      tests/weakref/test.01.py
  65. 0
      tests/weakref/test.02.py
  66. 0
      tests/weakref/test.03.py
  67. 116
      tests/weakref/test.04.gc.01.py
  68. 88
      tests/weakref/test.04.gc.02.py
  69. 105
      tests/weakref/test.04.gc.03.py
  70. 92
      tests/weakref/test.05.TermTk.01.py
  71. 105
      tests/weakref/test.05.TermTk.02.py
  72. 95
      tests/weakref/test.05.TermTk.03.signals.py
  73. 37
      tools/check.import.sh
  74. 2
      ttkDesigner/app/propertyeditor.py
  75. 4
      ttkDesigner/app/superobj/__init__.py
  76. 5
      ttkDesigner/app/superobj/superwidget.py
  77. 44
      ttkDesigner/app/superobj/superwidgetabstractscrollarea.py
  78. 29
      ttkDesigner/app/superobj/superwidgetlist.py
  79. 3
      ttkDesigner/app/widgetbox.py
  80. 41
      tutorial/examples/TTkWidget/Focus.01.py

11
README.md

@ -19,14 +19,16 @@ and inspired by a mix of [Qt5](https://www.riverbankcomputing.com/static/Docs/Py
[pyTermTk.Showcase.002.webm](https://user-images.githubusercontent.com/8876552/206490679-2bbdc909-c9bc-41c1-9a50-339b06dabecd.webm)
## Features
- Self Contained (no external lib required)
- Cross compatible: [Linux](https://en.wikipedia.org/wiki/Linux)🐧, [MacOS](https://en.wikipedia.org/wiki/MacOS)🍎, [MS Windows](https://en.wikipedia.org/wiki/Microsoft_Windows)🪟, [HTML5](https://en.wikipedia.org/wiki/HTML5)🌍([Try](https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html))
- Basic widgets for [TUI](https://en.wikipedia.org/wiki/Text-based_user_interface) development (Button, Label, checkbox, ...)
- Specialized widgets to improve the usability (Windows, Frames, Tables, ...)
- QT Like Layout system to help arrange the widgets in the terminal
- True color support
- Ful/Half/Zero sized Unicode characters 😎
- I am pretty sure there is something else...
## Limitations
- The native **Windows** porting is not ready yet but it works with [Cygwin](https://www.cygwin.com) or **WSL**.
- Only the key combinations forwarded by the terminal emulator used are detected (ALT,CTRL may not be handled)
---
@ -70,13 +72,14 @@ python3 tests/test.input.py
#### Demos
```bash
# Press CTRL-C to exit
# the logs are written to "session.log"
# add "-f" option to run it in "fullscreen" :-D
# Press CTRL-C to exit (CTRL-Break on Windows)
# Showcase Demo
python3 demo/demo.py -f
# run the ttkDesigner
python3 -m ttkDesigner
# Paint demo
python3 demo/paint.py

8
TermTk/TTkCore/TTkTerm/__init__.py

@ -1,5 +1,5 @@
from .inputkey import *
from .inputmouse import *
from .colors import *
# from .inputkey import *
# from .inputmouse import *
# from .colors import *
# from .input import *
from .term import *
from .input import *

142
TermTk/TTkCore/TTkTerm/input.py

@ -27,72 +27,72 @@ from time import time
import platform
if platform.system() == 'Linux':
from .readinputlinux import ReadInput
# from .readinputlinux_thread import ReadInput
elif platform.system() == 'Darwin':
from .readinputlinux import ReadInput
elif platform.system() == 'Windows':
raise NotImplementedError('Windows OS not yet supported')
from ..drivers import TTkInputDriver
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.signal import pyTTkSignal
from TermTk.TTkCore.TTkTerm.term import TTkTerm
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
class TTkInput:
__slots__ = (
'_readInput',
'_leftLastTime', '_midLastTime', '_rightLastTime',
'_leftTap', '_midTap', '_rightTap',
'_pasteBuffer', '_bracketedPaste',
# Signals
'inputEvent', 'pasteEvent'
)
def __init__(self):
self.inputEvent = pyTTkSignal(TTkKeyEvent, TTkMouseEvent)
self.pasteEvent = pyTTkSignal(str)
self._pasteBuffer = ""
self._bracketedPaste = False
self._readInput = None
self._leftLastTime = 0
self._midLastTime = 0
self._rightLastTime = 0
self._leftTap = 0
self._midTap = 0
self._rightTap = 0
def close(self):
if self._readInput:
self._readInput.close()
def stop(self):
inputEvent = pyTTkSignal(TTkKeyEvent, TTkMouseEvent)
pasteEvent = pyTTkSignal(str)
_pasteBuffer = ""
_bracketedPaste = False
_readInput = None
_leftLastTime = 0
_midLastTime = 0
_rightLastTime = 0
_leftTap = 0
_midTap = 0
_rightTap = 0
_mouse_re = re.compile(r"\033\[<(\d+);(\d+);(\d+)([mM])")
class Mouse(int):
ON = 0x01
DIRECT = 0x02
@staticmethod
def init(mouse:bool=False, directMouse:bool=False) -> None:
TTkInput._readInput = TTkInputDriver()
TTkTerm.setMouse(mouse, directMouse)
@staticmethod
def close() -> None:
TTkTerm.setMouse(False, False)
if TTkInput._readInput:
TTkInput._readInput.close()
@staticmethod
def stop() -> None:
pass
def cont(self):
if self._readInput:
self._readInput.cont()
@staticmethod
def cont() -> None:
if TTkInput._readInput:
TTkInput._readInput.cont()
def start(self):
self._readInput = ReadInput()
for stdinRead in self._readInput.read():
self.key_process(stdinRead)
@staticmethod
def start() -> None:
for stdinRead in TTkInput._readInput.read():
TTkInput.key_process(stdinRead)
TTkLog.debug("Close TTkInput")
mouse_re = re.compile(r"\033\[<(\d+);(\d+);(\d+)([mM])")
def key_process(self, stdinRead):
if self._bracketedPaste:
@staticmethod
def key_process(stdinRead:str) -> None:
if TTkInput._bracketedPaste:
if stdinRead.endswith("\033[201~"):
self._pasteBuffer += stdinRead[:-6]
self._bracketedPaste = False
TTkInput._pasteBuffer += stdinRead[:-6]
TTkInput._bracketedPaste = False
# due to the CRNL methos (don't ask me why) the terminal
# is substituting all the \n with \r
self.pasteEvent.emit(self._pasteBuffer.replace('\r','\n'))
self._pasteBuffer = ""
TTkInput.pasteEvent.emit(TTkInput._pasteBuffer.replace('\r','\n'))
TTkInput._pasteBuffer = ""
else:
self._pasteBuffer += stdinRead
TTkInput._pasteBuffer += stdinRead
return
mevt,kevt = None, None
@ -102,7 +102,7 @@ class TTkInput:
kevt = TTkKeyEvent.parse(stdinRead)
else:
# Mouse Event
m = self.mouse_re.match(stdinRead)
m = TTkInput._mouse_re.match(stdinRead)
if not m:
# TODO: Return Error
hex = [f"0x{ord(x):02x}" for x in stdinRead]
@ -134,18 +134,18 @@ class TTkInput:
mod |= TTkK.AltModifier
if code == 0x00:
self._leftLastTime, self._leftTap = _checkTap(self._leftLastTime, self._leftTap)
tap = self._leftTap
TTkInput._leftLastTime, TTkInput._leftTap = _checkTap(TTkInput._leftLastTime, TTkInput._leftTap)
tap = TTkInput._leftTap
key = TTkMouseEvent.LeftButton
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release
elif code == 0x01:
self._midLastTime, self._midTap = _checkTap(self._midLastTime, self._midTap)
tap = self._midTap
TTkInput._midLastTime, TTkInput._midTap = _checkTap(TTkInput._midLastTime, TTkInput._midTap)
tap = TTkInput._midTap
key = TTkMouseEvent.MidButton
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release
elif code == 0x02:
self._rightLastTime, self._rightTap = _checkTap(self._rightLastTime, self._rightTap)
tap = self._rightTap
TTkInput._rightLastTime, TTkInput._rightTap = _checkTap(TTkInput._rightLastTime, TTkInput._rightTap)
tap = TTkInput._rightTap
key = TTkMouseEvent.RightButton
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release
elif code == 0x20:
@ -171,37 +171,13 @@ class TTkInput:
mevt = TTkMouseEvent(x, y, key, evt, mod, tap, m.group(0).replace("\033", "<ESC>"))
if kevt or mevt:
self.inputEvent.emit(kevt, mevt)
TTkInput.inputEvent.emit(kevt, mevt)
return
if stdinRead.startswith("\033[200~"):
self._pasteBuffer = stdinRead[6:]
self._bracketedPaste = True
TTkInput._pasteBuffer = stdinRead[6:]
TTkInput._bracketedPaste = True
return
hex = [f"0x{ord(x):02x}" for x in stdinRead]
TTkLog.error("UNHANDLED: "+stdinRead.replace("\033","<ESC>") + " - "+",".join(hex))
def main():
print("Retrieve Keyboard, Mouse press/drag/wheel Events")
print("Press q or <ESC> to exit")
from term import TTkTerm
TTkTerm.push(TTkTerm.Mouse.ON)
TTkTerm.setEcho(False)
def callback(kevt=None, mevt=None):
if kevt is not None:
print(f"Key Event: {kevt}")
if mevt is not None:
print(f"Mouse Event: {mevt}")
testInput = TTkInput()
testInput.get_key(callback)
TTkTerm.push(TTkTerm.Mouse.OFF + TTkTerm.Mouse.DIRECT_OFF)
TTkTerm.setEcho(True)
if __name__ == "__main__":
main()

7
TermTk/TTkCore/TTkTerm/term.py

@ -22,9 +22,4 @@
__all__ = ['TTkTerm']
import importlib.util
if importlib.util.find_spec('pyodideProxy'):
from .term_pyodide import TTkTerm
else:
from .term_unix import TTkTerm
from ..drivers import *

38
TermTk/TTkCore/TTkTerm/term_base.py

@ -30,13 +30,13 @@ class TTkTermBase():
SET_BRACKETED_PM = "\033[?2004h" # Ps = 2 0 0 4 ⇒ Set bracketed paste mode, xterm.
RESET_BRACKETED_PM = "\033[?2004l" # Ps = 2 0 0 4 ⇒ Reset bracketed paste mode, xterm.
class Mouse():
class Mouse(str):
ON = "\033[?1002h\033[?1006h" # Enable reporting of mouse position on click and release
OFF = "\033[?1002l\033[?1006l" # Disable mouse reporting
DIRECT_ON = "\033[?1003h" # Enable reporting of mouse position at any movement
DIRECT_OFF = "\033[?1003l" # Disable direct mouse reporting
class Cursor():
class Cursor(str):
# from:
# https://superuser.com/questions/607478/how-do-you-change-the-xterm-cursor-to-an-i-beam-or-vertical-bar
# echo -e -n "\x1b[\x30 q" # changes to blinking block
@ -91,44 +91,48 @@ class TTkTermBase():
_sigWinChCb = None
@staticmethod
def init(mouse: bool = True, directMouse: bool = False, title: str = "TermTk", sigmask=0):
def init(title: str = "TermTk", sigmask=0) -> None:
TTkTermBase.title = title
TTkTermBase.mouse = mouse | directMouse
TTkTermBase.directMouse = directMouse
TTkTermBase.Cursor.hide()
TTkTermBase.push(TTkTermBase.escTitle(TTkTermBase.title))
TTkTermBase.push(TTkTermBase.ALT_SCREEN)
TTkTermBase.push(TTkTermBase.SET_BRACKETED_PM)
TTkTermBase.push(TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE)
if TTkTermBase.mouse:
TTkTermBase.push(TTkTermBase.Mouse.ON)
if TTkTermBase.directMouse:
TTkTermBase.push(TTkTermBase.Mouse.DIRECT_ON)
TTkTermBase.setEcho(False)
TTkTermBase.CRNL(False)
TTkTermBase.setSigmask(sigmask, False)
@staticmethod
def exit():
def setMouse(mouse:bool=False, directMouse:bool=False) -> None:
TTkTermBase.mouse = mouse | directMouse
TTkTermBase.directMouse = directMouse
if TTkTermBase.mouse:
TTkTermBase.push(TTkTermBase.Mouse.DIRECT_OFF)
TTkTermBase.push(TTkTermBase.Mouse.ON)
if TTkTermBase.directMouse:
TTkTermBase.push(TTkTermBase.Mouse.DIRECT_ON)
else:
TTkTermBase.push(TTkTermBase.Mouse.OFF)
TTkTermBase.push(TTkTermBase.Mouse.DIRECT_OFF)
@staticmethod
def exit() -> None:
TTkTermBase.push(TTkTermBase.Mouse.OFF + TTkTermBase.Mouse.DIRECT_OFF)
TTkTermBase.push(TTkTermBase.CLEAR + TTkTermBase.NORMAL_SCREEN + TTkTermBase.RESET_BRACKETED_PM + TTkTermBase.Cursor.SHOW + TTkTermBase.escTitle())
TTkTermBase.setEcho(True)
TTkTermBase.CRNL(True)
@staticmethod
def stop():
def stop() -> None:
TTkTermBase.push(TTkTermBase.Mouse.OFF + TTkTermBase.Mouse.DIRECT_OFF)
TTkTermBase.push(TTkTermBase.CLEAR + TTkTermBase.NORMAL_SCREEN + TTkTermBase.RESET_BRACKETED_PM + TTkTermBase.Cursor.SHOW + TTkTermBase.escTitle())
TTkTermBase.setEcho(True)
TTkTermBase.CRNL(True)
@staticmethod
def cont():
def cont() -> None:
TTkTermBase.push(TTkTermBase.ALT_SCREEN + TTkTermBase.SET_BRACKETED_PM + TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE + TTkTermBase.escTitle(TTkTermBase.title))
if TTkTermBase.mouse:
TTkTermBase.push(TTkTermBase.Mouse.ON)
if TTkTermBase.directMouse:
TTkTermBase.push(TTkTermBase.Mouse.DIRECT_ON)
TTkTermBase.setMouse(TTkTermBase.mouse, TTkTermBase.directMouse)
TTkTermBase.setEcho(False)
TTkTermBase.CRNL(False)
@ -148,5 +152,5 @@ class TTkTermBase():
flush = lambda *args: None
setEcho = lambda *args: None
CRNL = lambda *args: None
getTerminalSize = lambda *args: None
getTerminalSize = lambda *args: (80,24)
registerResizeCb = lambda *args: None

11
TermTk/TTkCore/canvas.py

@ -36,21 +36,18 @@ class TTkCanvas:
:param height: the height of the Canvas
'''
__slots__ = (
'_widget',
'_width', '_height', '_newWidth', '_newHeight',
'_theme',
'_data', '_colors',
'_bufferedData', '_bufferedColors',
'_visible', '_transparent', '_doubleBuffer')
def __init__(self, *args, **kwargs):
self._widget = kwargs.get('widget', None)
self._visible = True
self._transparent = False
self._doubleBuffer = False
self._width = 0
self._height = 0
self._data = [[0]]
self._colors = [[TTkColor.RST]]
self._data = [[]]
self._colors = [[]]
self._newWidth = kwargs.get('width', 0 )
self._newHeight = kwargs.get('height', 0 )
self.updateSize()
@ -63,8 +60,6 @@ class TTkCanvas:
def setTransparent(self, tr):
self._transparent = tr
def getWidget(self): return self._widget
def enableDoubleBuffer(self):
self._doubleBuffer = True
self._bufferedData, self._bufferedColors = self.copyBuffers()
@ -638,7 +633,7 @@ class TTkCanvas:
if bx+bw<0 or by+bh<0 or bx>=cw or by>=ch: return
if x+w<=bx or y+h<=by or bx+bw<=x or by+bh<=y: return
if (0,0,cw,ch)==geom==bound and (cw,ch)==canvas.size():
if (0,0,cw,ch)==geom==bound and (cw,ch)==canvas.size() and not canvas._transparent:
# fast Copy
# the canvas match exactly on top of the current one
for y in range(h):

15
TermTk/TTkCore/constant.py

@ -128,6 +128,21 @@ class TTkConstant:
# InsertAlphabetically = 0x06
# '''The string is inserted in the alphabetic order in the combobox.'''
class DragDropMode(int):
'''Specifies the Drag and Drop mode allowed by this widget'''
NoDragDrop = 0x00
'''No Drag and Drop is allowed'''
AllowDrag = 0x01
'''Drag allowed'''
AllowDrop = 0x02
'''Drop allowed'''
AllowDragDrop = 0x03
'''Drag and Drop allowed'''
NoDragDrop = DragDropMode.NoDragDrop
AllowDrag = DragDropMode.AllowDrag
AllowDrop = DragDropMode.AllowDrop
AllowDragDrop = DragDropMode.AllowDragDrop
class ChildIndicatorPolicy(int):
ShowIndicator = 0x00 #The controls for expanding and collapsing will be shown for this item even if there are no children.
DontShowIndicator = 0x01 #The controls for expanding and collapsing will never be shown even if there are children. If the node is forced open the user will not be able to expand or collapse the item.

15
TermTk/TTkCore/drivers/__init__.py

@ -0,0 +1,15 @@
import importlib.util
import platform
if importlib.util.find_spec('pyodideProxy'):
from .pyodide import *
from .term_pyodide import *
elif platform.system() == 'Linux':
from .unix import *
from .term_unix import *
elif platform.system() == 'Darwin':
from .unix import *
from .term_unix import *
elif platform.system() == 'Windows':
from .windows import *
from .term_windows import *

44
TermTk/TTkCore/drivers/pyodide.py

@ -0,0 +1,44 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['TTkSignalDriver','TTkInputDriver']
from pyodide import __version__ as pyodideVersion
from TermTk.TTkCore.signal import pyTTkSignal
from TermTk.TTkCore.log import TTkLog
class TTkInputDriver():
def close(self): pass
def cont(self): pass
def read(self): pass
class TTkSignalDriver():
sigStop = pyTTkSignal()
sigCont = pyTTkSignal()
sigInt = pyTTkSignal()
@staticmethod
def init():
TTkLog.info(f"Pyodide Version:\033[38;5;11m{pyodideVersion}")
def exit(): pass

4
TermTk/TTkCore/TTkTerm/term_pyodide.py → TermTk/TTkCore/drivers/term_pyodide.py

@ -20,9 +20,11 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['TTkTerm']
import pyodideProxy
from .term_base import TTkTermBase
from ..TTkTerm.term_base import TTkTermBase
class TTkTerm(TTkTermBase):
@staticmethod

4
TermTk/TTkCore/TTkTerm/term_unix.py → TermTk/TTkCore/drivers/term_unix.py

@ -20,6 +20,8 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['TTkTerm']
import sys, os, signal
from threading import Thread, Lock
@ -28,7 +30,7 @@ except Exception as e:
print(f'ERROR: {e}')
exit(1)
from .term_base import TTkTermBase
from ..TTkTerm.term_base import TTkTermBase
from TermTk.TTkCore.log import TTkLog
class TTkTerm(TTkTermBase):

74
TermTk/TTkCore/drivers/term_windows.py

@ -0,0 +1,74 @@
# MIT License
#
# Copyright (c) 2022 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['TTkTerm']
import sys, os
from threading import Thread, Lock
from ..TTkTerm.term_base import TTkTermBase
from TermTk.TTkCore.log import TTkLog
from .windows import *
class TTkTerm(TTkTermBase):
@staticmethod
def _push(*args):
try:
sys.stdout.write(str(*args))
sys.stdout.flush()
except BlockingIOError as e:
TTkLog.fatal(f"{e=} {e.characters_written=}")
except Exception as e:
TTkLog.fatal(e)
TTkTermBase.push = _push
@staticmethod
def _flush():
sys.stdout.flush()
TTkTermBase.flush = _flush
@staticmethod
def _getTerminalSize():
try:
return os.get_terminal_size()
except OSError as e:
print(f'ERROR: {e}')
TTkTermBase.getTerminalSize = _getTerminalSize
_sigWinChMutex = Lock()
@staticmethod
def _sigWinCh(w,h):
def _sigWinChThreaded():
if not TTkTerm._sigWinChMutex.acquire(blocking=False): return
while (TTkTerm.width, TTkTerm.height) != (wh:=TTkTerm.getTerminalSize()):
TTkTerm.width, TTkTerm.height = wh
if TTkTerm._sigWinChCb is not None:
TTkTerm._sigWinChCb(TTkTerm.width, TTkTerm.height)
TTkTerm._sigWinChMutex.release()
Thread(target=_sigWinChThreaded).start()
@staticmethod
def _registerResizeCb(callback):
TTkTerm._sigWinChCb = callback
TTkInputDriver.windowResized.connect(TTkTerm._sigWinCh)
TTkTermBase.registerResizeCb = _registerResizeCb

28
TermTk/TTkCore/TTkTerm/readinputlinux.py → TermTk/TTkCore/drivers/unix.py

@ -20,7 +20,10 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['TTkSignalDriver','TTkInputDriver']
import sys, os, re
import signal
from select import select
try: import fcntl, termios, tty
@ -28,8 +31,10 @@ except Exception as e:
print(f'ERROR: {e}')
exit(1)
from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot
class ReadInput():
class TTkInputDriver():
__slots__ = ('_readPipe','_attr')
def __init__(self):
@ -64,3 +69,24 @@ class ReadInput():
else:
for ch in sr:
yield ch
class TTkSignalDriver():
sigStop = pyTTkSignal()
sigCont = pyTTkSignal()
sigInt = pyTTkSignal()
@staticmethod
def init():
# Register events
signal.signal(signal.SIGTSTP, TTkSignalDriver._SIGSTOP) # Ctrl-Z
signal.signal(signal.SIGCONT, TTkSignalDriver._SIGCONT) # Resume
signal.signal(signal.SIGINT, TTkSignalDriver._SIGINT) # Ctrl-C
def exit():
signal.signal(signal.SIGINT, signal.SIG_DFL)
def _SIGSTOP(signum, frame): TTkSignalDriver.sigStop.emit()
def _SIGCONT(signum, frame): TTkSignalDriver.sigCont.emit()
def _SIGINT( signum, frame): TTkSignalDriver.sigInt.emit()

0
TermTk/TTkCore/TTkTerm/readinputlinux_thread.py → TermTk/TTkCore/drivers/unix_thread.py

368
TermTk/TTkCore/drivers/windows.py

@ -0,0 +1,368 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['TTkSignalDriver','TTkInputDriver']
import signal
from ctypes import Structure, Union, byref, wintypes, windll
from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot
from TermTk.TTkCore.log import TTkLog
# Based on the example ported from:
# https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events
# https://github.com/ceccopierangiolieugenio/pyTermTk -> tests/test.input.win.01.py
# https://learn.microsoft.com/en-us/windows/console/getstdhandle
STD_INPUT_HANDLE = wintypes.DWORD(-10) # The standard input device. Initially, this is the console input buffer, CONIN$.
STD_OUTPUT_HANDLE = wintypes.DWORD(-11) # The standard output device. Initially, this is the active console screen buffer, CONOUT$.
STD_ERROR_HANDLE = wintypes.DWORD(-12) # The standard error device. Initially, this is the active console screen buffer, CONOUT$.
INVALID_HANDLE_VALUE = -1 # WinBase.h
# https://learn.microsoft.com/en-us/windows/console/SetConsoleMode
ENABLE_ECHO_INPUT = 0x0004 # Characters read by the ReadFile or ReadConsole function are written to the active screen buffer as they are typed into the console. This mode can be used only if the ENABLE_LINE_INPUT mode is also enabled.
ENABLE_INSERT_MODE = 0x0020 # When enabled, text entered in a console window will be inserted at the current cursor location and all text following that location will not be overwritten. When disabled, all following text will be overwritten.
ENABLE_LINE_INPUT = 0x0002 # The ReadFile or ReadConsole function returns only when a carriage return character is read. If this mode is disabled, the functions return when one or more characters are available.
ENABLE_MOUSE_INPUT = 0x0010 # If the mouse pointer is within the borders of the console window and the window has the keyboard focus, mouse events generated by mouse movement and button presses are placed in the input buffer. These events are discarded by ReadFile or ReadConsole, even when this mode is enabled. The ReadConsoleInput function can be used to read MOUSE_EVENT input records from the input buffer.
ENABLE_PROCESSED_INPUT = 0x0001 # CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer is being read by ReadFile or ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and line feed characters are handled by the system.
ENABLE_QUICK_EDIT_MODE = 0x0040 # This flag enables the user to use the mouse to select and edit text. To enable this mode, use ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS. To disable this mode, use ENABLE_EXTENDED_FLAGS without this flag.
ENABLE_WINDOW_INPUT = 0x0008 # User interactions that change the size of the console screen buffer are reported in the console's input buffer. Information about these events can be read from the input buffer by applications using the ReadConsoleInput function, but not by those using ReadFile or ReadConsole.
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 # Setting this flag directs the Virtual Terminal processing engine to convert user input received by the console window into Console Virtual Terminal Sequences that can be retrieved by a supporting application through ReadFile or ReadConsole functions.
ENABLE_PROCESSED_OUTPUT = 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
# https://learn.microsoft.com/en-us/windows/console/input-record-str
FOCUS_EVENT = 0x0010 # The Event member contains a FOCUS_EVENT_RECORD structure. These events are used internally and should be ignored.
KEY_EVENT = 0x0001 # The Event member contains a KEY_EVENT_RECORD structure with information about a keyboard event.
MENU_EVENT = 0x0008 # The Event member contains a MENU_EVENT_RECORD structure. These events are used internally and should be ignored.
MOUSE_EVENT = 0x0002 # The Event member contains a MOUSE_EVENT_RECORD structure with information about a mouse movement or button press event.
WINDOW_BUFFER_SIZE_EVENT = 0x0004 # The Event member contains a WINDOW_BUFFER_SIZE_RECORD structure with information about the new size of the console screen buffer.
# https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str
# dwButtonState
FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001 # The leftmost mouse button.
FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004 # The second button fom the left.
FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008 # The third button from the left.
FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010 # The fourth button from the left.
RIGHTMOST_BUTTON_PRESSED = 0x0002 # The rightmost mouse button.
# dwControlKeyState
CAPSLOCK_ON = 0x0080 # The CAPS LOCK light is on.
ENHANCED_KEY = 0x0100 # The key is enhanced. See remarks.
LEFT_ALT_PRESSED = 0x0002 # The left ALT key is pressed.
LEFT_CTRL_PRESSED = 0x0008 # The left CTRL key is pressed.
NUMLOCK_ON = 0x0020 # The NUM LOCK light is on.
RIGHT_ALT_PRESSED = 0x0001 # The right ALT key is pressed.
RIGHT_CTRL_PRESSED = 0x0004 # The right CTRL key is pressed.
SCROLLLOCK_ON = 0x0040 # The SCROLL LOCK light is on.
SHIFT_PRESSED = 0x0010 # The SHIFT key is pressed.
# dwEventFlags
DOUBLE_CLICK = 0x0002 # The second click (button press) of a double-click occurred. The first click is returned as a regular button-press event.
MOUSE_HWHEELED = 0x0008 # The horizontal mouse wheel was moved.
# If the high word of the dwButtonState member contains a positive value, the wheel was rotated to the right. Otherwise, the wheel was rotated to the left.
MOUSE_MOVED = 0x0001 # A change in mouse position occurred.
MOUSE_WHEELED = 0x0004 # The vertical mouse wheel was moved.
# If the high word of the dwButtonState member contains a positive value, the wheel was rotated forward, away from the user. Otherwise, the wheel was rotated backward, toward the user.
# https://docs.microsoft.com/en-us/windows/console/coord-str
#
# typedef struct _COORD {
# SHORT X;
# SHORT Y;
# } COORD, *PCOORD;
class COORD(Structure):
_fields_ = [
("X", wintypes.SHORT),
("Y", wintypes.SHORT)]
# https://docs.microsoft.com/en-us/windows/console/key-event-record-str
#
# typedef struct _KEY_EVENT_RECORD {
# BOOL bKeyDown;
# WORD wRepeatCount;
# WORD wVirtualKeyCode;
# WORD wVirtualScanCode;
# union {
# WCHAR UnicodeChar;
# CHAR AsciiChar;
# } uChar;
# DWORD dwControlKeyState;
# } KEY_EVENT_RECORD;
class KEY_EVENT_RECORD(Structure):
class _uChar(Union):
_fields_ = [
("UnicodeChar", wintypes.WCHAR) ,
("AsciiChar" , wintypes.CHAR ) ]
_fields_ = [
("bKeyDown" , wintypes.BOOL ),
("wRepeatCount" , wintypes.WORD ),
("wVirtualKeyCode" , wintypes.WORD ),
("wVirtualScanCode" , wintypes.WORD ),
("uChar" , _uChar ),
("dwControlKeyState", wintypes.DWORD)]
# https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
#
# typedef struct _MOUSE_EVENT_RECORD {
# COORD dwMousePosition;
# DWORD dwButtonState;
# DWORD dwControlKeyState;
# DWORD dwEventFlags;
# } MOUSE_EVENT_RECORD;
class MOUSE_EVENT_RECORD(Structure):
_fields_ = [
("dwMousePosition" , COORD),
("dwButtonState" , wintypes.DWORD),
("dwControlKeyState", wintypes.DWORD),
("dwEventFlags" , wintypes.DWORD)]
# https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str
#
# typedef struct _WINDOW_BUFFER_SIZE_RECORD {
# COORD dwSize;
# } WINDOW_BUFFER_SIZE_RECORD;
class WINDOW_BUFFER_SIZE_RECORD(Structure):
_fields_ = [("dwSize", COORD)]
# https://docs.microsoft.com/en-us/windows/console/menu-event-record-str
#
# typedef struct _MENU_EVENT_RECORD {
# UINT dwCommandId;
# } MENU_EVENT_RECORD, *PMENU_EVENT_RECORD;
class MENU_EVENT_RECORD(Structure):
_fields_ = [("dwCommandId", wintypes.UINT)]
# https://docs.microsoft.com/en-us/windows/console/focus-event-record-str
#
# typedef struct _FOCUS_EVENT_RECORD {
# BOOL bSetFocus;
# } FOCUS_EVENT_RECORD;
class FOCUS_EVENT_RECORD(Structure):
_fields_ = [("bSetFocus", wintypes.BOOL)]
# https://docs.microsoft.com/en-us/windows/console/input-record-str
#
# typedef struct _INPUT_RECORD {
# WORD EventType;
# union {
# KEY_EVENT_RECORD KeyEvent;
# MOUSE_EVENT_RECORD MouseEvent;
# WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
# MENU_EVENT_RECORD MenuEvent;
# FOCUS_EVENT_RECORD FocusEvent;
# } Event;
# } INPUT_RECORD;
class INPUT_RECORD(Structure):
class _Event(Union):
_fields_ = [
("KeyEvent" , KEY_EVENT_RECORD ),
("MouseEvent" , MOUSE_EVENT_RECORD ),
("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
("MenuEvent" , MENU_EVENT_RECORD ),
("FocusEvent" , FOCUS_EVENT_RECORD )]
_fields_ = [
("EventType", wintypes.WORD),
("Event" , _Event )]
class TTkInputDriver():
windowResized = pyTTkSignal(int,int)
def __init__(self):
self._run = True
self._initTerminal()
def _initTerminal(self):
# Get the standard input handle.
# From:
# https://learn.microsoft.com/en-us/windows/console/getstdhandle
#
# HANDLE WINAPI GetStdHandle(
# _In_ DWORD nStdHandle
# );
GetStdHandle = windll.kernel32.GetStdHandle
GetStdHandle.argtypes = [wintypes.DWORD]
GetStdHandle.restype = wintypes.HANDLE
self._hStdIn = GetStdHandle(STD_INPUT_HANDLE)
if self._hStdIn == INVALID_HANDLE_VALUE:
raise Exception("GetStdHandle")
self._hStdOut = GetStdHandle(STD_OUTPUT_HANDLE)
if self._hStdOut == INVALID_HANDLE_VALUE:
raise Exception("GetStdHandle")
# Save the current input mode, to be restored on exit.
# From:
# https://learn.microsoft.com/en-us/windows/console/GetConsoleMode
#
# BOOL WINAPI GetConsoleMode(
# _In_ HANDLE hConsoleHandle,
# _Out_ LPDWORD lpMode
# );
self._GetConsoleMode = windll.kernel32.GetConsoleMode
self._GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]
self._GetConsoleMode.restype = wintypes.BOOL
self._fdwSaveOldModeIn = wintypes.DWORD()
if not self._GetConsoleMode(self._hStdIn, byref(self._fdwSaveOldModeIn)):
raise Exception("GetConsoleMode")
self._fdwSaveOldModeOut = wintypes.DWORD()
if not self._GetConsoleMode(self._hStdOut, byref(self._fdwSaveOldModeOut)):
raise Exception("GetConsoleMode")
# TTkLog.debug(f"{fdwSaveOldModeIn.value=:02x}")
# TTkLog.debug(f"{fdwSaveOldModeOut.value=:02x}")
# Enable the window and mouse input events.
# From:
# https://learn.microsoft.com/en-us/windows/console/SetConsoleMode
#
# BOOL WINAPI SetConsoleMode(
# _In_ HANDLE hConsoleHandle,
# _In_ DWORD dwMode
# );
self._SetConsoleMode = windll.kernel32.SetConsoleMode
self._SetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.DWORD]
self._SetConsoleMode.restype = wintypes.BOOL
fdwModeIn = ENABLE_VIRTUAL_TERMINAL_INPUT
# fdwModeIn = ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT
# fdwModeIn = 0x0218
if not self._SetConsoleMode(self._hStdIn, fdwModeIn):
raise Exception("SetConsoleMode")
fdwModeOut = self._fdwSaveOldModeOut.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING
# fdwModeIn = 0x0218
if not self._SetConsoleMode(self._hStdOut, fdwModeOut):
raise Exception("SetConsoleMode")
# TTkLog.debug(f"{fdwModeIn=:02x}")
# TTkLog.debug(f"{fdwModeOut=:02x}")
def close(self):
self._run = False
# Restore input mode on exit.
if not self._SetConsoleMode(self._hStdIn, self._fdwSaveOldModeIn):
raise Exception("SetConsoleMode")
if not self._SetConsoleMode(self._hStdOut, self._fdwSaveOldModeOut):
raise Exception("SetConsoleMode")
def cont(self):
pass
def read(self) -> str|None:
# From:
# https://learn.microsoft.com/en-us/windows/console/ReadConsoleInput
#
# BOOL WINAPI ReadConsoleInput(
# _In_ HANDLE hConsoleInput,
# _Out_ PINPUT_RECORD lpBuffer,
# _In_ DWORD nLength,
# _Out_ LPDWORD lpNumberOfEventsRead
# );
ReadConsoleInput = windll.kernel32.ReadConsoleInputW # Unicode
# ReadConsoleInput = windll.kernel32.ReadConsoleInputA # ANSII
# ReadConsoleInput.argtypes = [wintypes.HANDLE,
# wintypes.LPINT,
# wintypes.DWORD,
# wintypes.LPWORD]
ReadConsoleInput.restype = wintypes.BOOL
# DWORD cNumRead;
# INPUT_RECORD irInBuf[128];
cNumRead = wintypes.DWORD(0)
irInBuf = (INPUT_RECORD * 256)()
# Loop to read and handle the next 100 input events.
while self._run:
# Wait for the events.
if not ReadConsoleInput(
self._hStdIn, # input buffer handle
byref(irInBuf), # buffer to read into
256, # size of read buffer
byref(cNumRead)): # number of records read
raise Exception("ReadConsoleInput")
# TTkLog.debug(f"{self._hStdIn=} {irInBuf=} {cNumRead=}")
# TTkLog.debug(f"{cNumRead=}")
# Dispatch the events to the appropriate handler.
saveKeys = []
for bb in irInBuf[:cNumRead.value]:
# if not bb.EventType: continue
# TTkLog.debug(f"{bb=} {bb.EventType=} {cNumRead.value=}")
if bb.EventType == KEY_EVENT:
ke = bb.Event.KeyEvent
if ( not ke.bKeyDown or
ke.dwControlKeyState or
ke.wVirtualKeyCode ):
continue
saveKeys.append(ke.uChar.UnicodeChar)
elif bb.EventType == MOUSE_EVENT:
# It is not supposed to receive Mouse Events
# due to ENABLE_VIRTUAL_TERMINAL_PROCESSING
# everything is received as ANSI sequence
pass
elif bb.EventType == WINDOW_BUFFER_SIZE_EVENT:
# TTkLog.debug(f"{bb.Event.WindowBufferSizeEvent=}")
# TTkLog.debug(f"{bb.Event.WindowBufferSizeEvent.dwSize.X=}")
# TTkLog.debug(f"{bb.Event.WindowBufferSizeEvent.dwSize.Y=}")
TTkInputDriver.windowResized.emit(bb.Event.WindowBufferSizeEvent.dwSize.X, bb.Event.WindowBufferSizeEvent.dwSize.Y)
if saveKeys:
yield "".join(saveKeys).encode("utf-16", "surrogatepass").decode("utf-16")
class TTkSignalDriver():
sigStop = pyTTkSignal()
sigCont = pyTTkSignal()
sigInt = pyTTkSignal()
@staticmethod
def init():
# Register events
# signal.signal(signal.SIGTSTP, TTkSignalDriver._SIGSTOP) # Ctrl-Z
# signal.signal(signal.SIGCONT, TTkSignalDriver._SIGCONT) # Resume
signal.signal(signal.SIGINT, TTkSignalDriver._SIGINT) # Ctrl-C
def exit():
signal.signal(signal.SIGINT, signal.SIG_DFL)
def _SIGSTOP(signum, frame): TTkSignalDriver.sigStop.emit()
def _SIGCONT(signum, frame): TTkSignalDriver.sigCont.emit()
def _SIGINT( signum, frame): TTkSignalDriver.sigInt.emit()

4
TermTk/TTkCore/helper.py

@ -414,7 +414,7 @@ class TTkHelper:
if w == widget:
widget=None
continue
if w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus:
if w.isEnabled() and w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus:
w.setFocus()
w.update()
return
@ -436,7 +436,7 @@ class TTkHelper:
widget=None
if prev:
break
if w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus:
if w.isEnabled() and w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus:
prev = w
if prev:
prev.setFocus()

11
TermTk/TTkCore/string.py

@ -77,11 +77,12 @@ class TTkString():
@staticmethod
def _importString1(text, colors):
ret = TTkString()
ret._text = text
ret._colors = colors
ret._baseColor = colors[-1]
ret._hasTab = '\t' in text
ret._checkWidth()
if text and colors:
ret._text = text
ret._colors = colors
ret._baseColor = colors[-1] if colors else TTkColor.RST
ret._hasTab = '\t' in text
ret._checkWidth()
return ret
@staticmethod

49
TermTk/TTkCore/ttk.py

@ -29,6 +29,7 @@ import queue
import threading
import platform
from TermTk.TTkCore.drivers import *
from TermTk.TTkCore.TTkTerm.input import TTkInput
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
@ -40,19 +41,18 @@ from TermTk.TTkCore.cfg import TTkCfg, TTkGlbl
from TermTk.TTkCore.helper import TTkHelper
from TermTk.TTkCore.timer import TTkTimer
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkTheme.theme import TTkTheme
from TermTk.TTkWidgets.widget import TTkWidget
from TermTk.TTkWidgets.container import TTkContainer
class TTk(TTkContainer):
class _mouseCursor(TTkWidget):
__slots__ = ('_cursor','_color')
def __init__(self, input):
def __init__(self):
super().__init__(name='MouseCursor')
self._cursor = ''
self._color = TTkColor.RST
self.resize(1,1)
input.inputEvent.connect(self._mouseInput)
TTkInput.inputEvent.connect(self._mouseInput)
@pyTTkSlot(TTkKeyEvent, TTkMouseEvent)
def _mouseInput(self, _, mevt):
if mevt is not None:
@ -99,9 +99,11 @@ class TTk(TTkContainer):
super().__init__(*args, **kwargs)
self._termMouse = True
self._termDirectMouse = kwargs.get('mouseTrack',False)
self._input = TTkInput()
self._input.inputEvent.connect(self._processInput)
self._input.pasteEvent.connect(self._processPaste)
TTkInput.inputEvent.connect(self._processInput)
TTkInput.pasteEvent.connect(self._processPaste)
TTkSignalDriver.sigStop.connect(self._SIGSTOP)
TTkSignalDriver.sigCont.connect(self._SIGCONT)
TTkSignalDriver.sigInt.connect( self._SIGINT)
self._title = kwargs.get('title','TermTk')
self._sigmask = kwargs.get('sigmask', TTkK.NONE)
self._showMouseCursor = os.environ.get("TTK_MOUSE",kwargs.get('mouseCursor', False))
@ -145,11 +147,10 @@ class TTk(TTkContainer):
TTkLog.debug(f" Version: {TTkCfg.version}" )
TTkLog.debug( "" )
TTkLog.debug( "Starting Main Loop..." )
TTkLog.debug(f"screen = ({TTkTerm.getTerminalSize()})")
# Register events
signal.signal(signal.SIGTSTP, self._SIGSTOP) # Ctrl-Z
signal.signal(signal.SIGCONT, self._SIGCONT) # Resume
signal.signal(signal.SIGINT, self._SIGINT) # Ctrl-C
TTkSignalDriver.init()
TTkLog.debug("Signal Event Registered")
@ -162,28 +163,29 @@ class TTk(TTkContainer):
# Keep track of the multiTap to avoid the extra key release
self._lastMultiTap = False
TTkInput.init(
mouse=self._termMouse,
directMouse=self._termDirectMouse)
TTkTerm.init(
title=self._title,
sigmask=self._sigmask,
mouse=self._termMouse,
directMouse=self._termDirectMouse )
sigmask=self._sigmask)
if self._showMouseCursor:
TTkTerm.push(TTkTerm.Mouse.DIRECT_ON)
m = TTk._mouseCursor(self._input)
m = TTk._mouseCursor()
self.rootLayout().addWidget(m)
self._mainLoop()
finally:
if platform.system() != 'Emscripten':
signal.signal(signal.SIGINT, signal.SIG_DFL)
TTkSignalDriver.exit()
self.quit()
TTkTerm.exit()
def _mainLoop(self):
if platform.system() == 'Emscripten':
return
self._input.start()
TTkInput.start()
@pyTTkSlot(str)
def _processPaste(self, txt:str):
@ -325,28 +327,31 @@ class TTk(TTkContainer):
'''Tells the application to exit with a return code.'''
if self._timer:
self._timer.timeout.disconnect(self._time_event)
self._input.inputEvent.clear()
TTkInput.inputEvent.clear()
self._paintEvent.set()
self._input.close()
TTkInput.close()
def _SIGSTOP(self, signum, frame):
@pyTTkSlot()
def _SIGSTOP(self):
"""Reset terminal settings and stop background input read before putting to sleep"""
TTkLog.debug("Captured SIGSTOP <CTRL-z>")
TTkTerm.stop()
self._input.stop()
TTkInput.stop()
# TODO: stop the threads
os.kill(os.getpid(), signal.SIGSTOP)
def _SIGCONT(self, signum, frame):
@pyTTkSlot()
def _SIGCONT(self):
"""Set terminal settings and restart background input read"""
TTkLog.debug("Captured SIGCONT 'fg/bg'")
TTkTerm.cont()
self._input.cont()
TTkInput.cont()
TTkHelper.rePaintAll()
# TODO: Restart threads
# TODO: Redraw the screen
def _SIGINT(self, signum, fraTERMTK_STACKTRACEme):
@pyTTkSlot()
def _SIGINT(self):
# If the "TERMTK_STACKTRACE" env variable is defined
# a stacktrace file is generated once CTRL+C is pressed
# i.e.

2
TermTk/TTkGui/textcursor.py

@ -459,7 +459,7 @@ class TTkTextCursor():
splitAfter = self._document._dataLines[line].substring(fr=pos)
xFrom = pos
xTo = pos
selectRE = '[^ \t\r\n\(\)\[\]\.\,\+\-\*\/]*'
selectRE = r'[^ \t\r\n()[\]\.\,\+\-\*\/]*'
if m := splitBefore.search(selectRE+'$'):
xFrom -= len(m.group(0))
if m := splitAfter.search('^'+selectRE):

3
TermTk/TTkTestWidgets/keypressview.py

@ -22,6 +22,7 @@
__all__ = ['TTkKeyPressView']
from TermTk.TTkCore.TTkTerm.input import TTkInput
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent, mod2str, key2str
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
from TermTk.TTkCore.helper import TTkHelper
@ -38,7 +39,7 @@ class TTkKeyPressView(TTkWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
TTkHelper._rootWidget._input.inputEvent.connect(self._processInput)
TTkInput.inputEvent.connect(self._processInput)
self._keys = []
self._fadeDuration = 2.5
self._anim = TTkPropertyAnimation(self, '_pushFade')

33
TermTk/TTkUiTools/properties/__init__.py

@ -1,33 +0,0 @@
# from .about import
from .button import *
from .checkbox import *
from .combobox import *
from .container import *
from .frame import *
# from .graph import
# from .image import
from .label import *
from .lineedit import *
from .list_ import *
# from .listwidget import
# from .menubar import
from .menu import *
# from .progressbar import
from .radiobutton import *
from .resizableframe import *
# from .scrollarea import
from .scrollbar import *
# from .spacer import
from .spinbox import *
from .splitter import *
# from .tabwidget import
from .texedit import *
from .widget import *
from .window import *
# Pickers
from .colorpicker import *
from .filepicker import *
# Layouts
from .layout import *

46
TermTk/TTkUiTools/properties/list_.py

@ -22,4 +22,48 @@
__all__ = ['TTkListProperties']
TTkListProperties = {'properties' : {},'signals' : {},'slots' : {}}
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkWidgets.list_ import TTkList
from TermTk.TTkWidgets.listwidget import TTkListWidget, TTkAbstractListItem
TTkListProperties = {
'properties' : {
'Selection Mode' : {
'init': {'name':'selectionMode', 'type':'singleflag',
'flags':{
'Single Seelction' : TTkK.SingleSelection,
'Multi Selection' : TTkK.MultiSelection,
}},
'get': {'cb':lambda w: w.selectionMode(), 'type':'singleflag',
'flags':{
'Single Seelction' : TTkK.SingleSelection,
'Multi Selection' : TTkK.MultiSelection,
}},
'set': {'cb':lambda w,v: w.setSelectionMode(v), 'type':'singleflag',
'flags':{
'Single Seelction' : TTkK.SingleSelection,
'Multi Selection' : TTkK.MultiSelection,
}}},
'DnD Mode' : {
'init': {'name':'dragDropMode', 'type':'multiflags',
'flags':{
'Allow Drag' : TTkK.DragDropMode.AllowDrag,
'Allow Drop' : TTkK.DragDropMode.AllowDrop,
}},
'get': {'cb':lambda w: w.dragDropMode(), 'type':'multiflags',
'flags':{
'Allow Drag' : TTkK.DragDropMode.AllowDrag,
'Allow Drop' : TTkK.DragDropMode.AllowDrop,
}},
'set': {'cb':lambda w,v: w.setDragDropMode(v), 'type':'multiflags',
'flags':{
'Allow Drag' : TTkK.DragDropMode.AllowDrag,
'Allow Drop' : TTkK.DragDropMode.AllowDrop,
}}},
},
'signals' : {
'itemClicked(TTkAbstractListItem)' : {'name': 'itemClicked', 'type' : TTkAbstractListItem},
'textClicked(str)' : {'name': 'textClicked', 'type' : str},
},
'slots' : {}}

8
TermTk/TTkUiTools/properties/texedit.py

@ -33,6 +33,10 @@ TTkTextEditProperties = {
'init': {'name':'lineNumber', 'type':bool } ,
'get': {'cb':TTkTextEdit.getLineNumber, 'type':bool } ,
'set': {'cb':TTkTextEdit.setLineNumber, 'type':bool } },
'Line Number Starting': {
'init': {'name':'lineNumberStarting', 'type':int } ,
'get': {'cb':TTkTextEdit.lineNumberStarting, 'type':int } ,
'set': {'cb':TTkTextEdit.setLineNumberStarting, 'type':int } },
'Read Only' : {
'init': {'name':'readOnly', 'type':bool } ,
'get': {'cb':lambda w: w.isReadOnly(), 'type':bool } ,
@ -47,7 +51,9 @@ TTkTextEditProperties = {
'textChanged()' : {'name': 'textChanged', 'type': None},
},'slots' : {
'setText(str)' : {'name':'setText', 'type':None},
'setColor(TTkColor)' : {'name':'setColor', 'type':TTkColor},
'setColor(TTkColor)' : {'name':'setColor', 'type':TTkColor},
'setLineNumber(bool)' : {'name':'setLineNumber', 'type':bool},
'setLineNumberStarting(int)' : {'name':'setLineNumberStarting', 'type':int},
'append(str)' : {'name':'append', 'type':None},
'undo()' : {'name':'undo', 'type':None},
'redo()' : {'name':'redo', 'type':None},

37
TermTk/TTkUiTools/uiproperties.py

@ -24,7 +24,40 @@ __all__ = ['TTkUiProperties']
from TermTk.TTkLayouts import *
from TermTk.TTkWidgets import *
from .properties import *
# from .properties.about import
from .properties.button import *
from .properties.checkbox import *
from .properties.combobox import *
from .properties.container import *
from .properties.frame import *
# from .properties.graph import
# from .properties.image import
from .properties.label import *
from .properties.lineedit import *
from .properties.list_ import *
# from .properties.listwidget import
# from .properties.menubar import
from .properties.menu import *
# from .properties.progressbar import
from .properties.radiobutton import *
from .properties.resizableframe import *
# from .properties.scrollarea import
from .properties.scrollbar import *
# from .properties.spacer import
from .properties.spinbox import *
from .properties.splitter import *
# from .properties.tabwidget import
from .properties.texedit import *
from .properties.widget import *
from .properties.window import *
# Pickers
from .properties.colorpicker import *
from .properties.filepicker import *
# Layouts
from .properties.layout import *
TTkUiProperties = {
# Widgets
@ -45,7 +78,7 @@ TTkUiProperties = {
TTkTextEdit.__name__: TTkTextEditProperties,
TTkWidget.__name__: TTkWidgetProperties,
TTkWindow.__name__: TTkWindowProperties,
# Pickers
# Pickers
TTkColorButtonPicker.__name__ : TTkColorButtonPickerProperties,
TTkFileButtonPicker.__name__ : TTkFileButtonPickerProperties,
# Layouts

2
TermTk/TTkWidgets/TTkModelView/filetreewidgetitem.py

@ -49,7 +49,7 @@ class TTkFileTreeWidgetItem(TTkTreeWidgetItem):
def _processFilter(self, filter):
if self.getType() == TTkFileTreeWidgetItem.FILE:
filterRe = "^"+filter.replace('.','\.').replace('*','.*')+"$"
filterRe = "^"+filter.replace('.',r'\.').replace('*','.*')+"$"
if re.match(filterRe, self._raw[0]):
self.setHidden(False)
else:

2
TermTk/TTkWidgets/TTkModelView/treewidgetitem.py

@ -156,7 +156,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
def setTreeItemParent(self, parent):
if parent:
widgets = self._setTreeItemParent(parent)
parent.rootLayout().addWidgets(widgets)
parent.layout().addWidgets(widgets)
else:
# pw = self._parentWidget
widgets = self._clearTreeItemParent()

2
TermTk/TTkWidgets/TTkPickers/filepicker.py

@ -362,7 +362,7 @@ class TTkFileDialogPicker(TTkWindow):
path, e = os.path.split(path)
if e:
ret.append(path)
if not path or path=='/':
if not path or path=='/' or path[1:]==":\\":
break
return ret
class TTkFileDialog:

2
TermTk/TTkWidgets/TTkTerminal/terminal.py

@ -63,7 +63,7 @@ class TTkTerminal(TTkAbstractScrollArea):
self.titleChanged = self._terminalView.titleChanged
self.bell = self._terminalView.bell
self.terminalClosed = pyTTkSignal(TTkTerminal)
self._terminalView.closed.connect(lambda : self.terminalClosed.emit(self))
self._terminalView.terminalClosed.connect(lambda : self.terminalClosed.emit(self))
def close(self):
self._terminalView.close()

123
TermTk/TTkWidgets/TTkTerminal/terminal_screen.py

@ -24,6 +24,7 @@ __all__ = ['']
import collections
import unicodedata
from dataclasses import dataclass
from TermTk.TTkCore.canvas import TTkCanvas
@ -47,7 +48,49 @@ from .terminal_screen_CSI import _TTkTerminalScreen_CSI
from .terminal_screen_C1 import _TTkTerminalScreen_C1
class _TTkTerminalScreen(_TTkTerminalScreen_CSI, _TTkTerminalScreen_C1):
@dataclass(frozen=False)
class _SelectCursor:
@dataclass(frozen=False)
class _CP:
line: int = 0
pos: int = 0
def setVal(self,x,y):
self.pos=x
self.line=y
def clear(self):
self.line = 0
self.pos = 0
def toNum(self):
return self.pos | self.line << 16
anchor: _CP = _CP()
position: _CP = _CP()
def __str__(self) -> str:
return f"a:({self.anchor.pos},{self.anchor.line}) p:({self.position.pos},{self.position.line})"
def select(self, x, y, moveAnchor=True):
x=max(0,x)
y=max(0,y)
self.position.setVal(x,y)
if moveAnchor:
self.anchor.setVal(x,y)
def selectionStart(self):
if self.position.toNum() > self.anchor.toNum():
return self.anchor
else:
return self.position
def selectionEnd(self):
if self.position.toNum() >= self.anchor.toNum():
return self.position
else:
return self.anchor
def hasSelection(self):
return self.position!=self.anchor
def clear(self):
self.anchor.clear()
self.position.clear()
__slots__ = ('_lines', '_terminalCursor',
'_selectCursor',
'_scrollingRegion',
'_bufferSize', '_bufferedLines',
'_w', '_h', '_color', '_canvas',
@ -61,13 +104,14 @@ class _TTkTerminalScreen(_TTkTerminalScreen_CSI, _TTkTerminalScreen_C1):
self.bufferedLinesChanged = pyTTkSignal()
self._w = w
self._h = h
self._canvasNewLine = [True]*h
self._canvasNewLine = [False]*h
self._canvasLineSize = [0]*h
self._last = None
self._bufferSize = bufferSize
self._bufferedLines = collections.deque(maxlen=bufferSize)
self._terminalCursor = (0,0)
self._scrollingRegion = (0,h)
self._selectCursor = _TTkTerminalScreen._SelectCursor()
self._color = color
self._canvas = TTkCanvas(width=w, height=h)
@ -83,8 +127,8 @@ class _TTkTerminalScreen(_TTkTerminalScreen_CSI, _TTkTerminalScreen_C1):
def resize(self, w, h):
# I normalize the size to the default terminal
# to avoid negative or zerosized term
w = max(80,w)
h = max(24,h)
w = max(3,w)
h = max(1,h)
ow, oh = self._w, self._h
# st,sb = self._scrollingRegion
# if oh <= h: # Terminal height decreasing
@ -94,13 +138,14 @@ class _TTkTerminalScreen(_TTkTerminalScreen_CSI, _TTkTerminalScreen_C1):
# self._scrollingRegion = (st,sb)
self._scrollingRegion = (0,h)
if w==ow and h==oh: return
self._selectCursor.clear()
self._w, self._h = w, h
newCanvas = TTkCanvas(width=w, height=h)
s = (0,0,w,h)
newCanvas.paintCanvas(self._canvas,s,s,s)
self._canvas = newCanvas
self._canvasNewLine += [True]*h
self._canvasNewLine += [False]*h
self._canvasLineSize += [0]*h
self._canvasNewLine = self._canvasNewLine[:h]
self._canvasLineSize = self._canvasLineSize[:h]
@ -128,13 +173,13 @@ class _TTkTerminalScreen(_TTkTerminalScreen_CSI, _TTkTerminalScreen_C1):
l = TTkString._getWidthText(ch)
# Scroll up if we are at the right border
if l+x > w:
self._canvasNewLine[y] = False
x=0
y+=1
if y >= sb:
self._CSI_S_SU(y-sb+1, None) # scroll up
y=sb-1
self._terminalCursor = (x,y)
self._canvasNewLine[y] = True
if l==1: # push normal char
if irm:
self._canvas._data[y][x:x] = [ch]
@ -176,6 +221,8 @@ class _TTkTerminalScreen(_TTkTerminalScreen_CSI, _TTkTerminalScreen_C1):
w,h = self._w, self._h
st,sb = self._scrollingRegion
self._selectCursor.clear()
lines = line.split('\n')
for i,l in enumerate(lines):
if i:
@ -198,10 +245,72 @@ class _TTkTerminalScreen(_TTkTerminalScreen_CSI, _TTkTerminalScreen_C1):
self._terminalCursor = (x,y)
self._pushTxt(lll,irm)
def select(self, x, y, moveAnchor=True):
# line = getLineFromX(x)
# pos = getPosFromX(linne,x)
# Convert x/y in line/pos
self._selectCursor.select(x,y,moveAnchor)
def getSelected(self):
if not self._selectCursor.hasSelection():
return ""
ret = []
st = self._selectCursor.selectionStart()
en = self._selectCursor.selectionEnd()
lbl = len(self._bufferedLines)
for i in range(min(st.line,lbl),min(en.line,lbl)):
line = self._bufferedLines[i]
pa = 0 if st.line < i else st.pos
pb = len(line) if en.line > i else en.pos
ret.append(line.substring(fr=pa, to=pb))
w,h = self._w, self._h
for y in range(max(0,min(st.line-lbl,h)),max(0,min(en.line-lbl+1,h))):
nl = self._canvasNewLine[y]
ls = self._canvasLineSize[y]
yyy = y+lbl
pa = 0 if st.line < yyy else st.pos
pb = ls if en.line > yyy else min(ls,en.pos)
data = self._canvas._data[y][pa:pb]
colors = self._canvas._colors[y][pa:pb]
line = TTkString._importString1("".join(data),colors)
if nl and ret:
ret[-1] += line
else:
ret.append(line)
return TTkString('\n').join(ret)
def paintEvent(self, canvas: TTkCanvas, w:int, h:int, ox:int=0, oy:int=0) -> None:
w,h = self._w, self._h
st = self._selectCursor.selectionStart()
en = self._selectCursor.selectionEnd()
# draw Buffered lines
ll = len(self._bufferedLines)
for y in range(ll-oy):
canvas.drawTTkString(pos=(0,y),text=self._bufferedLines[oy+y])
color=TTkColor.fg("#ffffff")+TTkColor.bg("#008888")
for y in range(min(h,ll-oy)):
line = self._bufferedLines[oy+y]
if st.line <= (yyy:=(y+oy)) <= en.line:
pa = 0 if st.line < yyy else st.pos
pb = len(line) if en.line > yyy else en.pos
canvas.drawTTkString(pos=(0,y),text=line.setColor(posFrom=pa, posTo=pb,color=color))
else:
canvas.drawTTkString(pos=(0,y),text=line)
# draw the Canvas
s = (-ox,ll-oy,w,h)
canvas.paintCanvas(self._canvas,s,s,s)
# canvas.drawText(pos=(0,0),text=f"({self._selectCursor})")
color=TTkColor.fg("#ffffff")+TTkColor.bg("#008844")
for y in range(max(st.line-oy,ll-oy),min(en.line-oy+1,h)):
did = y+oy-ll
data = self._canvas._data[did]
# colors = self._canvas._colors[did]
# nl = self._canvasNewLine[did]
ls = self._canvasLineSize[did]
yyy = y+oy
pa = 0 if st.line < yyy else st.pos
pb = ls if en.line > yyy else min(ls,en.pos)
canvas.drawText(pos=(pa,y), text="".join(data[pa:pb]), color=color)

10
TermTk/TTkWidgets/TTkTerminal/terminal_screen_CSI.py

@ -247,9 +247,13 @@ class _TTkTerminalScreen_CSI():
centerCNL[:ps],
centerCLS[:ps]):
if sz:
self._bufferedLines.append(TTkString._importString1(''.join(d[:sz]),c[:sz]))
txt = TTkString._importString1(''.join(d[:sz]),c[:sz])
else:
self._bufferedLines.append(TTkString())
txt = TTkString()
if nl:
self._bufferedLines[-1] += txt
else:
self._bufferedLines.append(txt)
# from TermTk.TTkCore.log import TTkLog
# TTkLog.debug(str(self._bufferedLines[-1])+f" - {sz=} {t=} {ps=} {self._canvasLineSize=}")
# Rotate the center part
@ -257,7 +261,7 @@ class _TTkTerminalScreen_CSI():
centerc = centerc[ps:] + [baseColors.copy() for _ in range(ps)]
centerd = centerd[:b-t]
centerc = centerc[:b-t]
centerCNL = centerCNL[ps:] + [True]*ps
centerCNL = centerCNL[ps:] + [False]*ps
centerCLS = centerCLS[ps:] + [0]*ps
centerCNL = centerCNL[:b-t]
centerCLS = centerCLS[:b-t]

77
TermTk/TTkWidgets/TTkTerminal/terminalview.py

@ -89,18 +89,19 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC):
reportMove: bool = False
sgrMode: bool = False
__slots__ = ('_shell', '_fd', '_inout', '_pid',
__slots__ = ('_selecct',
'_shell', '_fd', '_inout', '_pid',
'_quit_pipe', '_resize_pipe',
'_mode_normal'
'_clipboard',
'_clipboard', '_selecting',
'_buffer_lines', '_buffer_screen',
'_keyboard', '_mouse', '_terminal',
'_screen_current', '_screen_normal', '_screen_alt',
# Signals
'titleChanged', 'bell', 'closed')
'titleChanged', 'bell', 'terminalClosed')
def __init__(self, *args, **kwargs):
self.bell = pyTTkSignal()
self.closed = pyTTkSignal()
self.terminalClosed = pyTTkSignal()
self.titleChanged = pyTTkSignal(str)
self._shell = os.environ.get('SHELL', 'sh')
@ -119,6 +120,7 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC):
self._screen_alt = _TTkTerminalScreen()
self._screen_current = self._screen_normal
self._clipboard = TTkClipboard()
self._selecting = False
# self._screen_normal.bell.connect(lambda : _termLog.debug("BELL!!! 🔔🔔🔔"))
# self._screen_alt.bell.connect( lambda : _termLog.debug("BELL!!! 🔔🔔🔔"))
@ -175,8 +177,8 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC):
def resizeEvent(self, w: int, h: int):
if ( self._resize_pipe and
self._screen_current._w != w and
self._screen_current._h != h ):
( self._screen_current._w != w or
self._screen_current._h != h ) ):
os.write(self._resize_pipe[1], b'resize')
# self._screen_alt.resize(w,h)
@ -193,9 +195,9 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC):
if self._pid == 0:
def _spawnTerminal(argv=[self._shell], env=os.environ):
os.execvpe(argv[0], argv, env)
threading.Thread(target=_spawnTerminal).start()
# threading.Thread(target=_spawnTerminal).start()
TTkHelper.quit()
# _spawnTerminal()
_spawnTerminal()
import sys
sys.exit()
# os.execvpe(argv[0], argv, env)
@ -245,17 +247,28 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC):
@pyTTkSlot()
def _quit(self):
os.kill(self._pid,0)
if self._pid:
os.kill(self._pid,0)
os.kill(self._pid,15)
if self._quit_pipe:
os.write(self._quit_pipe[1], b'quit')
try:
os.write(self._quit_pipe[1], b'quit')
except:
pass
def _inputGenerator(self):
while rs := select( [self._inout,self._quit_pipe[0],self._resize_pipe[0]], [], [])[0]:
if self._quit_pipe[0] in rs:
# os.close(self._quit_pipe[0])
os.close(self._quit_pipe[1])
# os.close(self._resize_pipe[0])
os.close(self._resize_pipe[1])
os.close(self._fd)
return
if self._resize_pipe[0] in rs:
self._resizeScreen()
os.read(self._resize_pipe[0],100)
if self._inout not in rs:
continue
@ -274,7 +287,7 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC):
fcntl.fcntl(self._inout, fcntl.F_SETFL, _fl)
except Exception as e:
_termLog.error(f"Error: {e=}")
self.closed.emit()
self.terminalClosed.emit()
return
# out = out.decode('utf-8','ignore')
@ -952,10 +965,44 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC):
self._inout.write(bah)
return True
def mousePressEvent(self, evt): return self._sendMouse(evt) | True
def mouseReleaseEvent(self, evt): return self._sendMouse(evt)
def mouseDragEvent(self, evt): return self._sendMouse(evt)
def wheelEvent(self, evt): return True if self._sendMouse(evt) else super().wheelEvent(evt)
def mousePressEvent(self, evt):
if self._mouse.reportPress:
self._screen_current.select(0,0)
return self._sendMouse(evt) | True
self._selecting = True
x,y = evt.x,evt.y
ox,oy = self.getViewOffsets()
self._screen_current.select(x+ox,y+oy)
self.update()
return True
def mouseDragEvent(self, evt):
if self._mouse.reportPress:
self._screen_current.select(0,0)
return self._sendMouse(evt)
x,y = evt.x,evt.y
ox,oy = self.getViewOffsets()
self._screen_current.select(x+ox,y+oy,moveAnchor=False)
self.update()
return True
def mouseReleaseEvent(self, evt):
self._selecting = False
if (selected := self._screen_current.getSelected()):
self._clipboard.setText(selected)
return self._sendMouse(evt)
def wheelEvent(self, evt):
if self._sendMouse(evt):
return True
ret = super().wheelEvent(evt)
if self._selecting:
x,y = evt.x,evt.y
ox,oy = self.getViewOffsets()
self._screen_current.select(x+ox,y+oy,moveAnchor=False)
self.update()
return ret
def mouseTapEvent(self, evt): return self._sendMouse(evt)
def mouseDoubleClickEvent(self, evt): return self._sendMouse(evt)
def mouseMoveEvent(self, evt):

10
TermTk/TTkWidgets/button.py

@ -226,6 +226,9 @@ class TTkButton(TTkWidget):
def keyEvent(self, evt):
if ( evt.type == TTkK.Character and evt.key==" " ) or \
( evt.type == TTkK.SpecialKey and evt.key == TTkK.Key_Enter ):
if self._checkable:
self._checked = not self._checked
self.toggled.emit(self._checked)
self.update()
self.clicked.emit()
return True
@ -237,10 +240,13 @@ class TTkButton(TTkWidget):
style = self.style()['checked']
else:
style = self.style()['unchecked']
if self.hasFocus():
borderColor = self.style()['focus']['borderColor']
else:
borderColor = style['borderColor']
else:
style = self.currentStyle()
borderColor = style['borderColor']
borderColor = style['borderColor']
textColor = style['color']
grid = style['grid']

4
TermTk/TTkWidgets/kodetab.py

@ -79,11 +79,11 @@ class _TTkKodeTab(TTkTabWidget):
kt._tabBarTopLayout.update()
def dragEnterEvent(self, evt) -> bool:
TTkLog.debug(f"leave")
TTkLog.debug(f"Drag Enter")
return True
def dragLeaveEvent(self, evt) -> bool:
TTkLog.debug(f"leave")
TTkLog.debug(f"Drag Leave")
self._frameOverlay = None
self.update()
return True

38
TermTk/TTkWidgets/lineedit.py

@ -66,8 +66,9 @@ class TTkLineEdit(TTkWidget):
self._text = TTkString(kwargs.get('text' , '' ))
self._inputType = kwargs.get('inputType' , TTkK.Input_Text )
super().__init__(*args, **kwargs)
if self._inputType & TTkK.Input_Number and\
not self._text.lstrip('-').isdigit(): self._text = TTkString()
if ( self._inputType & TTkK.Input_Number and
not self._isFloat(self._text)):
self._text = TTkString('0')
self.setMaximumHeight(1)
self.setMinimumSize(1,1)
self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus)
@ -156,7 +157,7 @@ class TTkLineEdit(TTkWidget):
self._selectionFrom = len(before)
self._selectionTo = len(before)
selectRE = '[^ \t\r\n\(\)\[\]\.\,\+\-\*\/]*'
selectRE = r'[^ \t\r\n()[\]\.\,\+\-\*\/]*'
if m := before.search(selectRE+'$'):
self._selectionFrom -= len(m.group(0))
@ -181,6 +182,15 @@ class TTkLineEdit(TTkWidget):
self.update()
return True
@staticmethod
def _isFloat(num):
try:
float(str(num))
return True
except:
return False
def pasteEvent(self, txt:str):
txt = TTkString().join(txt.split('\n'))
@ -198,8 +208,8 @@ class TTkLineEdit(TTkWidget):
post = text.substring(fr=self._cursorPos)
text = pre + txt + post
if self._inputType & TTkK.Input_Number and \
not text.lstrip('-').isdigit():
if ( self._inputType & TTkK.Input_Number and
not self._isFloat(text) ):
return True
self.setText(text, self._cursorPos+txt.termWidth())
@ -210,12 +220,12 @@ class TTkLineEdit(TTkWidget):
def keyEvent(self, evt):
baseText = self._text
if evt.type == TTkK.SpecialKey:
# Don't Handle the special tab key
if evt.key == TTkK.Key_Tab:
# Don't Handle the special focus switch key
if evt.key in (
TTkK.Key_Tab, TTkK.Key_Up, TTkK.Key_Down):
return False
if evt.key == TTkK.Key_Up: pass
elif evt.key == TTkK.Key_Down: pass
elif evt.key == TTkK.Key_Left:
if evt.key == TTkK.Key_Left:
if self._selectionFrom < self._selectionTo:
self._cursorPos = self._selectionTo
self._cursorPos = self._text.prevPos(self._cursorPos)
@ -244,8 +254,8 @@ class TTkLineEdit(TTkWidget):
self._text = self._text.substring(to=prev) + self._text.substring(fr=self._cursorPos)
self._cursorPos = prev
if self._inputType & TTkK.Input_Number and \
not self._text.lstrip('-').isdigit():
if ( self._inputType & TTkK.Input_Number and
not self._isFloat(self._text) ):
self.setText('0', 1)
self._pushCursor()
@ -267,8 +277,8 @@ class TTkLineEdit(TTkWidget):
post = text.substring(fr=self._cursorPos)
text = pre + evt.key + post
if self._inputType & TTkK.Input_Number and \
not text.lstrip('-').isdigit():
if ( self._inputType & TTkK.Input_Number and
not self._isFloat(text) ):
return True
self.setText(text, self._cursorPos+1)

15
TermTk/TTkWidgets/list_.py

@ -30,9 +30,12 @@ class TTkList(TTkAbstractScrollArea):
__slots__ = (
'_listView', 'itemClicked', 'textClicked',
# Forwarded Methods
'items', 'addItem', 'addItemAt', 'indexOf', 'itemAt',
'moveItem', 'removeAt', 'removeItem',
'setSelectionMode', 'selectedItems', 'selectedLabels',
'items',
'dragDropMode', 'setDragDropMode',
'addItem', 'addItemAt', 'addItems', 'addItemsAt',
'indexOf', 'itemAt', 'moveItem',
'removeAt', 'removeItem', 'removeItems',
'selectionMode', 'setSelectionMode', 'selectedItems', 'selectedLabels',
'setCurrentRow', 'setCurrentItem', )
def __init__(self, *args, **kwargs):
@ -51,11 +54,17 @@ class TTkList(TTkAbstractScrollArea):
self.moveItem = self._listView.moveItem
self.removeAt = self._listView.removeAt
self.removeItem = self._listView.removeItem
self.removeItems = self._listView.removeItems
self.addItem = self._listView.addItem
self.addItems = self._listView.addItems
self.addItemAt = self._listView.addItemAt
self.addItemsAt = self._listView.addItemsAt
self.selectionMode = self._listView.selectionMode
self.setSelectionMode = self._listView.setSelectionMode
self.selectedItems = self._listView.selectedItems
self.selectedLabels = self._listView.selectedLabels
self.setCurrentRow = self._listView.setCurrentRow
self.setCurrentItem = self._listView.setCurrentItem
self.dragDropMode = self._listView.dragDropMode
self.setDragDropMode = self._listView.setDragDropMode

172
TermTk/TTkWidgets/listwidget.py

@ -22,14 +22,17 @@
__all__ = ['TTkAbstractListItem', 'TTkListWidget']
from dataclasses import dataclass
from TermTk.TTkCore.cfg import TTkCfg
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkCore.canvas import TTkCanvas
from TermTk.TTkCore.string import TTkString
from TermTk.TTkGui.drag import TTkDrag
from TermTk.TTkWidgets.widget import TTkWidget
from TermTk.TTkWidgets.label import TTkLabel
from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView
class TTkAbstractListItem(TTkWidget):
@ -37,9 +40,9 @@ class TTkAbstractListItem(TTkWidget):
classStyle = TTkWidget.classStyle | {
'default': {'color': TTkColor.RST},
'highlighted': {'color': TTkColor.fg('#00FF00')+TTkColor.bg('#0055FF')+TTkColor.UNDERLINE},
'hover': {'color': TTkColor.fg('#00FF00')+TTkColor.bg('#0088FF')},
'selected': {'color': TTkColor.fg('#00FF00')+TTkColor.bg('#0055FF')},
'highlighted': {'color': TTkColor.bg('#008855')+TTkColor.UNDERLINE},
'hover': {'color': TTkColor.bg('#0088FF')},
'selected': {'color': TTkColor.bg('#0055FF')},
'clicked': {'color': TTkColor.fg('#FFFF00')},
'disabled': {'color': TTkColor.fg('#888888')},
}
@ -92,29 +95,39 @@ class TTkAbstractListItem(TTkWidget):
self.update()
def paintEvent(self, canvas):
style = self.currentStyle()
if style == self.classStyle['hover']:
pass
elif self._highlighted:
style = self.style()['highlighted']
elif self._selected:
style = self.style()['selected']
color = (style:=self.currentStyle())['color']
if self._highlighted:
color = color+self.style()['highlighted']['color']
if self._selected:
color = color+self.style()['selected']['color']
if style==self.style()['hover']:
color = color+self.style()['hover']['color']
w = self.width()
canvas.drawTTkString(pos=(0,0), width=w, color=style['color'] ,text=self._text)
canvas.drawTTkString(pos=(0,0), width=w, color=color ,text=self._text)
class TTkListWidget(TTkAbstractScrollView):
@dataclass(frozen=True)
class _DropListData:
widget: TTkAbstractScrollView
items: list
'''TTkListWidget'''
__slots__ = ('itemClicked', 'textClicked', '_selectedItems', '_selectionMode', '_highlighted', '_items')
__slots__ = ('itemClicked', 'textClicked',
'_selectedItems', '_selectionMode',
'_highlighted', '_items',
'_dragPos', '_dndMode')
def __init__(self, *args, **kwargs):
# Default Class Specific Values
self._selectionMode = kwargs.get("selectionMode", TTkK.SingleSelection)
self._selectedItems = []
self._items = []
self._highlighted = None
self._dragPos = None
self._dndMode = kwargs.get("dragDropMode", TTkK.DragDropMode.NoDragDrop)
# Signals
self.itemClicked = pyTTkSignal(TTkWidget)
self.itemClicked = pyTTkSignal(TTkAbstractListItem)
self.textClicked = pyTTkSignal(str)
# Init Super
TTkAbstractScrollView.__init__(self, *args, **kwargs)
@ -149,6 +162,18 @@ class TTkListWidget(TTkAbstractScrollView):
self.itemClicked.emit(label)
self.textClicked.emit(label.text())
def dragDropMode(self):
'''dragDropMode'''
return self._dndMode
def setDragDropMode(self, dndMode):
'''setDragDropMode'''
self._dndMode = dndMode
def selectionMode(self):
'''selectionMode'''
return self._selectionMode
def setSelectionMode(self, mode):
'''setSelectionMode'''
self._selectionMode = mode
@ -186,6 +211,10 @@ class TTkListWidget(TTkAbstractScrollView):
'''addItem'''
self.addItemAt(item, len(self._items), data)
def addItems(self, items):
'''addItems'''
self.addItemAt(items, len(self._items))
def _placeItems(self):
minw = self.width()
for item in self._items:
@ -193,16 +222,24 @@ class TTkListWidget(TTkAbstractScrollView):
for y,item in enumerate(self._items):
item.setGeometry(0,y,minw,1)
self.viewChanged.emit()
self.update()
def addItemAt(self, item, pos, data=None):
'''addItemAt'''
if isinstance(item, str) or isinstance(item, TTkString):
#label = TTkAbstractListItem(text=item, width=max(len(item),self.width()))
label = TTkAbstractListItem(text=item, data=data)
return self.addItemAt(label,pos)
item.listItemClicked.connect(self._labelSelectedHandler)
self._items.insert(pos,item)
self.layout().addWidget(item)
item = TTkAbstractListItem(text=item, data=data)
return self.addItemsAt([item],pos)
def addItemsAt(self, items, pos):
'''addItemsAt'''
for item in items:
if not issubclass(type(item),TTkAbstractListItem):
TTkLog.error(f"{item=} is not an TTkAbstractListItem")
return
for item in items:
item.listItemClicked.connect(self._labelSelectedHandler)
self._items[pos:pos] = items
self.layout().addWidgets(items)
self._placeItems()
def indexOf(self, item):
@ -226,10 +263,20 @@ class TTkListWidget(TTkAbstractScrollView):
def removeItem(self, item):
'''removeItem'''
self.removeWidget(item)
self._items.remove(item)
if item in self._selectedItems:
self._selectedItems.remove(item)
self.removeItems([item])
def removeItems(self, items):
'''removeItems'''
self.layout().removeWidgets(items)
for item in items.copy():
item.listItemClicked.disconnect(self._labelSelectedHandler)
item._setSelected(False)
item._setHighlighted(False)
self._items.remove(item)
if item in self._selectedItems:
self._selectedItems.remove(item)
if item == self._highlighted:
self._highlighted = None
self._placeItems()
def removeAt(self, pos):
@ -256,6 +303,67 @@ class TTkListWidget(TTkAbstractScrollView):
elif index <= offy:
self.viewMoveTo(offx, index)
def mouseDragEvent(self, evt) -> bool:
if not(self._dndMode & TTkK.DragDropMode.AllowDrag):
return False
if not (items:=self._selectedItems.copy()):
return True
drag = TTkDrag()
data =TTkListWidget._DropListData(widget=self,items=items)
h = min(3,ih:=len(items)) + 2 + (1 if ih>3 else 0)
w = min(20,iw:=max([it.text().termWidth() for it in items[:3]])) + 2
pm = TTkCanvas(width=w,height=h)
for y,it in enumerate(items[:3],1):
txt = it.text()
if txt.termWidth() < 20:
pm.drawText(pos=(1,y), text=it.text())
else:
pm.drawText(pos=(1,y), text=it.text(), width=17)
pm.drawText(pos=(18,y), text='...')
if ih>3:
pm.drawText(pos=(1,4), text='...')
pm.drawBox(pos=(0,0),size=(w,h))
drag.setPixmap(pm)
drag.setData(data)
drag.exec()
return True
def dragEnterEvent(self, evt):
if not(self._dndMode & TTkK.DragDropMode.AllowDrop):
return False
if issubclass(type(evt.data()),TTkListWidget._DropListData):
return self.dragMoveEvent(evt)
return False
def dragMoveEvent(self, evt):
offx,offy = self.getViewOffsets()
y=min(evt.y+offy,len(self._items))
self._dragPos = (offx+evt.x, y)
self.update()
return True
def dragLeaveEvent(self, evt):
self._dragPos = None
self.update()
return True
def dropEvent(self, evt) -> bool:
if not(self._dndMode & TTkK.DragDropMode.AllowDrop):
return False
self._dragPos = None
if not issubclass(type(evt.data()) ,TTkListWidget._DropListData):
return False
offx,offy = self.getViewOffsets()
wid = evt.data().widget
items = evt.data().items
if wid and items:
wid.removeItems(items)
for it in items:
it.setCurrentStyle(it.style()['default'])
self.addItemsAt(items,offy+evt.y)
return True
return False
def keyEvent(self, evt):
if not self._highlighted: return False
if ( evt.type == TTkK.Character and evt.key==" " ) or \
@ -304,3 +412,19 @@ class TTkListWidget(TTkAbstractScrollView):
def focusOutEvent(self):
if self._highlighted:
self._highlighted._setHighlighted(False)
self._dragPos = None
# Stupid hack to paint on top of the child widgets
def paintChildCanvas(self):
super().paintChildCanvas()
if self._dragPos:
canvas = self.getCanvas()
x,y = self._dragPos
offx,offy = self.getViewOffsets()
p1 = (0,y-offy-1)
p2 = (0,y-offy)
canvas.drawText(pos=p1,text="╙─╼", color=TTkColor.fg("#FFFF00")+TTkColor.bg("#008855"))
canvas.drawText(pos=p2,text="╓─╼", color=TTkColor.fg("#FFFF00")+TTkColor.bg("#008855"))

18
TermTk/TTkWidgets/tabwidget.py

@ -359,15 +359,15 @@ class TTkTabBar(TTkContainer):
self._leftScroller.setSideEnd(sideEnd&TTkK.LEFT)
self._updateTabs()
def addTab(self, label, data=None):
def addTab(self, label, data=None, closable=None):
'''addTab'''
return self.insertTab(len(self._tabButtons), label=label, data=data)
return self.insertTab(len(self._tabButtons), label=label, data=data, closable=closable)
def insertTab(self, index, label, data=None):
def insertTab(self, index, label, data=None, closable=None):
'''insertTab'''
if index <= self._currentIndex:
self._currentIndex += 1
button = TTkTabButton(parent=self, text=label, border=not self._small, closable=self._tabClosable, data=data)
button = TTkTabButton(parent=self, text=label, border=not self._small, closable=self._tabClosable if closable is None else closable, data=data)
self._tabButtons.insert(index,button)
button.clicked.connect(lambda :self.setCurrentIndex(self._tabButtons.index(button)))
button.clicked.connect(lambda :self.tabBarClicked.emit(self._tabButtons.index(button)))
@ -668,7 +668,7 @@ class TTkTabWidget(TTkFrame):
if index <= newIndex:
newIndex -= 1
tw.removeTab(index)
self.insertTab(newIndex, widget, tb.text(), data)
self.insertTab(newIndex, widget, tb.text(), data, tb._closable)
self.setCurrentIndex(newIndex)
#self._tabChanged(newIndex)
elif tw != self:
@ -699,19 +699,19 @@ class TTkTabWidget(TTkFrame):
self._tabBarTopLayout.update()
return button
def addTab(self, widget, label, data=None):
def addTab(self, widget, label, data=None, closable=None):
'''addTab'''
widget.hide()
self._tabWidgets.append(widget)
self.layout().addWidget(widget)
self._tabBar.addTab(label, data)
self._tabBar.addTab(label, data, closable)
def insertTab(self, index, widget, label, data=None):
def insertTab(self, index, widget, label, data=None, closable=None):
'''insertTab'''
widget.hide()
self._tabWidgets.insert(index, widget)
self.layout().addWidget(widget)
self._tabBar.insertTab(index, label, data)
self._tabBar.insertTab(index, label, data, closable)
@pyTTkSlot(int)
def removeTab(self, index):

36
TermTk/TTkWidgets/texedit.py

@ -50,15 +50,17 @@ class _TTkTextEditViewLineNumber(TTkAbstractScrollView):
'separatorColor': TTkColor.fg("#888888")},
}
__slots__ = ('_textWrap')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setMaximumWidth(20)
__slots__ = ('_textWrap','_startingNumber')
def __init__(self, startingNumber=0, **kwargs):
self._startingNumber = startingNumber
self._textWrap = None
super().__init__(**kwargs)
self.setMaximumWidth(2)
def _wrapChanged(self):
dt = max(1,self._textWrap._lines[-1][0])
width = 2+floor(log10(dt))
off = self._startingNumber
width = 1+max(len(str(int(dt+off))),len(str(int(off))))
self.setMaximumWidth(width)
self.update()
@ -80,6 +82,7 @@ class _TTkTextEditViewLineNumber(TTkAbstractScrollView):
if not self._textWrap: return
_, oy = self.getViewOffsets()
w, h = self.size()
off = self._startingNumber
style = self.currentStyle()
color = style['color']
@ -91,11 +94,11 @@ class _TTkTextEditViewLineNumber(TTkAbstractScrollView):
if fr:
canvas.drawText(pos=(0,i), text='<', width=w, color=wrapColor)
else:
canvas.drawText(pos=(0,i), text=f"{dt}", width=w, color=color)
canvas.drawText(pos=(0,i), text=f"{dt+off}", width=w, color=color)
canvas.drawChar(pos=(w-1,i), char='', color=separatorColor)
else:
for y in range(h):
canvas.drawText(pos=(0,y), text=f"{y+oy}", width=w, color=color)
canvas.drawText(pos=(0,y), text=f"{y+oy+off}", width=w, color=color)
canvas.drawChar(pos=(w-1,y), char='', color=separatorColor)
class TTkTextEditView(TTkAbstractScrollView):
@ -799,17 +802,17 @@ class TTkTextEdit(TTkAbstractScrollArea):
'undoAvilable', 'redoAvailable',
'textChanged'
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, textEditView=None, lineNumber=False, lineNumberStarting=0, **kwargs):
super().__init__(**kwargs)
if 'parent' in kwargs: kwargs.pop('parent')
self._textEditView = kwargs.get('textEditView', TTkTextEditView(*args, **kwargs))
self._textEditView = textEditView if textEditView else TTkTextEditView(**kwargs)
# self.setFocusPolicy(self._textEditView.focusPolicy())
# self._textEditView.setFocusPolicy(TTkK.ParentFocus)
self._lineNumber = kwargs.get('lineNumber', False)
self._lineNumber = lineNumber
textEditLayout = TTkAbstractScrollViewGridLayout()
textEditLayout.addWidget(self._textEditView,0,1)
self._lineNumberView = _TTkTextEditViewLineNumber(visible=self._lineNumber)
self._lineNumberView = _TTkTextEditViewLineNumber(visible=self._lineNumber, startingNumber=lineNumberStarting)
self._lineNumberView.setTextWrap(self._textEditView._textWrap)
textEditLayout.addWidget(self._lineNumberView,0,0)
self.setViewport(textEditLayout)
@ -858,10 +861,19 @@ class TTkTextEdit(TTkAbstractScrollArea):
'''getLineNumber'''
return self._lineNumberView.isVisible()
@pyTTkSlot(bool)
def setLineNumber(self, ln):
'''setLineNumber'''
self._lineNumberView.setVisible(ln)
def lineNumberStarting(self):
return self._lineNumberView._startingNumber
@pyTTkSlot(int)
def setLineNumberStarting(self, starting):
self._lineNumberView._startingNumber = starting
self._lineNumberView._wrapChanged()
def setDocument(self, document):
'''setDocument'''
self._textEditView.setDocument(document)

3
TermTk/TTkWidgets/widget.py

@ -153,7 +153,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
self._processStyleEvent(TTkWidget._S_DEFAULT)
self._canvas = TTkCanvas(
widget = self,
width = self._width ,
height = self._height )
@ -167,8 +166,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
self._parent.layout().addWidget(self)
self._parent.update(repaint=True, updateLayout=True)
self.update(repaint=True, updateLayout=True)
def __del__(self):
''' .. caution:: Don't touch this! '''
# TTkLog.debug("DESTRUCTOR")

13
demo/showcase/_showcasehelper.py

@ -21,13 +21,20 @@
# SOFTWARE.
import sys, os, random
import platform
sys.path.append(os.path.join(sys.path[0],'../..'))
import TermTk as ttk
zc1 = chr(0x07a6) # Zero width chars oަ
zc2 = chr(0x20D7) # Zero width chars o
zc3 = chr(0x065f) # Zero width chars oٟ
if platform.system() == 'Windows':
# The windows terminals badly supports zero sized chars
zc1 = 'X'
zc2 = 'Y'
zc3 = 'Z'
else:
zc1 = chr(0x07a6) # Zero width chars oަ
zc2 = chr(0x20D7) # Zero width chars o
zc3 = chr(0x065f) # Zero width chars oٟ
utfwords = [
f"--Zero{zc1}{zc2}{zc3}-1-", f"--Zero-2{zc1}{zc2}{zc3}-", f"--Ze{zc1}{zc2}{zc3}ro-3-", f"{zc1}{zc2}{zc3}--Zero-4-",
"Lorem", "i🙻sum", "d😮l😱r", "sit", "am😎t,", "c😱nsectetur", "adi🙻iscing", "elit,", "sed", "do", "eiusmod", "t😜mpor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliq😞ip", "ex", "ea", "comm😞do", "cons😿quat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "cul🙻a", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."]

4
demo/showcase/formwidgets02.py

@ -29,7 +29,7 @@ sys.path.append(os.path.join(sys.path[0],'../..'))
import TermTk as ttk
sys.path.append(os.path.join(sys.path[0],'..'))
from showcase._showcasehelper import getUtfSentence
from showcase._showcasehelper import getUtfSentence, zc1
def demoFormWidgets(root=None):
win_form1_grid_layout = ttk.TTkGridLayout(columnMinWidth=1)
@ -72,7 +72,7 @@ def demoFormWidgets(root=None):
win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='Line Edit Test 2 😎 -'),row,2)
win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled)
row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Test 3'),row,0)
win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='Line Edit Test 3 oަ -'),row,2)
win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text=f'Line Edit Test 3 o{zc1}-'),row,2)
win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled)
row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Number'),row,0)

78
demo/showcase/list.py

@ -33,23 +33,43 @@ from showcase._showcasehelper import getUtfWord
def demoList(root= None):
# Define the main Layout
splitter = ttk.TTkSplitter(parent=root, orientation=ttk.TTkK.HORIZONTAL)
frame2 = ttk.TTkFrame(parent=splitter, border=0, layout=ttk.TTkVBoxLayout())
frame1 = ttk.TTkFrame(parent=splitter, border=0, layout=ttk.TTkVBoxLayout())
frame3 = ttk.TTkFrame(parent=splitter, border=0, layout=ttk.TTkVBoxLayout())
retFrame = ttk.TTkFrame(parent=root, border=False, layout=(rootLayout:=ttk.TTkGridLayout()))
# Multi Selection List
ttk.TTkLabel(parent=frame1, text="[ MultiSelect ]",maxHeight=2)
listWidgetMulti = ttk.TTkList(parent=frame1, maxWidth=40, minWidth=10, selectionMode=ttk.TTkK.MultiSelection)
# Define the main Layout
win1 = ttk.TTkWindow(title="Single List", layout=ttk.TTkVBoxLayout())
win2 = ttk.TTkWindow(title="Multi List", layout=ttk.TTkVBoxLayout())
win3 = ttk.TTkWindow(title="Log", layout=ttk.TTkVBoxLayout())
win4 = ttk.TTkWindow(title="Oly Drag Allowed", layout=ttk.TTkVBoxLayout())
win5 = ttk.TTkWindow(title="Oly Drop Allowed", layout=ttk.TTkVBoxLayout())
layout1 = ttk.TTkLayout()
# Place the widgets in the root layout
rootLayout.addWidget(win1,0,0,2,1)
rootLayout.addWidget(win2,0,1,2,1)
rootLayout.addWidget(win3,0,2,2,3)
rootLayout.addItem(layout1,2,0,1,3)
rootLayout.addWidget(win4,2,3)
rootLayout.addWidget(win5,2,4)
# Single Selection List
ttk.TTkLabel(parent=frame2, text="[ SingleSelect ]",maxHeight=2)
listWidgetSingle = ttk.TTkList(parent=frame2, maxWidth=40, minWidth=10)
listWidgetSingle = ttk.TTkList(parent=win1, maxWidth=40, minWidth=10, dragDropMode=ttk.TTkK.AllowDragDrop)
# Multi Selection List
listWidgetMulti = ttk.TTkList(parent=win2, maxWidth=40, minWidth=10, dragDropMode=ttk.TTkK.AllowDragDrop, selectionMode=ttk.TTkK.MultiSelection)
# Multi Selection List - Drag Allowed
listWidgetDrag = ttk.TTkList(parent=win4, maxWidth=40, minWidth=10, dragDropMode=ttk.TTkK.AllowDrag)
listWidgetDrop = ttk.TTkList(parent=win5, maxWidth=40, minWidth=10, dragDropMode=ttk.TTkK.AllowDrop)
# Log Viewer
label1 = ttk.TTkLabel(parent=frame3, text="[ list1 ]",maxHeight=2)
label2 = ttk.TTkLabel(parent=frame3, text="[ list2 ]",maxHeight=2)
ttk.TTkLogViewer(parent=frame3)#, border=True)
label1 = ttk.TTkLabel(pos=(10,0), text="[ list1 ]",maxHeight=2)
label2 = ttk.TTkLabel(pos=(10,1), text="[ list2 ]",maxHeight=2)
ttk.TTkLogViewer(parent=win3)
btn_mv1 = ttk.TTkButton(pos=(0,0), text=" >> ")
btn_mv2 = ttk.TTkButton(pos=(0,1), text=" << ")
btn_del = ttk.TTkButton(pos=(0,2), text="Delete")
layout1.addWidgets([label1,label2,btn_mv1,btn_mv2,btn_del])
@ttk.pyTTkSlot(str)
def _listCallback1(label):
@ -61,16 +81,42 @@ def demoList(root= None):
ttk.TTkLog.info(f'Clicked label2: "{label}" - selected: {[str(s) for s in listWidgetMulti.selectedLabels()]}')
label2.setText(f'[ list2 ] clicked "{label}" - {[str(s) for s in listWidgetMulti.selectedLabels()]}')
@ttk.pyTTkSlot()
def _moveToRight2():
for i in listWidgetSingle.selectedItems().copy():
listWidgetSingle.removeItem(i)
listWidgetMulti.addItemAt(i,0)
@ttk.pyTTkSlot()
def _moveToLeft1():
for i in listWidgetMulti.selectedItems().copy():
listWidgetMulti.removeItem(i)
listWidgetSingle.addItemAt(i,0)
@ttk.pyTTkSlot()
def _delSelected():
items = listWidgetMulti.selectedItems()
listWidgetMulti.removeItems(items)
items = listWidgetSingle.selectedItems()
listWidgetSingle.removeItems(items)
btn_mv1.clicked.connect(_moveToRight2)
btn_mv2.clicked.connect(_moveToLeft1)
btn_del.clicked.connect(_delSelected)
# Connect the signals to the 2 slots defines
listWidgetSingle.textClicked.connect(_listCallback1)
listWidgetMulti.textClicked.connect(_listCallback2)
# populate the lists with random entries
for i in range(100):
listWidgetSingle.addItem(f"{i}) {getUtfWord()} {getUtfWord()}")
listWidgetMulti.addItem(f"{getUtfWord()} {getUtfWord()}")
for i in range(50):
listWidgetSingle.addItem(f"S-{i}) {getUtfWord()} {getUtfWord()}")
listWidgetMulti.addItem( f"M-{i}){getUtfWord()} {getUtfWord()}")
listWidgetDrag.addItem( f"D-{i}){getUtfWord()} {getUtfWord()}")
return splitter
return retFrame
def main():
parser = argparse.ArgumentParser()

15
demo/showcase/textedit.py

@ -31,7 +31,7 @@ sys.path.append(os.path.join(sys.path[0],'../..'))
import TermTk as ttk
sys.path.append(os.path.join(sys.path[0],'..'))
from showcase._showcasehelper import getUtfColoredSentence
from showcase._showcasehelper import getUtfColoredSentence, zc1, zc2, zc3
class superSimpleHorizontalLine(ttk.TTkWidget):
def paintEvent(self, canvas):
@ -52,7 +52,7 @@ def demoTextEdit(root=None, document=None):
# If no document is passed a default one is created,
# In this showcase I want to be able to share the same
# document among 2 textEdit widgets
te = ttk.TTkTextEdit(document=document, lineNumber=True)
te = ttk.TTkTextEdit(document=document, lineNumber=True, lineNumberStarting=1)
te.setReadOnly(False)
@ -74,10 +74,6 @@ def demoTextEdit(root=None, document=None):
te.append( " |.|.|.|.|.||.|.|.||.|.|.")
te.append("")
zc1 = chr(0x07a6)
zc2 = chr(0x20D7)
zc3 = chr(0x065f)
te.append( " - | | | | | -")
te.append(f"Zero Size: - o{zc1} o{zc2} o{zc3} o{zc1}{zc2} o{zc1}{zc2}{zc3} -")
te.append( " - | | | | | -")
@ -127,7 +123,7 @@ def demoTextEdit(root=None, document=None):
# Empty columns/cells are 1 char wide due to "columnMinWidth=1" parameter in the GridLayout
# 1 3 8 11
# 0 2 4 5 6 7 9 10 12
# 0 [ ] FG [ ] BG [ ] LineNumber
# 0 [ ] FG [ ] BG [ ] LineNumber [ 0]Starting Number
# 1 ┌─────┐ ┌─────┐ ╒═══╕╒═══╕╒═══╕╒═══╕ ┌──────┐┌──────┐
# 2 │ │ │ │ │ a ││ a ││ a ││ a │ │ UNDO ││ REDO │
# 3 └─────┘ └─────┘ └───┘└───┘└───┘└───┘ ╘══════╛└──────┘ ┕━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┙
@ -140,6 +136,7 @@ def demoTextEdit(root=None, document=None):
fontLayout.addWidget(btn_bgColor := ttk.TTkColorButtonPicker(border=True, enabled=False, maxSize=(7 ,3)),1,2)
fontLayout.addWidget(cb_linenumber := ttk.TTkCheckbox(text=" LineNumber", checked=True),0,4,1,3)
fontLayout.addWidget(sb_linenumber := ttk.TTkSpinBox(value=1, maxWidth=5, maximum=10000, minimum=-10000, enabled=True),0,7,1,1)
# Char style buttons
fontLayout.addWidget(btn_bold := ttk.TTkButton(border=True, maxSize=(5,3), checkable=True, text=ttk.TTkString( 'a' , ttk.TTkColor.BOLD) ),1,4)
@ -209,7 +206,9 @@ def demoTextEdit(root=None, document=None):
cb_fg.clicked.connect(lambda _: _setStyle())
cb_bg.clicked.connect(lambda _: _setStyle())
cb_linenumber.stateChanged.connect(lambda x: te.setLineNumber(x==ttk.TTkK.Checked))
cb_linenumber.toggled.connect(te.setLineNumber)
cb_linenumber.toggled.connect(sb_linenumber.setEnabled)
sb_linenumber.valueChanged.connect(te.setLineNumberStarting)
btn_fgColor.colorSelected.connect(lambda _: _setStyle())
btn_bgColor.colorSelected.connect(lambda _: _setStyle())

12
docs/MDNotes/Resources.md

@ -98,15 +98,3 @@ Check as reference:
Pty Demo:
- https://docs.python.org/3/library/pty.html#example
Run Python - pyTermTk on Wine32:
```bash
~/.var/app/net.lutris.Lutris/data/lutris/runners/wine/lutris-GE-Proton8-5-x86_64/bin/wine /home/one/.wine/drive_c/windows/system32/cmd.exe
# Install python from https://www.python.org/downloads/windows/
# Copy the pyTermTk demo and TermTk folder in
# ~/.wine/drive_c/users/one/AppData/Local/Programs/Python/Python310-32
cd C:\users\one\AppData\Local\Programs\Python\Python310-32
python.exe demo/demo.py
```

36
docs/MDNotes/msWindows/Init Sequence.md

@ -0,0 +1,36 @@
# How it Was
TTk:
- __init__()
```python
self._input = TTkInput()
self._input.inputEvent.connect(self._processInput)
self._input.pasteEvent.connect(self._processPaste)
```
- mainLoop()
```python
TTkTerm.registerResizeCb(self._win_resize_cb)
TTkTerm.init(
title=self._title,
sigmask=self._sigmask,
mouse=self._termMouse,
directMouse=self._termDirectMouse )
```
# How it Should Be
- __init__()
```python
TTkInput.inputEvent.connect(self._processInput)
TTkInput.pasteEvent.connect(self._processPaste)
TTkSignalDriver.sigStop.connect(self._SIGSTOP)
TTkSignalDriver.sigCont.connect(self._SIGCONT)
TTkSignalDriver.sigInt.connect( self._SIGINT)
```
- mainLoop()
```python
TTkInput.init(
mouse=self._termMouse,
directMouse=self._termDirectMouse)
TTkTerm.init(
title=self._title,
sigmask=self._sigmask)
```

31
docs/MDNotes/msWindows/Resources.md

@ -0,0 +1,31 @@
# Run Python - pyTermTk on Wine32:
```bash
# cmd in the terminal
~/.var/app/net.lutris.Lutris/data/lutris/runners/wine/lutris-GE-Proton8-5-x86_64/bin/wine cmd
# cmd in a wine window
~/.var/app/net.lutris.Lutris/data/lutris//runners/wine/lutris-GE-Proton8-5-x86_64/bin/wine wineconsole
# Install python from https://www.python.org/downloads/windows/
# Copy the pyTermTk demo and TermTk folder in
# ~/.wine/drive_c/users/one/AppData/Local/Programs/Python/Python310-32
C:
cd C:\users\one\AppData\Local\Programs\Python\Python310-32
python.exe demo/demo.py
python.exe tests/test.input.win.py
```
# termios wrappers
- termiWin -> https://github.com/veeso/termiWin
# Competitors with MS-Win support
### Textual -> https://github.com/Textualize/textual
https://github.com/Textualize/textual/blob/main/src/textual/drivers/win32.py
### TheVTPyProject -> https://github.com/srccircumflex/TheVTPyProject
# Incompatible code (the one using termios):
- TermTk/TTkCore/TTkTerm/readinputlinux.py
- TermTk/TTkCore/TTkTerm/readinputlinux_thread.py
- TermTk/TTkCore/TTkTerm/term_unix.py

2
setup.ttkDesigner.py

@ -33,7 +33,7 @@ setup(
package_data={'ttkDesigner': ['tui/*']},
python_requires=">=3.9",
install_requires=[
'pyTermTk>=0.30.0a115',
'pyTermTk>=0.36.0a',
'pyperclip',
'Pillow'],
entry_points={

4
tests/pytest/conftest.py

@ -27,11 +27,11 @@ import sys
from mock_term import Mock_TTkTerm
from mock_input import Mock_TTkInput
moduleTerm = type(sys)('TermTk.TTkCore.TTkTerm.term')
moduleTerm = type(sys)('TermTk.TTkCore.drivers.term_unix')
moduleTerm.TTkTerm = Mock_TTkTerm
moduleInput = type(sys)('TermTk.TTkCore.TTkTerm.input')
moduleInput.TTkInput = Mock_TTkInput
sys.modules['TermTk.TTkCore.TTkTerm.term'] = moduleTerm
sys.modules['TermTk.TTkCore.drivers.term_unix'] = moduleTerm
sys.modules['TermTk.TTkCore.TTkTerm.input'] = moduleInput

20
tests/pytest/mock_input.py

@ -23,12 +23,20 @@
# Thanks to: https://stackoverflow.com/questions/43162722/mocking-a-module-import-in-pytest
class Mock_TTkInput():
def __init__(self): pass
def close(self): pass
def stop(self): pass
def cont(self): pass
def get_key(self, callback=None): pass
def start(self): pass
@staticmethod
def init(mouse, directMouse):pass
@staticmethod
def setMouse(mouse, directMouse): pass
@staticmethod
def close(): pass
@staticmethod
def stop(): pass
@staticmethod
def cont(): pass
@staticmethod
def get_key( callback=None): pass
@staticmethod
def start(): pass
class inputEvent():
def connect(*args):

2
tests/pytest/mock_term.py

@ -79,7 +79,7 @@ class Mock_TTkTerm():
@staticmethod
def exit(): pass
@staticmethod
def init(title,sigmask,mouse,directMouse): pass
def init(title,sigmask): pass
@staticmethod
def getTerminalSize():
return 250,70

4
tests/sandbox/sandbox.html

@ -231,11 +231,11 @@
pyodide.runPython(`
import sys
import TermTk as ttk
from TermTk.TTkCore.TTkTerm.input import TTkInput
import pyodideProxy
def ttk_input(val):
if ttk.TTkHelper._rootWidget and ttk.TTkHelper._rootWidget._input:
ttk.TTkHelper._rootWidget._input.key_process(val)
TTkInput.key_process(val)
def ttk_resize(w,h):
ttk.TTkLog.debug(f"Resize: {w=} {h=}")

68
tests/test.input.curses.py

@ -0,0 +1,68 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys, os
import curses
print("Retrieve Keyboard, Mouse press/drag/wheel Events")
print("Press q or <ESC> to exit")
def reset():
# Reset
sys.stdout.write("\033[?1000l")
sys.stdout.write("\033[?1002l")
sys.stdout.write("\033[?1015l")
sys.stdout.write("\033[?1006l")
sys.stdout.write("\033[?1049l") # Switch to normal screen
sys.stdout.write("\033[?2004l") # Paste Bracketed mode
sys.stdout.flush()
stdscr = curses.initscr()
curses.curs_set(0)
stdscr.keypad(1)
curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION)
curses.mouseinterval(0)
print('\033[?1003h')
# reset()
while True:
c = stdscr.getch()
print(f"{c=}\r")
if c == ord('q'):
break # Exit the while loop
elif c == curses.KEY_HOME:
print("HOME\r")
elif c == curses.KEY_MOUSE:
m = curses.getmouse()
y, x = stdscr.getyx()
print(f"Mouse {m=} {(x,y)=}\r")
elif c == curses.KEY_RESIZE:
print(f"Resize\r")
print('\033[?1003l')
curses.endwin()
curses.flushinp()

17
tests/test.input.py

@ -26,7 +26,8 @@ import sys, os
import logging
sys.path.append(os.path.join(sys.path[0],'..'))
from TermTk import TTkLog, TTkK, TTkInput, TTkTerm
from TermTk import TTkLog, TTkK, TTkTerm
from TermTk.TTkCore.TTkTerm.input import TTkInput
def message_handler(mode, context, message):
log = logging.debug
@ -54,7 +55,6 @@ def winCallback(width, height):
TTkTerm.registerResizeCb(winCallback)
input = TTkInput()
def keyCallback(kevt=None, mevt=None):
if mevt is not None:
@ -65,7 +65,7 @@ def keyCallback(kevt=None, mevt=None):
else:
TTkLog.info(f"Key Event: Special '{kevt}'")
if kevt.key == "q":
input.close()
TTkInput.close()
return False
return True
@ -73,11 +73,12 @@ def pasteCallback(txt:str):
TTkLog.info(f"PASTE = {txt}")
return True
input.inputEvent.connect(keyCallback)
input.pasteEvent.connect(pasteCallback)
TTkInput.inputEvent.connect(keyCallback)
TTkInput.pasteEvent.connect(pasteCallback)
TTkInput.init(mouse=True, directMouse=True)
# TTkInput.init(mouse=True, directMouse=False)
try:
input.start()
TTkInput.start()
finally:
TTkTerm.push(TTkTerm.Mouse.OFF + TTkTerm.Mouse.DIRECT_OFF)
TTkInput.close()
TTkTerm.setEcho(True)

16
tests/test.input.raw.py

@ -48,17 +48,13 @@ def reset():
reset()
# TTkTerm.push("\033[?2004h") # Paste Bracketed mode
TTkTerm.push("\033[?2004l") # disable Paste Bracketed mode
TTkTerm.push("\033[?1049h") # Switch to alternate screen
TTkTerm.push("\033[?1000h")
# TTkTerm.push("\033[?1002h")
TTkTerm.push("\033[?1003h")
# TTkTerm.push("\033[?1006h")
TTkTerm.push("\033[?1015h")
TTkTerm.push("\033[?2004h") # Paste Bracketed mode
# TTkTerm.push("\033[?1000h")
TTkTerm.push("\033[?1002h")
# TTkTerm.push("\033[?1003h")
TTkTerm.push("\033[?1006h")
TTkTerm.push("\033[?25l")
# TTkTerm.push("\033[?1015h")
# TTkTerm.push("\033[?1049h") # Switch to alternate screen
# TTkTerm.push(TTkTerm.Mouse.ON)
# TTkTerm.push(TTkTerm.Mouse.DIRECT_ON)
TTkTerm.setEcho(False)

393
tests/test.input.win.01.py

@ -0,0 +1,393 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys
from ctypes import Structure, Union, byref, wintypes, windll
# Example ported from:
# https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events
# https://learn.microsoft.com/en-us/windows/console/getstdhandle
STD_INPUT_HANDLE = wintypes.DWORD(-10) # The standard input device. Initially, this is the console input buffer, CONIN$.
STD_OUTPUT_HANDLE = wintypes.DWORD(-11) # The standard output device. Initially, this is the active console screen buffer, CONOUT$.
STD_ERROR_HANDLE = wintypes.DWORD(-12) # The standard error device. Initially, this is the active console screen buffer, CONOUT$.
INVALID_HANDLE_VALUE = -1 # WinBase.h
# https://learn.microsoft.com/en-us/windows/console/SetConsoleMode
ENABLE_ECHO_INPUT = 0x0004 # Characters read by the ReadFile or ReadConsole function are written to the active screen buffer as they are typed into the console. This mode can be used only if the ENABLE_LINE_INPUT mode is also enabled.
ENABLE_INSERT_MODE = 0x0020 # When enabled, text entered in a console window will be inserted at the current cursor location and all text following that location will not be overwritten. When disabled, all following text will be overwritten.
ENABLE_LINE_INPUT = 0x0002 # The ReadFile or ReadConsole function returns only when a carriage return character is read. If this mode is disabled, the functions return when one or more characters are available.
ENABLE_MOUSE_INPUT = 0x0010 # If the mouse pointer is within the borders of the console window and the window has the keyboard focus, mouse events generated by mouse movement and button presses are placed in the input buffer. These events are discarded by ReadFile or ReadConsole, even when this mode is enabled. The ReadConsoleInput function can be used to read MOUSE_EVENT input records from the input buffer.
ENABLE_PROCESSED_INPUT = 0x0001 # CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer is being read by ReadFile or ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and line feed characters are handled by the system.
ENABLE_QUICK_EDIT_MODE = 0x0040 # This flag enables the user to use the mouse to select and edit text. To enable this mode, use ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS. To disable this mode, use ENABLE_EXTENDED_FLAGS without this flag.
ENABLE_WINDOW_INPUT = 0x0008 # User interactions that change the size of the console screen buffer are reported in the console's input buffer. Information about these events can be read from the input buffer by applications using the ReadConsoleInput function, but not by those using ReadFile or ReadConsole.
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 # Setting this flag directs the Virtual Terminal processing engine to convert user input received by the console window into Console Virtual Terminal Sequences that can be retrieved by a supporting application through ReadFile or ReadConsole functions.
ENABLE_PROCESSED_OUTPUT = 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
# https://learn.microsoft.com/en-us/windows/console/input-record-str
FOCUS_EVENT = 0x0010 # The Event member contains a FOCUS_EVENT_RECORD structure. These events are used internally and should be ignored.
KEY_EVENT = 0x0001 # The Event member contains a KEY_EVENT_RECORD structure with information about a keyboard event.
MENU_EVENT = 0x0008 # The Event member contains a MENU_EVENT_RECORD structure. These events are used internally and should be ignored.
MOUSE_EVENT = 0x0002 # The Event member contains a MOUSE_EVENT_RECORD structure with information about a mouse movement or button press event.
WINDOW_BUFFER_SIZE_EVENT = 0x0004 # The Event member contains a WINDOW_BUFFER_SIZE_RECORD structure with information about the new size of the console screen buffer.
# https://docs.microsoft.com/en-us/windows/console/coord-str
#
# typedef struct _COORD {
# SHORT X;
# SHORT Y;
# } COORD, *PCOORD;
class COORD(Structure):
_fields_ = [
("X", wintypes.SHORT),
("Y", wintypes.SHORT)]
# https://docs.microsoft.com/en-us/windows/console/key-event-record-str
#
# typedef struct _KEY_EVENT_RECORD {
# BOOL bKeyDown;
# WORD wRepeatCount;
# WORD wVirtualKeyCode;
# WORD wVirtualScanCode;
# union {
# WCHAR UnicodeChar;
# CHAR AsciiChar;
# } uChar;
# DWORD dwControlKeyState;
# } KEY_EVENT_RECORD;
class KEY_EVENT_RECORD(Structure):
class _uChar(Union):
_fields_ = [
("UnicodeChar", wintypes.WCHAR) ,
("AsciiChar" , wintypes.CHAR ) ]
_fields_ = [
("bKeyDown" , wintypes.BOOL ),
("wRepeatCount" , wintypes.WORD ),
("wVirtualKeyCode" , wintypes.WORD ),
("wVirtualScanCode" , wintypes.WORD ),
("uChar" , _uChar ),
("dwControlKeyState", wintypes.DWORD)]
# https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
#
# typedef struct _MOUSE_EVENT_RECORD {
# COORD dwMousePosition;
# DWORD dwButtonState;
# DWORD dwControlKeyState;
# DWORD dwEventFlags;
# } MOUSE_EVENT_RECORD;
class MOUSE_EVENT_RECORD(Structure):
_fields_ = [
("dwMousePosition" , COORD),
("dwButtonState" , wintypes.DWORD),
("dwControlKeyState", wintypes.DWORD),
("dwEventFlags" , wintypes.DWORD)]
# https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str
#
# typedef struct _WINDOW_BUFFER_SIZE_RECORD {
# COORD dwSize;
# } WINDOW_BUFFER_SIZE_RECORD;
class WINDOW_BUFFER_SIZE_RECORD(Structure):
_fields_ = [("dwSize", COORD)]
# https://docs.microsoft.com/en-us/windows/console/menu-event-record-str
#
# typedef struct _MENU_EVENT_RECORD {
# UINT dwCommandId;
# } MENU_EVENT_RECORD, *PMENU_EVENT_RECORD;
class MENU_EVENT_RECORD(Structure):
_fields_ = [("dwCommandId", wintypes.UINT)]
# https://docs.microsoft.com/en-us/windows/console/focus-event-record-str
#
# typedef struct _FOCUS_EVENT_RECORD {
# BOOL bSetFocus;
# } FOCUS_EVENT_RECORD;
class FOCUS_EVENT_RECORD(Structure):
_fields_ = [("bSetFocus", wintypes.BOOL)]
# https://docs.microsoft.com/en-us/windows/console/input-record-str
#
# typedef struct _INPUT_RECORD {
# WORD EventType;
# union {
# KEY_EVENT_RECORD KeyEvent;
# MOUSE_EVENT_RECORD MouseEvent;
# WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
# MENU_EVENT_RECORD MenuEvent;
# FOCUS_EVENT_RECORD FocusEvent;
# } Event;
# } INPUT_RECORD;
class INPUT_RECORD(Structure):
class _Event(Union):
_fields_ = [
("KeyEvent" , KEY_EVENT_RECORD ),
("MouseEvent" , MOUSE_EVENT_RECORD ),
("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
("MenuEvent" , MENU_EVENT_RECORD ),
("FocusEvent" , FOCUS_EVENT_RECORD )]
_fields_ = [
("EventType", wintypes.WORD),
("Event" , _Event )]
def reset():
# Reset
sys.stdout.write("\033[?1000l")
# sys.stdout.write("\033[?1002l")
sys.stdout.write("\033[?1003l")
sys.stdout.write("\033[?1006l")
sys.stdout.write("\033[?1015l")
# sys.stdout.write("\033[?1049l") # Switch to normal screen
# sys.stdout.write("\033[?2004l") # Paste Bracketed mode
sys.stdout.flush()
def init():
sys.stdout.write("\x1b[?1000h")
sys.stdout.write("\x1b[?1003h")
sys.stdout.write("\x1b[?1006h")
sys.stdout.write("\x1b[?1015h")
sys.stdout.flush()
# DWORD cNumRead, fdwMode, i;
# INPUT_RECORD irInBuf[128];
# int counter=0;
# // Get the standard input handle.
#
# hStdIn = GetStdHandle(STD_INPUT_HANDLE);
# if (hStdIn == INVALID_HANDLE_VALUE)
# ErrorExit("GetStdHandle");
#
# From:
# https://learn.microsoft.com/en-us/windows/console/getstdhandle
#
# HANDLE WINAPI GetStdHandle(
# _In_ DWORD nStdHandle
# );
GetStdHandle = windll.kernel32.GetStdHandle
GetStdHandle.argtypes = [wintypes.DWORD]
GetStdHandle.restype = wintypes.HANDLE
hStdIn = GetStdHandle(STD_INPUT_HANDLE)
if hStdIn == INVALID_HANDLE_VALUE:
raise Exception("GetStdHandle")
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE)
if hStdOut == INVALID_HANDLE_VALUE:
raise Exception("GetStdHandle")
# // Save the current input mode, to be restored on exit.
#
# if (! GetConsoleMode(hStdIn, &fdwSaveOldModeIn) )
# ErrorExit("GetConsoleMode");
#
# From:
# https://learn.microsoft.com/en-us/windows/console/GetConsoleMode
#
# BOOL WINAPI GetConsoleMode(
# _In_ HANDLE hConsoleHandle,
# _Out_ LPDWORD lpMode
# );
GetConsoleMode = windll.kernel32.GetConsoleMode
GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]
GetConsoleMode.restype = wintypes.BOOL
fdwSaveOldModeIn = wintypes.DWORD()
if not GetConsoleMode(hStdIn, byref(fdwSaveOldModeIn)):
raise Exception("GetConsoleMode")
fdwSaveOldModeOut = wintypes.DWORD()
if not GetConsoleMode(hStdOut, byref(fdwSaveOldModeOut)):
raise Exception("GetConsoleMode")
print(f"{fdwSaveOldModeIn.value=:02x}")
print(f"{fdwSaveOldModeOut.value=:02x}")
# // Enable the window and mouse input events.
#
# fdwMode = ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT;
# if (! SetConsoleMode(hStdIn, fdwMode) )
# ErrorExit("SetConsoleMode");
#
# From:
# https://learn.microsoft.com/en-us/windows/console/SetConsoleMode
#
# BOOL WINAPI SetConsoleMode(
# _In_ HANDLE hConsoleHandle,
# _In_ DWORD dwMode
# );
SetConsoleMode = windll.kernel32.SetConsoleMode
SetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.DWORD]
SetConsoleMode.restype = wintypes.BOOL
fdwModeIn = ENABLE_VIRTUAL_TERMINAL_INPUT
# fdwModeIn = 0x0218
if not SetConsoleMode(hStdIn, fdwModeIn):
raise Exception("SetConsoleMode")
fdwModeOut = fdwSaveOldModeOut.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING
# fdwModeIn = 0x0218
if not SetConsoleMode(hStdOut, fdwModeOut):
raise Exception("SetConsoleMode")
print(f"{fdwModeIn=:02x}")
print(f"{fdwModeOut=:02x}")
init()
# From:
# https://learn.microsoft.com/en-us/windows/console/ReadConsoleInput
#
# BOOL WINAPI ReadConsoleInput(
# _In_ HANDLE hConsoleInput,
# _Out_ PINPUT_RECORD lpBuffer,
# _In_ DWORD nLength,
# _Out_ LPDWORD lpNumberOfEventsRead
# );
ReadConsoleInput = windll.kernel32.ReadConsoleInputW # Unicode
# ReadConsoleInput = windll.kernel32.ReadConsoleInputA # ANSII
# ReadConsoleInput.argtypes = [wintypes.HANDLE,
# wintypes.LPINT,
# wintypes.DWORD,
# wintypes.LPWORD]
ReadConsoleInput.restype = wintypes.BOOL
# DWORD cNumRead;
# INPUT_RECORD irInBuf[128];
cNumRead = wintypes.DWORD(0)
irInBuf = (INPUT_RECORD * 256)()
# // Loop to read and handle the next 100 input events.
#
# while (counter++ <= 100)
# {
for _ in range(50):
# // Wait for the events.
#
# if (! ReadConsoleInput(
# hStdIn, // input buffer handle
# irInBuf, // buffer to read into
# 128, // size of read buffer
# &cNumRead) ) // number of records read
# ErrorExit("ReadConsoleInput");
if not ReadConsoleInput(
hStdIn, # input buffer handle
byref(irInBuf), # buffer to read into
256, # size of read buffer
byref(cNumRead)): # number of records read
raise Exception("ReadConsoleInput")
# print(f"{hStdIn=} {irInBuf=} {cNumRead=}")
print(f"{cNumRead=}")
# // Dispatch the events to the appropriate handler.
#
# for (i = 0; i < cNumRead; i++)
# {
for bb in irInBuf[:cNumRead.value]:
# if not bb.EventType: continue
print(f"{bb=} {bb.EventType=}")
# switch(irInBuf[i].EventType)
# {
# case KEY_EVENT: // keyboard input
# KeyEventProc(irInBuf[i].Event.KeyEvent);
# break;
if bb.EventType == KEY_EVENT:
print(f"{bb.Event.KeyEvent=}")
print(f"{bb.Event.KeyEvent.bKeyDown=}")
print(f"{bb.Event.KeyEvent.wRepeatCount=}")
print(f"{bb.Event.KeyEvent.wVirtualKeyCode=}")
print(f"{bb.Event.KeyEvent.wVirtualScanCode=}")
print(f"{bb.Event.KeyEvent.uChar.UnicodeChar=}")
print(f"{bb.Event.KeyEvent.uChar.AsciiChar=}")
print(f"{bb.Event.KeyEvent.dwControlKeyState=}")
# case MOUSE_EVENT: // mouse input
# MouseEventProc(irInBuf[i].Event.MouseEvent);
# break;
elif bb.EventType == MOUSE_EVENT:
print(f"{bb.Event.MouseEvent=}")
print(f"{bb.Event.MouseEvent.dwMousePosition.X=}")
print(f"{bb.Event.MouseEvent.dwMousePosition.Y=}")
print(f"{bb.Event.MouseEvent.dwButtonState=}")
print(f"{bb.Event.MouseEvent.dwControlKeyState=}")
print(f"{bb.Event.MouseEvent.dwEventFlags=}")
# case WINDOW_BUFFER_SIZE_EVENT: // scrn buf. resizing
# ResizeEventProc( irInBuf[i].Event.WindowBufferSizeEvent );
# break;
elif bb.EventType == WINDOW_BUFFER_SIZE_EVENT:
print(f"{bb.Event.WindowBufferSizeEvent=}")
print(f"{bb.Event.WindowBufferSizeEvent.dwSize.X=}")
print(f"{bb.Event.WindowBufferSizeEvent.dwSize.Y=}")
# case FOCUS_EVENT: // disregard focus events
#
# case MENU_EVENT: // disregard menu events
# break;
#
# default:
# ErrorExit("Unknown event type");
# break;
# }
# }
# }
# // Restore input mode on exit.
#
# SetConsoleMode(hStdIn, fdwSaveOldModeIn);
if not SetConsoleMode(hStdIn, fdwSaveOldModeIn):
raise Exception("SetConsoleMode")
if not SetConsoleMode(hStdOut, fdwSaveOldModeOut):
raise Exception("SetConsoleMode")
# return 0;
#
reset()
print('OK')

436
tests/test.input.win.02.py

@ -0,0 +1,436 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys,os
from ctypes import Structure, Union, byref, wintypes, windll
sys.path.append(os.path.join(sys.path[0],'..'))
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
# Example ported from:
# https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events
# https://learn.microsoft.com/en-us/windows/console/getstdhandle
STD_INPUT_HANDLE = wintypes.DWORD(-10) # The standard input device. Initially, this is the console input buffer, CONIN$.
STD_OUTPUT_HANDLE = wintypes.DWORD(-11) # The standard output device. Initially, this is the active console screen buffer, CONOUT$.
STD_ERROR_HANDLE = wintypes.DWORD(-12) # The standard error device. Initially, this is the active console screen buffer, CONOUT$.
INVALID_HANDLE_VALUE = -1 # WinBase.h
# https://learn.microsoft.com/en-us/windows/console/SetConsoleMode
ENABLE_ECHO_INPUT = 0x0004 # Characters read by the ReadFile or ReadConsole function are written to the active screen buffer as they are typed into the console. This mode can be used only if the ENABLE_LINE_INPUT mode is also enabled.
ENABLE_INSERT_MODE = 0x0020 # When enabled, text entered in a console window will be inserted at the current cursor location and all text following that location will not be overwritten. When disabled, all following text will be overwritten.
ENABLE_LINE_INPUT = 0x0002 # The ReadFile or ReadConsole function returns only when a carriage return character is read. If this mode is disabled, the functions return when one or more characters are available.
ENABLE_MOUSE_INPUT = 0x0010 # If the mouse pointer is within the borders of the console window and the window has the keyboard focus, mouse events generated by mouse movement and button presses are placed in the input buffer. These events are discarded by ReadFile or ReadConsole, even when this mode is enabled. The ReadConsoleInput function can be used to read MOUSE_EVENT input records from the input buffer.
ENABLE_PROCESSED_INPUT = 0x0001 # CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer is being read by ReadFile or ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and line feed characters are handled by the system.
ENABLE_QUICK_EDIT_MODE = 0x0040 # This flag enables the user to use the mouse to select and edit text. To enable this mode, use ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS. To disable this mode, use ENABLE_EXTENDED_FLAGS without this flag.
ENABLE_WINDOW_INPUT = 0x0008 # User interactions that change the size of the console screen buffer are reported in the console's input buffer. Information about these events can be read from the input buffer by applications using the ReadConsoleInput function, but not by those using ReadFile or ReadConsole.
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 # Setting this flag directs the Virtual Terminal processing engine to convert user input received by the console window into Console Virtual Terminal Sequences that can be retrieved by a supporting application through ReadFile or ReadConsole functions.
ENABLE_PROCESSED_OUTPUT = 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
# https://learn.microsoft.com/en-us/windows/console/input-record-str
FOCUS_EVENT = 0x0010 # The Event member contains a FOCUS_EVENT_RECORD structure. These events are used internally and should be ignored.
KEY_EVENT = 0x0001 # The Event member contains a KEY_EVENT_RECORD structure with information about a keyboard event.
MENU_EVENT = 0x0008 # The Event member contains a MENU_EVENT_RECORD structure. These events are used internally and should be ignored.
MOUSE_EVENT = 0x0002 # The Event member contains a MOUSE_EVENT_RECORD structure with information about a mouse movement or button press event.
WINDOW_BUFFER_SIZE_EVENT = 0x0004 # The Event member contains a WINDOW_BUFFER_SIZE_RECORD structure with information about the new size of the console screen buffer.
# https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str
# dwButtonState
FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001 # The leftmost mouse button.
FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004 # The second button fom the left.
FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008 # The third button from the left.
FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010 # The fourth button from the left.
RIGHTMOST_BUTTON_PRESSED = 0x0002 # The rightmost mouse button.
# dwControlKeyState
CAPSLOCK_ON = 0x0080 # The CAPS LOCK light is on.
ENHANCED_KEY = 0x0100 # The key is enhanced. See remarks.
LEFT_ALT_PRESSED = 0x0002 # The left ALT key is pressed.
LEFT_CTRL_PRESSED = 0x0008 # The left CTRL key is pressed.
NUMLOCK_ON = 0x0020 # The NUM LOCK light is on.
RIGHT_ALT_PRESSED = 0x0001 # The right ALT key is pressed.
RIGHT_CTRL_PRESSED = 0x0004 # The right CTRL key is pressed.
SCROLLLOCK_ON = 0x0040 # The SCROLL LOCK light is on.
SHIFT_PRESSED = 0x0010 # The SHIFT key is pressed.
# dwEventFlags
DOUBLE_CLICK = 0x0002 # The second click (button press) of a double-click occurred. The first click is returned as a regular button-press event.
MOUSE_HWHEELED = 0x0008 # The horizontal mouse wheel was moved.
# If the high word of the dwButtonState member contains a positive value, the wheel was rotated to the right. Otherwise, the wheel was rotated to the left.
MOUSE_MOVED = 0x0001 # A change in mouse position occurred.
MOUSE_WHEELED = 0x0004 # The vertical mouse wheel was moved.
# If the high word of the dwButtonState member contains a positive value, the wheel was rotated forward, away from the user. Otherwise, the wheel was rotated backward, toward the user.
# https://docs.microsoft.com/en-us/windows/console/coord-str
#
# typedef struct _COORD {
# SHORT X;
# SHORT Y;
# } COORD, *PCOORD;
class COORD(Structure):
_fields_ = [
("X", wintypes.SHORT),
("Y", wintypes.SHORT)]
# https://docs.microsoft.com/en-us/windows/console/key-event-record-str
#
# typedef struct _KEY_EVENT_RECORD {
# BOOL bKeyDown;
# WORD wRepeatCount;
# WORD wVirtualKeyCode;
# WORD wVirtualScanCode;
# union {
# WCHAR UnicodeChar;
# CHAR AsciiChar;
# } uChar;
# DWORD dwControlKeyState;
# } KEY_EVENT_RECORD;
class KEY_EVENT_RECORD(Structure):
class _uChar(Union):
_fields_ = [
("UnicodeChar", wintypes.WCHAR) ,
("AsciiChar" , wintypes.CHAR ) ]
_fields_ = [
("bKeyDown" , wintypes.BOOL ),
("wRepeatCount" , wintypes.WORD ),
("wVirtualKeyCode" , wintypes.WORD ),
("wVirtualScanCode" , wintypes.WORD ),
("uChar" , _uChar ),
("dwControlKeyState", wintypes.DWORD)]
# https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
#
# typedef struct _MOUSE_EVENT_RECORD {
# COORD dwMousePosition;
# DWORD dwButtonState;
# DWORD dwControlKeyState;
# DWORD dwEventFlags;
# } MOUSE_EVENT_RECORD;
class MOUSE_EVENT_RECORD(Structure):
_fields_ = [
("dwMousePosition" , COORD),
("dwButtonState" , wintypes.DWORD),
("dwControlKeyState", wintypes.DWORD),
("dwEventFlags" , wintypes.DWORD)]
# https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str
#
# typedef struct _WINDOW_BUFFER_SIZE_RECORD {
# COORD dwSize;
# } WINDOW_BUFFER_SIZE_RECORD;
class WINDOW_BUFFER_SIZE_RECORD(Structure):
_fields_ = [("dwSize", COORD)]
# https://docs.microsoft.com/en-us/windows/console/menu-event-record-str
#
# typedef struct _MENU_EVENT_RECORD {
# UINT dwCommandId;
# } MENU_EVENT_RECORD, *PMENU_EVENT_RECORD;
class MENU_EVENT_RECORD(Structure):
_fields_ = [("dwCommandId", wintypes.UINT)]
# https://docs.microsoft.com/en-us/windows/console/focus-event-record-str
#
# typedef struct _FOCUS_EVENT_RECORD {
# BOOL bSetFocus;
# } FOCUS_EVENT_RECORD;
class FOCUS_EVENT_RECORD(Structure):
_fields_ = [("bSetFocus", wintypes.BOOL)]
# https://docs.microsoft.com/en-us/windows/console/input-record-str
#
# typedef struct _INPUT_RECORD {
# WORD EventType;
# union {
# KEY_EVENT_RECORD KeyEvent;
# MOUSE_EVENT_RECORD MouseEvent;
# WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
# MENU_EVENT_RECORD MenuEvent;
# FOCUS_EVENT_RECORD FocusEvent;
# } Event;
# } INPUT_RECORD;
class INPUT_RECORD(Structure):
class _Event(Union):
_fields_ = [
("KeyEvent" , KEY_EVENT_RECORD ),
("MouseEvent" , MOUSE_EVENT_RECORD ),
("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
("MenuEvent" , MENU_EVENT_RECORD ),
("FocusEvent" , FOCUS_EVENT_RECORD )]
_fields_ = [
("EventType", wintypes.WORD),
("Event" , _Event )]
def reset():
# Reset
sys.stdout.write("\033[?1000l")
# sys.stdout.write("\033[?1002l")
sys.stdout.write("\033[?1003l")
sys.stdout.write("\x1b[?1004l") # UnSet Bracheted Paste Mode
sys.stdout.write("\033[?1006l")
sys.stdout.write("\033[?1015l")
# sys.stdout.write("\033[?1049l") # Switch to normal screen
# sys.stdout.write("\033[?2004l") # Paste Bracketed mode
sys.stdout.flush()
def init():
sys.stdout.write("\x1b[?1000h")
sys.stdout.write("\x1b[?1003h")
sys.stdout.write("\x1b[?1004h") # Set Bracheted Paste Mode
sys.stdout.write("\x1b[?1006h")
sys.stdout.write("\x1b[?1015h")
sys.stdout.flush()
# Get the standard input handle.
# From:
# https://learn.microsoft.com/en-us/windows/console/getstdhandle
#
# HANDLE WINAPI GetStdHandle(
# _In_ DWORD nStdHandle
# );
GetStdHandle = windll.kernel32.GetStdHandle
GetStdHandle.argtypes = [wintypes.DWORD]
GetStdHandle.restype = wintypes.HANDLE
hStdIn = GetStdHandle(STD_INPUT_HANDLE)
if hStdIn == INVALID_HANDLE_VALUE:
raise Exception("GetStdHandle")
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE)
if hStdOut == INVALID_HANDLE_VALUE:
raise Exception("GetStdHandle")
# Save the current input mode, to be restored on exit.
# From:
# https://learn.microsoft.com/en-us/windows/console/GetConsoleMode
#
# BOOL WINAPI GetConsoleMode(
# _In_ HANDLE hConsoleHandle,
# _Out_ LPDWORD lpMode
# );
GetConsoleMode = windll.kernel32.GetConsoleMode
GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]
GetConsoleMode.restype = wintypes.BOOL
fdwSaveOldModeIn = wintypes.DWORD()
if not GetConsoleMode(hStdIn, byref(fdwSaveOldModeIn)):
raise Exception("GetConsoleMode")
fdwSaveOldModeOut = wintypes.DWORD()
if not GetConsoleMode(hStdOut, byref(fdwSaveOldModeOut)):
raise Exception("GetConsoleMode")
print(f"{fdwSaveOldModeIn.value=:02x}")
print(f"{fdwSaveOldModeOut.value=:02x}")
# Enable the window and mouse input events.
# From:
# https://learn.microsoft.com/en-us/windows/console/SetConsoleMode
#
# BOOL WINAPI SetConsoleMode(
# _In_ HANDLE hConsoleHandle,
# _In_ DWORD dwMode
# );
SetConsoleMode = windll.kernel32.SetConsoleMode
SetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.DWORD]
SetConsoleMode.restype = wintypes.BOOL
fdwModeIn = ENABLE_VIRTUAL_TERMINAL_INPUT
# fdwModeIn = ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT
# fdwModeIn = 0x0218
if not SetConsoleMode(hStdIn, fdwModeIn):
raise Exception("SetConsoleMode")
fdwModeOut = fdwSaveOldModeOut.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING
# fdwModeIn = 0x0218
if not SetConsoleMode(hStdOut, fdwModeOut):
raise Exception("SetConsoleMode")
print(f"{fdwModeIn=:02x}")
print(f"{fdwModeOut=:02x}")
init()
# From:
# https://learn.microsoft.com/en-us/windows/console/ReadConsoleInput
#
# BOOL WINAPI ReadConsoleInput(
# _In_ HANDLE hConsoleInput,
# _Out_ PINPUT_RECORD lpBuffer,
# _In_ DWORD nLength,
# _Out_ LPDWORD lpNumberOfEventsRead
# );
ReadConsoleInput = windll.kernel32.ReadConsoleInputW # Unicode
# ReadConsoleInput = windll.kernel32.ReadConsoleInputA # ANSII
# ReadConsoleInput.argtypes = [wintypes.HANDLE,
# wintypes.LPINT,
# wintypes.DWORD,
# wintypes.LPWORD]
ReadConsoleInput.restype = wintypes.BOOL
# DWORD cNumRead;
# INPUT_RECORD irInBuf[128];
cNumRead = wintypes.DWORD(0)
irInBuf = (INPUT_RECORD * 256)()
# Loop to read and handle the next 100 input events.
for _ in range(50):
# Wait for the events.
if not ReadConsoleInput(
hStdIn, # input buffer handle
byref(irInBuf), # buffer to read into
256, # size of read buffer
byref(cNumRead)): # number of records read
raise Exception("ReadConsoleInput")
# print(f"{hStdIn=} {irInBuf=} {cNumRead=}")
print(f"{cNumRead=}")
# Dispatch the events to the appropriate handler.
lastMousePress = 0
saveKeyb = b""
saveKeys = b""
listKeys = []
for bb in irInBuf[:cNumRead.value]:
# if not bb.EventType: continue
# print(f"{bb=} {bb.EventType=} {cNumRead.value=}")
if bb.EventType == MOUSE_EVENT:
x = bb.Event.MouseEvent.dwMousePosition.X
y = bb.Event.MouseEvent.dwMousePosition.Y
print(f"{bb.Event.MouseEvent.dwControlKeyState=}")
print(f"{bb.Event.MouseEvent.dwEventFlags=}")
bstate = bb.Event.MouseEvent.dwButtonState
cstate = bb.Event.MouseEvent.dwControlKeyState
key = TTkMouseEvent.NoButton
evt = TTkMouseEvent.Move
mod = TTkK.NoModifier
tap = 0
# Release the mouse
if not bstate and lastMousePress:
pass
# Ignore the input if another button is pressed while holding the previous
if lastMousePress and lastMousePress & bstate:
continue
# Release the mouse if another button is pressed
# while still holding the first one
if lastMousePress and lastMousePress != (bstate&lastMousePress):
pass
if cstate & CAPSLOCK_ON: pass
if cstate & ENHANCED_KEY: pass
if cstate & LEFT_ALT_PRESSED: mod |= TTkK.AltModifier
if cstate & LEFT_CTRL_PRESSED: mod |= TTkK.ControlModifier
if cstate & NUMLOCK_ON: pass
if cstate & RIGHT_ALT_PRESSED: mod |= TTkK.AltModifier
if cstate & RIGHT_CTRL_PRESSED: mod |= TTkK.ControlModifier
if cstate & SCROLLLOCK_ON: pass
if cstate & SHIFT_PRESSED: mod |= TTkK.ShiftModifier
# Exclude extra button pressed at the same time
if bstate & RIGHTMOST_BUTTON_PRESSED:
key = TTkMouseEvent.RightButton
lastMousePress = RIGHTMOST_BUTTON_PRESSED
elif bstate & FROM_LEFT_1ST_BUTTON_PRESSED:
key = TTkMouseEvent.LeftButton
lastMousePress = FROM_LEFT_1ST_BUTTON_PRESSED
elif bstate & FROM_LEFT_2ND_BUTTON_PRESSED:
key = TTkMouseEvent.MidButton
lastMousePress = FROM_LEFT_2ND_BUTTON_PRESSED
elif bstate & FROM_LEFT_3RD_BUTTON_PRESSED:
lastMousePress = 0 # FROM_LEFT_3RD_BUTTON_PRESSED
elif bstate & FROM_LEFT_4TH_BUTTON_PRESSED:
lastMousePress = 0 # FROM_LEFT_4TH_BUTTON_PRESSED
mevt = TTkMouseEvent(x, y, key, evt, mod, tap, "")
print(f"{str(mevt)=}")
elif bb.EventType == WINDOW_BUFFER_SIZE_EVENT:
print(f"{bb.Event.WindowBufferSizeEvent=}")
print(f"{bb.Event.WindowBufferSizeEvent.dwSize.X=}")
print(f"{bb.Event.WindowBufferSizeEvent.dwSize.Y=}")
elif bb.EventType == KEY_EVENT:
# if not bb.Event.KeyEvent.bKeyDown:
# saveKeys += bb.Event.KeyEvent.uChar.UnicodeChar
# saveKeyb += bb.Event.KeyEvent.uChar.AsciiChar
# listKeys.append()
key = bb.Event.KeyEvent.uChar.UnicodeChar
if bb.Event.KeyEvent.bKeyDown or key == "\x1b":
if (bb.Event.KeyEvent.dwControlKeyState
and bb.Event.KeyEvent.wVirtualKeyCode ):
continue
listKeys.append(key)
#continue
print(
# f" evt:{bb.Event.KeyEvent}" +
f"\tkd:{bb.Event.KeyEvent.bKeyDown}" +
f"\trc:{bb.Event.KeyEvent.wRepeatCount}" +
f"\tVKC:{bb.Event.KeyEvent.wVirtualKeyCode}" +
f"\tVSC:{bb.Event.KeyEvent.wVirtualScanCode}" +
f"\tUC:{ord(bb.Event.KeyEvent.uChar.UnicodeChar):x}" +
f"\tAC:{bb.Event.KeyEvent.uChar.AsciiChar}" +
f"\tCKS:{bb.Event.KeyEvent.dwControlKeyState} -> {listKeys=}"
)
print(f"{listKeys=}")
kk = "".join(listKeys)
print(f"{kk=}")
kk = kk.encode("utf-16", "surrogatepass")
print(f"{kk=}")
kk = kk.decode("utf-16")
print(f"{kk=}")
# Restore input mode on exit.
if not SetConsoleMode(hStdIn, fdwSaveOldModeIn):
raise Exception("SetConsoleMode")
if not SetConsoleMode(hStdOut, fdwSaveOldModeOut):
raise Exception("SetConsoleMode")
# return 0;
#
reset()
print('OK')

89
tests/test.pty.006.terminal.04.py

@ -0,0 +1,89 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the"Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED"AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# This test is based on:
# pyte - In memory VTXXX-compatible terminal emulator.
# Terminal Emulator Example
# https://github.com/selectel/pyte/blob/master/examples/terminal_emulator.py
#
# pty — Pseudo-terminal utilities¶
# https://docs.python.org/3/library/pty.html#example
#
# Using a pseudo-terminal to interact with interactive Python in a subprocess
# by Thomas Billinger
# https://gist.github.com/thomasballinger/7979808
#
# Run interactive Bash with popen and a dedicated TTY Python
# https://stackoverflow.com/questions/41542960/run-interactive-bash-with-popen-and-a-dedicated-tty-python
import os
import pty
import sys
import threading
import argparse
from select import select
sys.path.append(os.path.join(sys.path[0],'..'))
import TermTk as ttk
parser = argparse.ArgumentParser()
parser.add_argument('-d', help='Debug (Add LogViewer Panel)', action='store_true')
args = parser.parse_args()
# ttk.TTkLog.use_default_file_logging()
root = ttk.TTk(layout=ttk.TTkGridLayout(), mouseTrack=True)
split = ttk.TTkSplitter(parent=root, orientation=ttk.TTkK.VERTICAL)
split.addItem(top := ttk.TTkLayout())
if args.d:
split.addWidget(ttk.TTkLogViewer(follow=False ), title='Log', size=20)
quitBtn = ttk.TTkButton(text="QUIT", border=True)
quitBtn.clicked.connect(ttk.TTkHelper.quit)
cb_c = ttk.TTkCheckbox(pos=(0,3),size=(20,1), text="CTRL-C (VINTR) ", checked=ttk.TTkK.Checked)
cb_s = ttk.TTkCheckbox(pos=(0,4),size=(20,1), text="CTRL-S (VSTOP) ", checked=ttk.TTkK.Checked)
cb_z = ttk.TTkCheckbox(pos=(0,5),size=(20,1), text="CTRL-Z (VSUSP) ", checked=ttk.TTkK.Checked)
cb_q = ttk.TTkCheckbox(pos=(0,6),size=(20,1), text="CTRL-Q (VSTART)", checked=ttk.TTkK.Checked)
cb_c.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_C,x==ttk.TTkK.Checked))
cb_s.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_S,x==ttk.TTkK.Checked))
cb_z.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Z,x==ttk.TTkK.Checked))
cb_q.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Q,x==ttk.TTkK.Checked))
win = ttk.TTkWindow(pos=(10,0), size=(100,30), title="Terminallo n.2", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint|ttk.TTkK.WindowFlag.WindowCloseButtonHint)
term = ttk.TTkTerminal(parent=win)
term.bell.connect(lambda : ttk.TTkLog.debug("BELL!!! 🔔🔔🔔"))
term.titleChanged.connect(win.setTitle)
term.runShell()
term.terminalClosed.connect(win.close)
win.closed.connect(term.close)
top.addWidgets([quitBtn, cb_c, cb_s, cb_z, cb_q, win])
term.setFocus()
root.mainloop()

73
tests/test.pty.006.terminal.05.py

@ -0,0 +1,73 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the"Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED"AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os, sys, argparse
from select import select
sys.path.append(os.path.join(sys.path[0],'..'))
import TermTk as ttk
parser = argparse.ArgumentParser()
parser.add_argument('-d', help='Debug (Add LogViewer Panel)', action='store_true')
args = parser.parse_args()
# ttk.TTkLog.use_default_file_logging()
root = ttk.TTk(layout=ttk.TTkGridLayout(), mouseTrack=True)
split = ttk.TTkSplitter(parent=root, orientation=ttk.TTkK.VERTICAL)
split.addItem(top := ttk.TTkLayout())
if args.d:
split.addWidget(ttk.TTkLogViewer(follow=False ), title='Log', size=20)
quitBtn = ttk.TTkButton(text="QUIT", border=True)
quitBtn.clicked.connect(ttk.TTkHelper.quit)
cb_c = ttk.TTkCheckbox(pos=(0,3),size=(20,1), text="CTRL-C (VINTR) ", checked=ttk.TTkK.Checked)
cb_s = ttk.TTkCheckbox(pos=(0,4),size=(20,1), text="CTRL-S (VSTOP) ", checked=ttk.TTkK.Checked)
cb_z = ttk.TTkCheckbox(pos=(0,5),size=(20,1), text="CTRL-Z (VSUSP) ", checked=ttk.TTkK.Checked)
cb_q = ttk.TTkCheckbox(pos=(0,6),size=(20,1), text="CTRL-Q (VSTART)", checked=ttk.TTkK.Checked)
cb_c.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_C,x==ttk.TTkK.Checked))
cb_s.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_S,x==ttk.TTkK.Checked))
cb_z.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Z,x==ttk.TTkK.Checked))
cb_q.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Q,x==ttk.TTkK.Checked))
win = ttk.TTkWindow(pos=(10,0), size=(100,30), title="Terminallo n.2", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint|ttk.TTkK.WindowFlag.WindowCloseButtonHint)
term = ttk.TTkTerminal(parent=win)
term.bell.connect(lambda : ttk.TTkLog.debug("BELL!!! 🔔🔔🔔"))
term.titleChanged.connect(win.setTitle)
term.runShell()
term.terminalClosed.connect(win.close)
win.closed.connect(term.close)
winT = ttk.TTkWindow(pos=(20,10), size=(100,30), title="TextEdit", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint|ttk.TTkK.WindowFlag.WindowCloseButtonHint)
ttk.TTkTextEdit(parent=winT, readOnly=False, lineNumber=True)
top.addWidgets([quitBtn, cb_c, cb_s, cb_z, cb_q, win, winT])
term.setFocus()
root.mainloop()

0
tests/test.ui.014.list.py → tests/test.ui.014.list.01.py

112
tests/test.ui.014.list.03.py

@ -0,0 +1,112 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys, os, argparse, math, random
sys.path.append(os.path.join(sys.path[0],'..'))
import TermTk as ttk
zc1 = chr(0x07a6) # Zero width chars oަ
zc2 = chr(0x20D7) # Zero width chars o
zc3 = chr(0x065f) # Zero width chars oٟ
utfwords = [
f"--Zero{zc1}{zc2}{zc3}-1-", f"--Zero-2{zc1}{zc2}{zc3}-", f"--Ze{zc1}{zc2}{zc3}ro-3-", f"{zc1}{zc2}{zc3}--Zero-4-",
"d😮l😱r", "sit", "am😎t,", "c😱nsectetur", "t😜mpor", "inci😜di😜dunt", "u😜t", "l😜abore", "et", "d😜olore", "m😜a😜gna", "ali😜qua😜.", "Ut", "enim", "😜a😜d😜", "minim", "veniam,", "😜q😜uis", "😜nostrud", "exer😜c😜i😜tation", "ullamco", "labo😜ris", "n😜isi", "ut", "aliq😞ip", "e😜x😜", "ea", "comm😞do", "cons😿quat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "cul🙻a", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."]
words = ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit,", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."]
def getWord():
return random.choice(utfwords)
# return random.choice(words)
parser = argparse.ArgumentParser()
parser.add_argument('-t', help='Track Mouse', action='store_true')
args = parser.parse_args()
mouseTrack = args.t
root = ttk.TTk(title="pyTermTk List Demo", mouseTrack=mouseTrack)
# Define the main Layout
frame1 = ttk.TTkResizableFrame(parent=root, pos=( 0, 0), size=(30,30), title="Single List", border=0, layout=ttk.TTkVBoxLayout())
frame2 = ttk.TTkResizableFrame(parent=root, pos=(30, 0), size=(30,30), title="Multi List", border=0, layout=ttk.TTkVBoxLayout())
frame3 = ttk.TTkResizableFrame(parent=root, pos=(60, 0), size=(80,30), title="Log", border=0, layout=ttk.TTkVBoxLayout())
# Single Selection List
listWidgetSingle = ttk.TTkList(parent=frame1, maxWidth=40, minWidth=10)
# Multi Selection List
listWidgetMulti = ttk.TTkList(parent=frame2, maxWidth=40, minWidth=10, selectionMode=ttk.TTkK.MultiSelection)
# Log Viewer
label1 = ttk.TTkLabel(parent=root, pos=(10,30), text="[ list1 ]",maxHeight=2)
label2 = ttk.TTkLabel(parent=root, pos=(10,31), text="[ list2 ]",maxHeight=2)
ttk.TTkLogViewer(parent=frame3)#, border=True)
btn_mv1 = ttk.TTkButton(parent=root, pos=(0,30), text=" >> ")
btn_mv2 = ttk.TTkButton(parent=root, pos=(0,31), text=" << ")
btn_del = ttk.TTkButton(parent=root, pos=(0,32), text="Delete")
@ttk.pyTTkSlot(str)
def _listCallback1(label):
ttk.TTkLog.info(f'Clicked label1: "{label}"')
label1.setText(f'[ list1 ] clicked "{label}" - Selected: {[str(s) for s in listWidgetSingle.selectedLabels()]}')
@ttk.pyTTkSlot(str)
def _listCallback2(label):
ttk.TTkLog.info(f'Clicked label2: "{label}" - selected: {[str(s) for s in listWidgetMulti.selectedLabels()]}')
label2.setText(f'[ list2 ] clicked "{label}" - {[str(s) for s in listWidgetMulti.selectedLabels()]}')
@ttk.pyTTkSlot()
def _moveToRight2():
for i in listWidgetSingle.selectedItems().copy():
listWidgetSingle.removeItem(i)
listWidgetMulti.addItemAt(i,0)
@ttk.pyTTkSlot()
def _moveToLeft1():
for i in listWidgetMulti.selectedItems().copy():
listWidgetMulti.removeItem(i)
listWidgetSingle.addItemAt(i,0)
@ttk.pyTTkSlot()
def _delSelected():
items = listWidgetMulti.selectedItems()
listWidgetMulti.removeItems(items)
items = listWidgetSingle.selectedItems()
listWidgetSingle.removeItems(items)
btn_mv1.clicked.connect(_moveToRight2)
btn_mv2.clicked.connect(_moveToLeft1)
btn_del.clicked.connect(_delSelected)
# Connect the signals to the 2 slots defines
listWidgetSingle.textClicked.connect(_listCallback1)
listWidgetMulti.textClicked.connect(_listCallback2)
# populate the lists with random entries
for i in range(10):
listWidgetSingle.addItem(f"S-{i}) {getWord()} {getWord()}")
listWidgetMulti.addItem(f"M-{i}) {getWord()} {getWord()}")
root.mainloop()

112
tests/test.ui.014.list.04.py

@ -0,0 +1,112 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys, os, argparse, math, random
sys.path.append(os.path.join(sys.path[0],'..'))
import TermTk as ttk
zc1 = chr(0x07a6) # Zero width chars oަ
zc2 = chr(0x20D7) # Zero width chars o
zc3 = chr(0x065f) # Zero width chars oٟ
utfwords = [
f"--Zero{zc1}{zc2}{zc3}-1-", f"--Zero-2{zc1}{zc2}{zc3}-", f"--Ze{zc1}{zc2}{zc3}ro-3-", f"{zc1}{zc2}{zc3}--Zero-4-",
"d😮l😱r", "sit", "am😎t,", "c😱nsectetur", "t😜mpor", "inci😜di😜dunt", "u😜t", "l😜abore", "et", "d😜olore", "m😜a😜gna", "ali😜qua😜.", "Ut", "enim", "😜a😜d😜", "minim", "veniam,", "😜q😜uis", "😜nostrud", "exer😜c😜i😜tation", "ullamco", "labo😜ris", "n😜isi", "ut", "aliq😞ip", "e😜x😜", "ea", "comm😞do", "cons😿quat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "cul🙻a", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."]
words = ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit,", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."]
def getWord():
return random.choice(utfwords)
# return random.choice(words)
parser = argparse.ArgumentParser()
parser.add_argument('-t', help='Track Mouse', action='store_true')
args = parser.parse_args()
mouseTrack = args.t
root = ttk.TTk(title="pyTermTk List Demo", mouseTrack=mouseTrack)
# Define the main Layout
frame1 = ttk.TTkWindow(parent=root, pos=( 0, 0), size=(30,30), title="Single List", border=0, layout=ttk.TTkVBoxLayout())
frame2 = ttk.TTkWindow(parent=root, pos=(30, 0), size=(30,30), title="Multi List", border=0, layout=ttk.TTkVBoxLayout())
frame3 = ttk.TTkWindow(parent=root, pos=(60, 0), size=(80,30), title="Log", border=0, layout=ttk.TTkVBoxLayout())
# Single Selection List
listWidgetSingle = ttk.TTkList(parent=frame1, maxWidth=40, minWidth=10)
# Multi Selection List
listWidgetMulti = ttk.TTkList(parent=frame2, maxWidth=40, minWidth=10, selectionMode=ttk.TTkK.MultiSelection)
# Log Viewer
label1 = ttk.TTkLabel(parent=root, pos=(10,30), text="[ list1 ]",maxHeight=2)
label2 = ttk.TTkLabel(parent=root, pos=(10,31), text="[ list2 ]",maxHeight=2)
ttk.TTkLogViewer(parent=frame3)#, border=True)
btn_mv1 = ttk.TTkButton(parent=root, pos=(0,30), text=" >> ")
btn_mv2 = ttk.TTkButton(parent=root, pos=(0,31), text=" << ")
btn_del = ttk.TTkButton(parent=root, pos=(0,32), text="Delete")
@ttk.pyTTkSlot(str)
def _listCallback1(label):
ttk.TTkLog.info(f'Clicked label1: "{label}"')
label1.setText(f'[ list1 ] clicked "{label}" - Selected: {[str(s) for s in listWidgetSingle.selectedLabels()]}')
@ttk.pyTTkSlot(str)
def _listCallback2(label):
ttk.TTkLog.info(f'Clicked label2: "{label}" - selected: {[str(s) for s in listWidgetMulti.selectedLabels()]}')
label2.setText(f'[ list2 ] clicked "{label}" - {[str(s) for s in listWidgetMulti.selectedLabels()]}')
@ttk.pyTTkSlot()
def _moveToRight2():
for i in listWidgetSingle.selectedItems().copy():
listWidgetSingle.removeItem(i)
listWidgetMulti.addItemAt(i,0)
@ttk.pyTTkSlot()
def _moveToLeft1():
for i in listWidgetMulti.selectedItems().copy():
listWidgetMulti.removeItem(i)
listWidgetSingle.addItemAt(i,0)
@ttk.pyTTkSlot()
def _delSelected():
items = listWidgetMulti.selectedItems()
listWidgetMulti.removeItems(items)
items = listWidgetSingle.selectedItems()
listWidgetSingle.removeItems(items)
btn_mv1.clicked.connect(_moveToRight2)
btn_mv2.clicked.connect(_moveToLeft1)
btn_del.clicked.connect(_delSelected)
# Connect the signals to the 2 slots defines
listWidgetSingle.textClicked.connect(_listCallback1)
listWidgetMulti.textClicked.connect(_listCallback2)
# populate the lists with random entries
for i in range(10):
listWidgetSingle.addItem(f"S-{i}) {getWord()} {getWord()}")
listWidgetMulti.addItem(f"M-{i}) {getWord()} {getWord()}")
root.mainloop()

68
tests/timeit/21.weakref.01.py

@ -0,0 +1,68 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys, os, weakref
import timeit
from threading import Lock
class Foo():
def a(self,v):
return v+v*v
f1 = Foo()
f21 = Foo()
f22 = Foo()
a1 = f1.a
a21 = weakref.WeakMethod(f21.a)
a22 = weakref.WeakMethod(f22.a)
a31 = weakref.ref(_a31:=f21.a)
a32 = weakref.ref(_a32:=f22.a)
del f22,_a32
def test1(v=a1,ff=f1): return sum([ v(x) for x in range(100)])
def test2(v=a21,ff=f21): return sum([v()(x) if v() else 0 for x in range(100)])
def test3(v=a22,ff=f21): return sum([v()(x) if v() else 0 for x in range(100)])
def test4(v=a21,ff=f21): return sum([ _v(x) if (_v:=v()) else 0 for x in range(100)])
def test5(v=a22,ff=f21): return sum([ _v(x) if (_v:=v()) else 0 for x in range(100)])
def test6(v=a31,ff=f21): return sum([v()(x) if v() else 0 for x in range(100)])
def test7(v=a32,ff=f21): return sum([v()(x) if v() else 0 for x in range(100)])
def test8(v=a31,ff=f21): return sum([ _v(x) if (_v:=v()) else 0 for x in range(100)])
def test9(v=a32,ff=f21): return sum([ _v(x) if (_v:=v()) else 0 for x in range(100)])
loop = 10000
a = {}
iii = 1
while (testName := f'test{iii}') and (testName in globals()):
result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop)
# print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}")
print(f"test{iii:02}) | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}")
iii+=1

84
tests/timeit/22.queue.01.py

@ -0,0 +1,84 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys, os, weakref
import timeit
from queue import Queue
qu = Queue()
r = 100
def test1(q=qu):
ret = 0
for i in range(r):
ret += i
return ret
def test2(q=qu):
ret = 0
for i in range(r):
qu.put(i)
while x := q.get():
ret += x
return ret
def test3(q=qu):
ret = 0
ar = []
for i in range(r):
ar.append(i)
for x in ar:
ret += x
return ret
def test4(q=qu):
return sum(i for i in range(r))
def test5(q=qu):
ar = []
for i in range(r):
ar.append(i)
return sum(ar)
def test6(q=qu):
ret = 0
ar = []
for i in range(r):
ar.append(i)
while ar:
ret += ar.pop()
return ret
loop = 1000
a = {}
iii = 1
while (testName := f'test{iii}') and (testName in globals()):
result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop)
# print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}")
print(f"test{iii:02}) | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}")
iii+=1

0
tests/test.generic.006.weakref.01.py → tests/weakref/test.01.py

0
tests/test.generic.006.weakref.02.py → tests/weakref/test.02.py

0
tests/test.generic.006.weakref.03.py → tests/weakref/test.03.py

116
tests/weakref/test.04.gc.01.py

@ -0,0 +1,116 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Example inspired by
# https://stackoverflow.com/questions/39838793/python-object-is-being-referenced-by-an-object-i-cannot-find
import gc, weakref
class Foo(object):
__slots__ = ('a','b')
def __init__(self, a=1234) -> None:
self.a = a
self.b = lambda : self.a
def f(self):
return self.a
# def __del__(self):
# print(f"Deleted {self}")
def pobjs():
for i,o in enumerate(gc.get_objects()[-100:]):
ss = str(o)
if "Foo" in ss:
print(f" * {i} - {ss}")
v1 = {'b':2345}
print(f"\nStart {gc.isenabled()=}")
# print(f"{gc.set_debug(gc.DEBUG_LEAK)=}")
print("\n############# Phase 1 ##################")
foo = Foo(v1)
print(f"{gc.get_referents(foo)=}")
print(f"{gc.get_count()=}")
print(f"{foo.a=} - {foo.b=} - {foo.f()=}")
del foo
print(f"{gc.collect()=}")
print("\n############# Phase 2 ##################")
foo = Foo(v1)
bar = foo.a
print(f"{gc.get_referents(foo)=}")
print(f"{gc.get_count()=}")
print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar=}")
del foo
print(f"{gc.collect()=}")
print(f"{bar=}")
print("\n############# Phase 3 ##################")
foo = Foo(v1)
bar = foo.b
print(f"{gc.get_referents(foo)=}")
print(f"{gc.get_count()=}")
print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar()=}")
del foo
print(f"{gc.collect()=}")
print(f"{bar()=}")
print("\n############# Phase 4 ##################")
foo = Foo(v1)
bar = foo.b
print(f"{gc.get_referents(foo)=}")
print(f"{gc.get_referents(v1)=}")
print(f"{gc.get_count()=}")
print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar()=}")
del foo
pobjs()
print(f"{gc.collect()=}")
print(f"{bar()=}")
del bar
pobjs()
print(f"{gc.collect()=}")
pobjs()
print("\n############# Phase 5 ##################")
foo = Foo(v1)
bar = weakref.ref(foo.b)
xx = foo.f
baz = weakref.ref(xx)
print(f"{gc.get_referents(foo)=}")
print(f"{gc.get_referents(v1)=}")
print(f"{gc.get_count()=}")
print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar()()=}")
del foo
pobjs()
print(f"{gc.collect()=}")
print(f"{bar()() if bar() else None=}")
del bar
pobjs()
print(f"{gc.collect()=}")
pobjs()
print(f"{gc.garbage=}")
print(f"End {gc.get_count()=}")

88
tests/weakref/test.04.gc.02.py

@ -0,0 +1,88 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Example inspired by
# https://stackoverflow.com/questions/39838793/python-object-is-being-referenced-by-an-object-i-cannot-find
import gc, weakref, time
class Foo():
__slots__ = ('__weakref__','a','b')
def __init__(self, a=1234) -> None:
self.a = a
self.b = lambda : self.a
def f(self):
return self.a
# def __del__(self):
# print(f"Deleted {self}")
def pobjs():
for i,o in enumerate(gc.get_objects()[-100:]):
ss = str(o)
if "Foo" in ss:
print(f" * {i} - {ss}")
v1 = {'b':2345}
print(f"\nStart {gc.isenabled()=}")
# print(f"{gc.set_debug(gc.DEBUG_LEAK)=}")
def _gccb(phase,info):
print(f" ---> {gc.garbage=}")
print(f" ---> {phase=} {info=}")
# gc.callbacks.append(_gccb)
print("\n############# Phase 1 ##################")
foo = Foo(v1)
bar =foo.b
wrfoo = weakref.ref(foo)
wrbar = weakref.ref(bar)
wrf = weakref.WeakMethod(foo.f)
# print(f"{gc.get_referents(foo)=}")
# print(f"{gc.get_referrers(foo)=}")
# print(f"{gc.get_referents(v1)=}")
# print(f"{gc.get_referrers(v1)=}")
# print(f"{gc.get_count()=}")
print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar()=}")
print(f"{wrfoo()=} {wrbar()=} {wrf()=}")
del foo
print(f"{gc.collect()=}")
print(f"{bar()}")
# print(f"{gc.get_referents(v1)=}")
# print(f"{gc.get_referrers(v1)=}")
print(f"{wrfoo()=} {wrbar()=} {wrf()=}")
bar = None
print(f"{wrfoo()=} {wrbar()=} {wrf()=}")
time.sleep(4)
print(f"{wrfoo()=} {wrbar()=} {wrf()=}")
print(f"{gc.collect()=}")
print(f"{wrfoo()=} {wrbar()=} {wrf()=}")
print(f"{gc.garbage=}")
print(f"End {gc.get_count()=}")

105
tests/weakref/test.04.gc.03.py

@ -0,0 +1,105 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Example inspired by
# https://stackoverflow.com/questions/39838793/python-object-is-being-referenced-by-an-object-i-cannot-find
import gc, weakref, time
class Bar():
__slots__ = ('_foo')
def __init__(self, foo) -> None:
self._foo = foo
class Foo():
__slots__ = ('__weakref__','a','b','_bar')
def __init__(self, a=1234) -> None:
self._bar = Bar(self)
self.a = a
self.b = lambda : self.a
def f(self):
return self.a
# def __del__(self):
# print(f"Deleted {self}")
def pobjs():
for i,o in enumerate(gc.get_objects()[-100:]):
ss = str(o)
if "Foo" in ss:
print(f" * {i} - {ss}")
def _ref(o):
print(f"\n### -> Referents - {o}")
for i,r in enumerate(gc.get_referents(o)):
print(f" - {i} ) {r}")
print(f"\n### -> Referrers - {o}")
for i,r in enumerate(gc.get_referrers(o)):
print(f" - {i} ) {r}")
print("")
v1 = {'b':2345}
print(f"\nStart {gc.isenabled()=}")
# print(f"{gc.set_debug(gc.DEBUG_LEAK)=}")
def _gccb(phase,info):
print(f" ---> {gc.garbage=}")
print(f" ---> {phase=} {info=}")
# gc.callbacks.append(_gccb)
print("\n############# Phase 1 ##################")
foo = Foo(v1)
bar =foo.b
wrfoo = weakref.ref(foo)
wrbar = weakref.ref(bar)
wrf = weakref.WeakMethod(foo.f)
# print(f"{gc.get_referents(foo)=}")
# print(f"{gc.get_referrers(foo)=}")
# print(f"{gc.get_referents(v1)=}")
# print(f"{gc.get_referrers(v1)=}")
# print(f"{gc.get_count()=}")
_ref(foo)
print(f"{foo.a=} - {foo.b=} - {foo.f()=} - {bar()=}")
print(f"{wrfoo()=} {wrbar()=} {wrf()=}")
del foo
print(f"{gc.collect()=}")
print(f"{bar()}")
# print(f"{gc.get_referents(v1)=}")
# print(f"{gc.get_referrers(v1)=}")
print(f"{wrfoo()=} {wrbar()=} {wrf()=}")
bar = None
print(f"{wrfoo()=} {wrbar()=} {wrf()=}")
time.sleep(4)
print(f"{wrfoo()=} {wrbar()=} {wrf()=}")
print(f"{gc.collect()=}")
print(f"{wrfoo()=} {wrbar()=} {wrf()=}")
print(f"{gc.garbage=}")
print(f"End {gc.get_count()=}")

92
tests/weakref/test.05.TermTk.01.py

@ -0,0 +1,92 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys, os
import gc, weakref, time
sys.path.append(os.path.join(sys.path[0],'../..'))
sys.path.append(os.path.join(sys.path[0],'.'))
import TermTk as ttk
def pobjs():
for i,o in enumerate(gc.get_objects()[-100:]):
ss = str(o)
if "Foo" in ss:
print(f" * {i} - {ss}")
print(f"\nStart {gc.isenabled()=}")
def _gccb(phase,info):
print(f" ---> {gc.garbage=}")
print(f" ---> {phase=} {info=}")
# gc.callbacks.append(_gccb)
def _ref(o):
print(f"\n### -> Referents - {o}")
for i,r in enumerate(gc.get_referents(o)):
print(f" - {i} ) {r}")
print(f"\n### -> Referrers - {o}")
for i,r in enumerate(gc.get_referrers(o)):
print(f" - {i} ) {r}")
for ii,rr in enumerate(gc.get_referrers(r)):
print(f" | {ii} ) {rr}")
print("")
print("")
print("\n############# Phase 1 ##################")
# wid = ttk.TTkWidget()
wid = ttk.TTkButton()
# wid = ttk.TTkLabel()
# wid = ttk.TTkGraph()
# wid = ttk.TTkSpacer()
# wid = ttk.TTkSplitter()
# wid = ttk.TTkCanvas()
# sizef = wid.size
sizef = []
ttk.TTkHelper._updateWidget = set()
ttk.TTkHelper._updateBuffer = set()
wrwid = weakref.ref(wid)
# wrsizef = weakref.ref(sizef)
wrsizef = wrwid
# wrsizef2 = weakref.WeakMethod(wid.size)
wrsizef2 = wrwid
_ref(wid)
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
del wid
print(f"{gc.collect()=}")
# print(f"{sizef()}")
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
sizef = None
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
# time.sleep(4)
# print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
print(f"{gc.collect()=}")
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
print(f"{gc.garbage=}")
print(f"End {gc.get_count()=}")

105
tests/weakref/test.05.TermTk.02.py

@ -0,0 +1,105 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys, os
import gc, weakref, time
sys.path.append(os.path.join(sys.path[0],'../..'))
sys.path.append(os.path.join(sys.path[0],'.'))
import TermTk as ttk
def pobjs():
for i,o in enumerate(gc.get_objects()[-100:]):
ss = str(o)
if "Foo" in ss:
print(f" * {i} - {ss}")
print(f"\nStart {gc.isenabled()=}")
def _gccb(phase,info):
print(f" ---> {gc.garbage=}")
print(f" ---> {phase=} {info=}")
# gc.callbacks.append(_gccb)
def _ref(o):
print(f"\n### -> Referents - {o}")
for i,r in enumerate(gc.get_referents(o)):
print(f" - {i} ) {r}")
print(f"\n### -> Referrers - {o}")
for i,r in enumerate(gc.get_referrers(o)):
print(f" - {i} ) {r}")
for ii,rr in enumerate(gc.get_referrers(r)):
print(f" | {ii} ) {rr}")
print("")
print("")
class TestWid(ttk.TTkWidget):
__slots__ = ('_a','_b')
def __init__(self, *args, **kwargs):
self.setDefaultSize(kwargs, 10, 10)
super().__init__(*args, **kwargs)
self._b = ttk.pyTTkSignal(bool)
self.setFocusPolicy(ttk.TTkK.ClickFocus + ttk.TTkK.TabFocus)
def mousePressEvent(self, evt):
# TTkLog.debug(f"{self._text} Test Mouse {evt}")
self.update()
return True
def paintEvent(self, canvas):
canvas.fill(pos=(0,0), size=(2,2))
print("\n############# Phase 1 ##################")
# wid = ttk.TTkWidget()
wid = ttk.TTkButton()
# wid = ttk.TTkGraph()
# wid = ttk.TTkSpacer()
# wid = ttk.TTkSplitter()
# wid = TestWid()
# sizef = wid.size
sizef = []
wrwid = weakref.ref(wid)
# wrsizef = weakref.ref(sizef)
wrsizef = wrwid
# wrsizef2 = weakref.WeakMethod(wid.size)
wrsizef2 = wrwid
_ref(wid)
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
del wid
print(f"{gc.collect()=}")
# print(f"{sizef()}")
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
sizef = None
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
# time.sleep(4)
# print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
print(f"{gc.collect()=}")
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
print(f"{gc.garbage=}")
print(f"End {gc.get_count()=}")

95
tests/weakref/test.05.TermTk.03.signals.py

@ -0,0 +1,95 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys, os
import gc, weakref, time
sys.path.append(os.path.join(sys.path[0],'../..'))
sys.path.append(os.path.join(sys.path[0],'.'))
import TermTk as ttk
def pobjs():
for i,o in enumerate(gc.get_objects()[-100:]):
ss = str(o)
if "Foo" in ss:
print(f" * {i} - {ss}")
print(f"\nStart {gc.isenabled()=}")
def _gccb(phase,info):
print(f" ---> {gc.garbage=}")
print(f" ---> {phase=} {info=}")
# gc.callbacks.append(_gccb)
def _ref(o):
print(f"\n### -> Referents - {o}")
for i,r in enumerate(gc.get_referents(o)):
print(f" - {i} ) {r}")
print(f"\n### -> Referrers - {o}")
for i,r in enumerate(gc.get_referrers(o)):
print(f" - {i} ) {r}")
for ii,rr in enumerate(gc.get_referrers(r)):
print(f" | {ii} ) {rr}")
print("")
print("")
print("\n############# Phase 1 ##################")
root = ttk.TTkWidget()
# wid = ttk.TTkWidget()
wid = ttk.TTkButton()
# wid = ttk.TTkLabel()
# wid = ttk.TTkGraph()
# wid = ttk.TTkSpacer()
# wid = ttk.TTkSplitter()
# sizef = wid.size
sizef = []
root.closed.connect(wid.close)
ttk.TTkHelper._updateWidget = set()
ttk.TTkHelper._updateBuffer = set()
wrwid = weakref.ref(wid)
# wrsizef = weakref.ref(sizef)
wrsizef = wrwid
# wrsizef2 = weakref.WeakMethod(wid.size)
wrsizef2 = wrwid
_ref(wid)
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
del wid
print(f"{gc.collect()=}")
# print(f"{sizef()}")
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
sizef = None
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
# time.sleep(4)
# print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
print(f"{gc.collect()=}")
print(f"{wrwid()=} {wrsizef()=} {wrsizef2()=}")
print(f"{gc.garbage=}")
print(f"End {gc.get_count()=}")

37
tools/check.import.sh

@ -14,18 +14,11 @@ __check(){
-e "log.py:import inspect" \
-e "log.py:import logging" \
-e "log.py:from collections.abc import Callable, Set" \
-e "from time" -e "input.py:import platform" \
-e "readinputlinux.py:import sys, os" \
-e "readinputlinux.py:from select import select" \
-e "readinputlinux_thread.py:import sys, os" \
-e "readinputlinux_thread.py:from select import select" \
-e "readinputlinux_thread.py:import threading" \
-e "readinputlinux_thread.py:import queue" \
-e "input.py:import platform" \
-e "input.py:from time import time" \
-e "term.py:import importlib.util" \
-e "term.*.py:import sys, os, signal" \
-e "term.*.py:from .term_base import TTkTermBase" \
-e "term_pyodide.py:import pyodideProxy" \
-e "term_unix.py:from threading import Thread, Lock" \
-e "timer.py:import importlib" \
-e "timer_unix.py:import threading" \
-e "timer_pyodide.py:import pyodideProxy" \
@ -40,12 +33,34 @@ __check(){
-e "string.py:import unicodedata" \
-e "progressbar.py:import math" \
-e "uiloader.py:import json" \
-e "uiproperties.py:from .properties import *" \
-e "uiproperties.py:from .properties.* import" \
-e "util.py:import zlib, pickle, base64" \
-e "propertyanimation.py:from inspect import getfullargspec" \
-e "propertyanimation.py:from types import LambdaType" \
-e "propertyanimation.py:import time, math" \
-e "terminal.py:from select import select"
-e "terminal.py:from select import select" |
grep -v \
-e "TTkTerm/input.py:from ..drivers import TTkInputDriver" \
-e "TTkTerm/term.py:from ..drivers import *" \
-e "drivers/unix_thread.py:import sys, os" \
-e "drivers/unix_thread.py:from select import select" \
-e "drivers/unix_thread.py:import threading" \
-e "drivers/unix_thread.py:import queue" \
-e "drivers/unix.py:import sys, os, re" \
-e "drivers/unix.py:import signal" \
-e "drivers/unix.py:from select import select" \
-e "drivers/windows.py:import signal" \
-e "drivers/windows.py:from ctypes import Structure, Union, byref, wintypes, windll" \
-e "drivers/term_windows.py:import sys, os" \
-e "drivers/term_windows.py:from threading import Thread, Lock" \
-e "drivers/term_windows.py:from ..TTkTerm.term_base import TTkTermBase" \
-e "drivers/term_windows.py:from .windows import *" \
-e "drivers/term_unix.py:from ..TTkTerm.term_base import TTkTermBase" \
-e "drivers/term_unix.py:from threading import Thread, Lock" \
-e "drivers/term_pyodide.py:import pyodideProxy" \
-e "drivers/term_pyodide.py:from ..TTkTerm.term_base import TTkTermBase" \
-e "drivers/__init__.py:import importlib.util" \
-e "drivers/__init__.py:import platform"
} ;
if __check ; then

2
ttkDesigner/app/propertyeditor.py

@ -183,7 +183,7 @@ class PropertyEditor(ttk.TTkGridLayout):
# Color Fields
def _processTTkColor(name, prop):
getval = prop['get']['cb'](domw)
value = ttk.TTkWidget(layout=ttk.TTkHBoxLayout(), height=1)
value = ttk.TTkContainer(layout=ttk.TTkHBoxLayout(), height=1)
value.layout().addWidget(_cb := ttk.TTkColorButtonPicker(color=getval, height=1))
value.layout().addWidget(_rc := ttk.TTkButton(text=ttk.TTkString('x',ttk.TTkColor.fg('#FFAA00')),maxWidth=3))
_cb.colorSelected.connect(_bound(prop['set']['cb'],domw,lambda v:v))

4
ttkDesigner/app/superobj/__init__.py

@ -25,10 +25,12 @@ from .supercontrol import SuperControlWidget
from .superwidget import SuperWidget
from .superwidgetcontainer import SuperWidgetContainer
from .superwidgettextedit import SuperWidgetTextEdit
from .superwidgetabstractscrollarea import SuperWidgetAbstractScrollArea
# from .superwidgettextedit import SuperWidgetTextEdit
from .superwidgetradiobutton import SuperWidgetRadioButton
from .superwidgetframe import SuperWidgetFrame
from .superwidgetsplitter import SuperWidgetSplitter
# from .superwidgetlist import SuperWidgetList
from .superwidgetmenubutton import SuperWidgetMenuButton
from .superlayout import SuperLayout

5
ttkDesigner/app/superobj/superwidget.py

@ -99,14 +99,17 @@ class SuperWidget(ttk.TTkContainer):
def swFromWidget(wid:object, *args, **kwargs):
swClass = so.SuperWidget
for c, sc in {
ttk.TTkTextEdit: so.SuperWidgetTextEdit,
# ttk.TTkTextEdit: so.SuperWidgetTextEdit,
ttk.TTkRadioButton: so.SuperWidgetRadioButton,
# ttk.TTkResizableFrame: so.SuperWidgetFrame,
# ttk.TTkWindow: so.SuperWidgetFrame,
ttk.TTkSplitter: so.SuperWidgetSplitter,
# ttk.TTkList: so.SuperWidgetList,
ttk.TTkMenuButton: so.SuperWidgetMenuButton,
ttk.TTkFrame: so.SuperWidgetFrame,
ttk.TTkAbstractScrollArea: so.SuperWidgetAbstractScrollArea,
ttk.TTkContainer: so.SuperWidgetContainer,
ttk.TTkWidget: so.SuperWidget,
}.items():
if c in type(wid).mro():
swClass = sc

44
ttkDesigner/app/superobj/superwidgetabstractscrollarea.py

@ -0,0 +1,44 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import TermTk as ttk
import ttkDesigner.app.superobj as so
from .superobj import SuperObject
class SuperWidgetAbstractScrollArea(so.SuperWidgetContainer):
@staticmethod
def _swFromWidget(wid, swClass, *args, **kwargs):
return swClass(wid=wid, *args, **kwargs)
def getSuperProperties(self):
additions, exceptions, exclude = super().getSuperProperties()
exclude += ['Layout','Padding']
return additions, exceptions, exclude
def dumpDict(self):
wid = self._wid
ret = {
'class' : wid.__class__.__name__,
'params' : SuperObject.dumpParams(wid,exclude=['Layout','Padding']),
}
return ret

29
ttkDesigner/app/superobj/superwidgetlist.py

@ -0,0 +1,29 @@
# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import TermTk as ttk
import ttkDesigner.app.superobj as so
from .superobj import SuperObject
class SuperWidgetList(so.SuperWidgetAbstractScrollArea):
pass

3
ttkDesigner/app/widgetbox.py

@ -56,7 +56,8 @@ dWidgets = {
},
'Widgets':{
"Label" : { "class":ttk.TTkLabel, "params":{'size':(20,1), 'text':'Label'}},
"List" : { "class":ttk.TTkListWidget, "params":{'size':(20,1)}, "disabled": True},
"List" : { "class":ttk.TTkList, "params":{'size':(20,5)}},
# "List Widget" : { "class":ttk.TTkListWidget, "params":{'size':(20,5)}},
"Scroll Area" : { "class":ttk.TTkScrollArea, "params":{'size':(20,5)}, "disabled": True},
"Spacer" : { "class":ttk.TTkSpacer, "params":{'size':(10,5)}},
"Tab Widget" : { "class":ttk.TTkTabWidget, "params":{'size':(20,3)}, "disabled": True},

41
tests/test.input.win.py → tutorial/examples/TTkWidget/Focus.01.py

@ -2,7 +2,7 @@
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@ -22,36 +22,21 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Those 2 lines are required to use the TermTk library straight from the main folder
import sys, os
import logging
sys.path.append(os.path.join(sys.path[0],'../../..'))
sys.path.append(os.path.join(sys.path[0],'..'))
from TermTk import TTkLog, TTkK, TTkGridLayout, TTk, TTkLogViewer, TTkHelper
import TermTk as ttk
def keyCallback(kevt=None, mevt=None):
if mevt is not None:
TTkLog.info(f"Mouse Event: {mevt}")
if kevt is not None:
if kevt.type == TTkK.Character:
TTkLog.info(f"Key Event: char '{kevt.key}' {kevt}")
else:
TTkLog.info(f"Key Event: Special '{kevt}'")
if kevt.key == "q":
input.close()
return False
return True
root=ttk.TTk()
def pasteCallback(txt:str):
TTkLog.info(f"PASTE:")
for s in txt.split('\n'):
TTkLog.info(f" | {s}")
return True
btn1 = ttk.TTkButton(parent=root, pos=(5,2), text='Button 1 - unfocussed')
btn2 = ttk.TTkButton(parent=root, pos=(5,3), text='Button 2 - unfocussed')
btn3 = ttk.TTkButton(parent=root, pos=(5,4), text='Button 3 - unfocussed')
btn4 = ttk.TTkButton(parent=root, pos=(5,5), text='Button 4 - focussed')
btn5 = ttk.TTkButton(parent=root, pos=(5,6), text='Button 5 - unfocussed')
root = TTk(layout=TTkGridLayout())
# Force the focus on the button 4
btn4.setFocus()
TTkLogViewer(parent=root)
TTkHelper._rootWidget._input.inputEvent.connect(keyCallback)
TTkHelper._rootWidget._input.pasteEvent.connect(pasteCallback)
root.mainloop()
root.mainloop()
Loading…
Cancel
Save