Browse Source

Finally, the first (buggy) but decent Output

pull/1/head
Eugenio Parodi 5 years ago
parent
commit
ed2467310b
  1. 20
      README.md
  2. 1
      TermTk/TTk/__init__.py
  3. 3
      TermTk/TTkCore/__init__.py
  4. 120
      TermTk/TTkCore/canvas.py
  5. 37
      TermTk/TTkCore/cfg.py
  6. 24
      TermTk/TTkCore/draw.py
  7. 87
      TermTk/TTkCore/helper.py
  8. 25
      TermTk/TTkCore/log.py
  9. 143
      TermTk/TTkCore/ttk.py
  10. 1
      TermTk/TTkWidgets/__init__.py
  11. 23
      TermTk/TTkWidgets/button.py
  12. 39
      TermTk/TTkWidgets/frame.py
  13. 277
      TermTk/TTkWidgets/layout.py
  14. 172
      TermTk/TTkWidgets/widget.py
  15. 2
      TermTk/__init__.py
  16. 5
      TermTk/libbpytop/__init__.py
  17. 16
      TermTk/libbpytop/canvas..py
  18. 171
      TermTk/libbpytop/colors.py
  19. 13
      TermTk/libbpytop/input.py
  20. 51
      TermTk/libbpytop/term.py
  21. 23
      docs/TODO.md
  22. 33
      tests/tailSession.sh
  23. 65
      tests/test.draw.001.py
  24. 74
      tests/test.draw.002.py
  25. 0
      tests/test.draw.py
  26. 35
      tests/test.input.py
  27. 35
      tests/test.ui.001.py
  28. 47
      tests/test.ui.002.py
  29. 63
      tests/test.ui.102..py
  30. 0
      tests/utf-8.txt

20
README.md

@ -1,2 +1,22 @@
# pyTermTk
Python Terminal Toolkit
## Quick Test/Try
#### Clone
```shell
clone git@github.com:ceccopierangiolieugenio/pyTermTk.git
pyTermTk
```
#### Run Basic input test
```shell
python3 tests/test.input.py
```
#### Run Terminal resize test
```shell
# Press CTRL-C to exit
# the logs are written to "session.log"
python3 tests/test.ui.002.py
```

1
TermTk/TTk/__init__.py

@ -1 +0,0 @@
from .log import *

3
TermTk/TTkCore/__init__.py

@ -0,0 +1,3 @@
from .log import *
from .cfg import *
from .ttk import *

120
TermTk/TTkCore/canvas.py

@ -0,0 +1,120 @@
#!/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 TermTk.libbpytop as lbt
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.cfg import *
from TermTk.TTkCore.helper import *
class TTkCanvas:
'''
TTkCanvas
canvas window primitives
...
Attributes
----------
Methods
-------
__init__({})
input obj{ width, height}
resize(w, h)
- resize the canvas keeping or cutting the current one
in w = the width of the new canvas
in h = the height of the new canvas
'''
def __init__(self, *args, **kwargs):
self._widget = kwargs.get('widget', None)
self._width = kwargs.get('width', 0 )
self._height = kwargs.get('height', 0 )
self.resize(self._width, self._height)
TTkLog.debug((self._width, self._height))
def getWidget(self): return self._widget
def move(self, x, y):
npos = TTkHelper.absParentPos(self._widget)
# CuTCore.cuDebug("Move: x:"+str(nx+x)+" y:"+str(ny+y))
# self._bufPaint['move']={'x':npos.x()+x, 'y':npos.y()+y}
TTkHelper.addPaintBuffer(self)
def resize(self, w, h):
# TTkLog.debug(f"CanvasResize:{(w,h)}")
self._data = [[]]*h
self._colors = [[]]*h
for i in range(0,h):
self._data[i] = [' ']*w
self._colors[i] = [None]*w
self._width = w
self._height = h
TTkHelper.addPaintBuffer(self)
def zTop(self):
# TODO: Figure out how to use this
pass
def drawBox(self, x, y, w, h):
def _set(_y,_x,_c):
if _y<self._height and _x < self._width:
self._data[_y][_x] = _c
# 4 corners
_set(y, x, "")
_set(y, x+w-1, "")
_set(y+h-1, x, "")
_set(y+h-1, x+w-1, "")
if w > 2:
for i in range(x+1,x+w-1):
_set(y, i, "")
_set(y+h-1, i, "")
if h > 2:
for i in range(y+1,y+h-1):
_set(i, x, "")
_set(i, x+w-1, "")
TTkHelper.addPaintBuffer(self)
def execPaint(self, winw, winh):
pass
def paintCanvas(self, canvas, x, y, w, h):
# TTkLog.debug(f"PaintCanvas:{(x,y,w,h)}")
x = x if x<self._width else self._width-1
y = y if y<self._height else self._height-1
w = w if x+w<self._width else self._width-x
h = h if y+h<self._height else self._height-y
for iy in range(0,h):
for ix in range(0,w):
self._data[y+iy][x+ix] = canvas._data[iy][ix]
self._colors[y+iy][x+ix] = canvas._colors[iy][ix]
def pushToTerminal(self, x, y, w, h):
TTkLog.debug("pushToTerminal")
ansi = ""
for y in range(0, self._height):
s = lbt.Mv.t(y+1,1)
for x in range(0, self._width):
c = self._data[y][x]
s+=c
ansi += s
lbt.Term.push(ansi)

37
TermTk/TTkCore/cfg.py

@ -0,0 +1,37 @@
#!/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.
class TTkCfg:
DEP_2: int = 0x02
DEP_4: int = 0x04
DEP_8: int = 0x08
DEP_24: int = 0x18
color_depth: int = DEP_24
class TTkGlbl:
term_w: int = 0
term_h: int = 0

24
TermTk/TTkCore/draw.py

@ -0,0 +1,24 @@
#!/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.

87
TermTk/TTkCore/helper.py

@ -0,0 +1,87 @@
#!/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 TermTk.libbpytop as lbt
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.cfg import *
class TTkHelper:
# TODO: Add Setter/Getter
_rootCanvas = None
_updateWidget = []
_paintBuffer = []
@staticmethod
def addUpdateWidget(widget):
if widget not in TTkHelper._updateWidget:
TTkHelper._updateWidget.append(widget)
@staticmethod
def addPaintBuffer(canvas):
if canvas is not TTkHelper._rootCanvas:
if canvas not in TTkHelper._paintBuffer:
TTkHelper._paintBuffer.append(canvas)
@staticmethod
def registerRootCanvas(canvas):
TTkHelper._rootCanvas = canvas
TTkHelper._paintBuffer = []
TTkHelper._updateWidget = []
@staticmethod
def execPaint(cw, ch):
if TTkHelper._rootCanvas is None :
return
for canvas in TTkHelper._paintBuffer:
widget = canvas.getWidget()
x = widget.getX()
y = widget.getY()
w = widget.getWidth()
h = widget.getHeight()
TTkHelper._rootCanvas.paintCanvas(canvas, x, y, w, h)
TTkHelper._paintBuffer = []
@staticmethod
def paintAll():
if TTkHelper._rootCanvas is None:
return
for widget in TTkHelper._updateWidget:
widget.paintEvent()
TTkHelper._updateWidget = []
TTkHelper.execPaint(TTkGlbl.term_w,TTkGlbl.term_h)
TTkHelper._rootCanvas.pushToTerminal(0, 0, TTkGlbl.term_w, TTkGlbl.term_h)
# curses.panel.update_panels()
# TTkGlbl.GLBL['screen'].refresh()
@staticmethod
def absPos(widget) -> (int,int):
pos = TTkHelper.absParentPos(widget)
return widget.pos() + pos
@staticmethod
def absParentPos(widget) -> (int,int):
if widget is None or widget.parentWidget() is None:
return (0, 0)
return TTkHelper.absPos(widget.parentWidget())

25
TermTk/TTk/log.py → TermTk/TTkCore/log.py

@ -26,6 +26,7 @@
# https://github.com/ceccopierangiolieugenio/pyCuT/blob/master/cupy/CuTCore/CuDebug.py
import inspect
import logging
from collections.abc import Callable
class _TTkContext:
@ -48,6 +49,30 @@ class TTkLog:
_messageHandler: Callable = None
@staticmethod
def _logging_message_handler(mode, context, message):
log = logging.debug
if mode == TTkLog.InfoMsg: log = logging.info
elif mode == TTkLog.WarningMsg: log = logging.warning
elif mode == TTkLog.CriticalMsg: log = logging.critical
elif mode == TTkLog.FatalMsg: log = logging.fatal
elif mode == TTkLog.ErrorMsg: log = logging.error
log(f"{context.file}:{context.line} {message}")
@staticmethod
def use_default_file_logging():
logging.basicConfig(level=logging.DEBUG,
filename='session.log',
format='%(levelname)s:(%(threadName)-9s) %(message)s',)
TTkLog.installMessageHandler(TTkLog._logging_message_handler)
@staticmethod
def use_default_stdout_logging():
logging.basicConfig(level=logging.DEBUG,
format='%(levelname)s:(%(threadName)-9s) %(message)s',)
TTkLog.installMessageHandler(TTkLog._logging_message_handler)
@staticmethod
def _process_msg(mode: int, msg: str):
if TTkLog._messageHandler is not None:

143
TermTk/TTkCore/ttk.py

@ -0,0 +1,143 @@
#!/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 os
import signal
import time
import threading, queue
import TermTk.libbpytop as lbt
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.cfg import *
from TermTk.TTkCore.canvas import *
from TermTk.TTkWidgets.layout import *
from TermTk.TTkWidgets.widget import *
class TTk(TTkWidget):
running: bool = False
events = None
key_events = None
mouse_events = None
screen_events = None
MOUSE_EVENT = 0x01
KEY_EVENT = 0x02
SCREEN_EVENT = 0x04
QUIT_EVENT = 0x08
TIME_EVENT = 0x10
def __init__(self, *args, **kwargs):
TTkWidget.__init__(self, *args, **kwargs)
self.events = queue.Queue()
self.key_events = queue.Queue()
self.mouse_events = queue.Queue()
self.screen_events = queue.Queue()
TTkHelper.registerRootCanvas(self._canvas)
def mainloop(self):
TTkLog.debug("Starting Main Loop...")
# Register events
try:
signal.signal(signal.SIGTSTP, self._SIGSTOP) # Ctrl-Z
signal.signal(signal.SIGCONT, self._SIGCONT) # Resume
signal.signal(signal.SIGINT, self._SIGINT) # Ctrl-C
except Exception as e:
TTkLog.error(f"{e}")
exit(1)
else:
TTkLog.debug("Signal Event Registered")
lbt.Term.registerResizeCb(self._win_resize_cb)
threading.Thread(target=self._input_thread, daemon=True).start()
# threading.Timer(30.0, hello)
self.running = True
lbt.Term.init()
while self.running:
# Main Loop
evt = self.events.get()
if evt is TTk.MOUSE_EVENT:
mevt = self.mouse_events.get()
TTkLog.info(f"Mouse Event: {mevt}")
elif evt is TTk.KEY_EVENT:
kevt = self.key_events.get()
TTkLog.info(f"Key Event: {kevt}")
pass
elif evt is TTk.TIME_EVENT:
pass
elif evt is TTk.SCREEN_EVENT:
self.setGeometry(0,0,TTkGlbl.term_w,TTkGlbl.term_h)
TTkLog.info(f"Resize: w:{TTkGlbl.term_w}, h:{TTkGlbl.term_h}")
elif evt is TTk.QUIT_EVENT:
TTkLog.debug(f"Quit.")
break
else:
TTkLog.error(f"Unhandled Event {evt}")
break
TTkHelper.paintAll()
lbt.Term.exit()
pass
def _win_resize_cb(self, width, height):
TTkGlbl.term_w = int(width)
TTkGlbl.term_h = int(height)
self.events.put(TTk.SCREEN_EVENT)
def _input_thread(self):
def _inputCallback(kevt=None, mevt=None):
if kevt is not None:
self.key_events.put(kevt)
self.events.put(TTk.KEY_EVENT)
if mevt is not None:
self.mouse_events.put(mevt)
self.events.put(TTk.MOUSE_EVENT)
return self.running
# Start input key loop
lbt.Input.get_key(_inputCallback)
def _canvas_thread(self):
pass
def quit(self):
self.events.put(TTk.QUIT_EVENT)
self.running = False
def _SIGSTOP(self, signum, frame):
"""Reset terminal settings and stop background input read before putting to sleep"""
TTkLog.debug("Captured SIGSTOP <CTRL-z>")
lbt.Term.stop()
# TODO: stop the threads
os.kill(os.getpid(), signal.SIGSTOP)
def _SIGCONT(self, signum, frame):
"""Set terminal settings and restart background input read"""
TTkLog.debug("Captured SIGCONT 'fg/bg'")
lbt.Term.cont()
# TODO: Restart threads
# TODO: Redraw the screen
def _SIGINT(self, signum, frame):
TTkLog.debug("Captured SIGINT <CTRL-C>")
self.quit()

1
TermTk/TTkWidgets/__init__.py

@ -0,0 +1 @@
from .frame import *

23
TermTk/TTkWidgets/button.py

@ -0,0 +1,23 @@
#!/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.

39
TermTk/TTkWidgets/frame.py

@ -0,0 +1,39 @@
#!/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.
from TermTk.TTkCore.log import TTkLog
from .widget import *
class TTkFrame(TTkWidget):
def __init__(self, *args, **kwargs):
TTkWidget.__init__(self, *args, **kwargs)
self._border = kwargs.get('border', True )
self.update()
def paintEvent(self):
if self._border:
self._canvas.drawBox(0,0,self._width,self._height)

277
TermTk/TTkWidgets/layout.py

@ -0,0 +1,277 @@
'''
Layout System
'''
class TTkLayoutItem:
__slots__ = ('_x', '_y', '_w', '_h', '_sMax', '_sMaxVal', '_sMin', '_sMinVal')
def __init__(self):
self._x, self._y = 0, 0
self._w, self._h = 0, 0
self._sMax, self._sMin = False, False
self._sMaxVal, self._sMinVal = 0, 0
pass
def minimumSize(self):
return self.minimumWidth(), self.minimumHeight()
def minimumHeight(self): return 0
def minimumWidth(self): return 0
def maximumSize(self):
return self.maximumWidth(), self.maximumHeight()
def maximumHeight(self): return 0x80000000
def maximumWidth(self): return 0x80000000
def geometry(self):
return self._x, self._y, self._w, self._h
def setGeometry(self, x, y, w, h):
self._x = x
self._y = y
self._w = w
self._h = h
class TTkLayout(TTkLayoutItem):
def __init__(self):
TTkLayoutItem.__init__(self)
self._items = []
self._parent = None
pass
def children(self):
return self._items
def count(self):
return len(self._items)
def itemAt(self, index):
if index < len(self._items):
return self._items[index]
return 0
def setParent(self, parent):
self._parent = parent
def parentWidget(self):
return self._parent
def addItem(self, item):
self._items.append(item)
def addWidget(self, widget):
self.addItem(TTkWidgetItem(widget))
def removeWidget(self, widget):
for i in self._items:
if i.widget() == widget:
self._items.remove(i)
return
def update(self):
for i in self.children():
if isinstance(i, TTkWidgetItem) and not i.isEmpty():
i.widget().update()
# TODO: Have a look at this:
# i.getCanvas().top()
elif isinstance(i, TTkLayout):
i.update()
class TTkWidgetItem(TTkLayoutItem):
def __init__(self, widget):
TTkLayoutItem.__init__(self)
self._widget = widget
def widget(self):
return self._widget
def isEmpty(self): return self._widget is None
def minimumSize(self): return self._widget.minimumSize()
def minimumHeight(self): return self._widget.minimumHeight()
def minimumWidth(self): return self._widget.minimumWidth()
def maximumSize(self): return self._widget.maximumSize()
def maximumHeight(self): return self._widget.maximumHeight()
def maximumWidth(self): return self._widget.maximumWidth()
def geometry(self): return self._widget.geometry()
def setGeometry(self, x, y, w, h):
self._widget.setGeometry(x, y, w, h)
class TTkHBoxLayout(TTkLayout):
def __init__(self):
TTkLayout.__init__(self)
def minimumWidth(self):
''' process the widgets and get the min size '''
minw = 0
for item in self.children():
w1 = item.minimumWidth()
minw += w1
return minw
def minimumHeight(self):
''' process the widgets and get the min size '''
minh = TTkLayout.minimumHeight(self)
for item in self.children():
h1 = item.minimumHeight()
if h1 > minh : minh = h1
return minh
def maximumWidth(self):
''' process the widgets and get the min size '''
maxw = 0
for item in self.children():
w1 = item.maximumWidth()
maxw += w1
return maxw
def maximumHeight(self):
''' process the widgets and get the min size '''
maxh = TTkLayout.maximumHeight(self)
for item in self.children():
h1 = item.maximumHeight()
if h1 < maxh : maxh = h1
return maxh
def update(self):
x, y, w, h = self.geometry()
numWidgets = self.count()
leftWidgets = numWidgets
freeWidth = w
newx, newy = x, y
# Loop to check the resizable space
for item in self.children():
item._sMax = False
item._sMin = False
iterate = True
while iterate and leftWidgets > 0:
iterate = False
sliceSize = freeWidth//leftWidgets
for item in self.children():
if item._sMax or item._sMin: continue
maxs = item.maximumWidth()
mins = item.minimumWidth()
if sliceSize > maxs:
freeWidth -= maxs
iterate = True
item._sMax = True
item._sMaxVal = maxs
leftWidgets -= 1
elif sliceSize < mins:
freeWidth -= mins
leftWidgets -= 1
# slicesize = freeWidth//leftWidgets
iterate = True
item._sMin = True
item._sMinVal = mins
# loop and set the geometry of any item
for item in self.children():
if item._sMax:
item.setGeometry(newx, newy, item._sMaxVal, h)
newy += item._sMaxVal
elif item._sMin:
item.setGeometry(newx, newy, item._sMinVal, h)
newy += item._sMinVal
else:
sliceSize = freeWidth//leftWidgets
item.setGeometry(newx, newy, sliceSize, h)
newx += sliceSize
freeWidth -= sliceSize
leftWidgets -= 1
if isinstance(item, TTkWidgetItem) and not item.isEmpty():
item.widget().update()
item.widget().getCanvas().zTop()
elif isinstance(item, TTkLayout):
item.update()
class TTkVBoxLayout(TTkLayout):
def __init__(self):
TTkLayout.__init__(self)
def minimumWidth(self):
''' process the widgets and get the min size '''
minw = TTkLayout.minimumWidth(self)
for item in self.children():
w1 = item.minimumWidth()
if w1 > minw : minw = w1
return minw
def minimumHeight(self):
''' process the widgets and get the min size '''
minh = 0
for item in self.children():
h1 = item.minimumHeight()
minh += h1
return minh
def maximumWidth(self):
''' process the widgets and get the min size '''
maxw = TTkLayout.maximumWidth(self)
for item in self.children():
w1 = item.maximumWidth()
if w1 < maxw : maxw = w1
return maxw
def maximumHeight(self):
''' process the widgets and get the min size '''
maxh = 0
for item in self.children():
h1 = item.maximumHeight()
maxh += h1
return maxh
def update(self):
x, y, w, h = self.geometry()
numWidgets = self.count()
leftWidgets = numWidgets
freeHeight = h
newx, newy = x, y
# Loop to check the resizable space
for item in self.children():
item._sMax = False
item._sMin = False
iterate = True
while iterate and leftWidgets > 0:
iterate = False
sliceSize = freeHeight//leftWidgets
for item in self.children():
if item._sMax or item._sMin: continue
maxs = item.maximumHeight()
mins = item.minimumHeight()
if sliceSize > maxs:
freeHeight -= maxs
iterate = True
item._sMax = True
item._sMaxVal = maxs
leftWidgets -= 1
elif sliceSize < mins:
freeHeight -= mins
leftWidgets -= 1
# slicesize = freeHeight//leftWidgets
iterate = True
item._sMin = True
item._sMinVal = mins
# loop and set the geometry of any item
for item in self.children():
if item._sMax:
item.setGeometry(newx, newy, w, item._sMaxVal)
newy += item._sMaxVal
elif item._sMin:
item.setGeometry(newx, newy, w, item._sMinVal)
newy += item._sMinVal
else:
sliceSize = freeHeight//leftWidgets
item.setGeometry(newx, newy, w, sliceSize)
newy += sliceSize
freeHeight -= sliceSize
leftWidgets -= 1
if isinstance(item, TTkWidgetItem) and not item.isEmpty():
item.widget().update()
item.widget().getCanvas().zTop()
elif isinstance(item, TTkLayout):
item.update()

172
TermTk/TTkWidgets/widget.py

@ -0,0 +1,172 @@
#!/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.
from TermTk.TTkCore.canvas import *
from TermTk.TTkCore.cfg import *
from .layout import *
class TTkWidget:
def __init__(self, *args, **kwargs):
self._childs = []
self._parent = kwargs.get('parent', None )
self._x = kwargs.get('x', 0 )
self._y = kwargs.get('y', 0 )
self._width = kwargs.get('width' , 0 )
self._height = kwargs.get('height', 0 )
self._maxw = 0x80000000
self._maxh = 0x80000000
self._minw = 0x00000000
self._minh = 0x00000000
self._layout = TTkLayout()
self._canvas = TTkCanvas(
widget = self,
width = self._width ,
height = self._height )
if self._parent is not None and \
self._parent._layout is not None:
self._parent._layout.addWidget(self)
def getX(self): return self._x
def getY(self): return self._y
def getWidth(self): return self._width
def getHeight(self): return self._height
def pos(self):
return (self._x, self._y)
def addLayout(self, l):
self._layout = l
def paintEvent(self): pass
def move(self, x, y):
self._x = x
self._y = y
self._canvas.move(self._x, self._y)
if self._layout is not None:
self._layout.setGeometry(self._x, self._y, self._width, self._height)
self.update()
def resize(self, w, h):
self._width = w
self._height = h
self._canvas.resize(self._width, self._height)
if self._layout is not None:
self._layout.setGeometry(self._x, self._y, self._width, self._height)
self.update()
def setGeometry(self, x, y, w, h):
self.resize(w, h)
self.move(x, y)
def mouseDoubleClickEvent(self, evt): pass
def mouseMoveEvent(self, evt): pass
def mousePressEvent(self, evt): pass
def mouseReleaseEvent(self, evt): pass
def wheelEvent(self, evt): pass
def enterEvent(self, evt): pass
def leaveEvent(self, evt): pass
def keyPressEvent(self, evt): pass
def keyReleaseEvent(self, evt): pass
def event(self, evt):
pass
# # handle own events
# if evt.type() == CuEvent.MouseMove:
# if evt.button() == CuT.NoButton:
# self.mouseMoveEvent(evt)
# elif evt.type() == CuEvent.MouseButtonRelease:
# self.mouseReleaseEvent(evt)
# elif evt.type() == CuEvent.MouseButtonPress:
# self.mousePressEvent(evt)
# if self.focusPolicy() & CuT.ClickFocus == CuT.ClickFocus:
# self.setFocus()
# elif evt.type() == CuEvent.Wheel:
# self.wheelEvent(evt)
# if self.focusPolicy() & CuT.WheelFocus == CuT.WheelFocus:
# self.setFocus()
# elif evt.type() == CuEvent.KeyPress:
# self.keyPressEvent(evt)
# elif evt.type() == CuEvent.KeyRelease:
# self.keyReleaseEvent(evt)
# # Trigger this event to the childs
# if self._layout is not None:
# return CuWidget._eventLayoutHandle(evt, self._layout)
def maximumSize(self):
return self.maximumWidth(), self.maximumHeight()
def maximumHeight(self):
wMaxH = self._maxh
if self._layout is not None:
lMaxH = self._layout.maximumHeight()
if lMaxH < wMaxH:
return lMaxH
return wMaxH
def maximumWidth(self):
wMaxW = self._maxw
if self._layout is not None:
lMaxW = self._layout.maximumWidth()
if lMaxW < wMaxW:
return lMaxW
return wMaxW
def minimumSize(self):
return self.minimumWidth(), self.minimumHeight()
def minimumHeight(self):
wMinH = self._minh
if self._layout is not None:
lMinH = self._layout.minimumHeight()
if lMinH > wMinH:
return lMinH
return wMinH
def minimumWidth(self):
wMinW = self._minw
if self._layout is not None:
lMinW = self._layout.minimumWidth()
if lMinW > wMinW:
return lMinW
return wMinW
def setMaximumSize(self, maxw, maxh): self._maxw = maxw; self._maxh = maxh
def setMaximumHeight(self, maxh): self._maxh = maxh
def setMaximumWidth(self, maxw): self._maxw = maxw
def setMinimumSize(self, minw, minh): self._minw = minw; self._minh = minh
def setMinimumHeight(self, minh): self._minh = minh
def setMinimumWidth(self, minw): self._minw = minw
def update(self):
TTkHelper.addUpdateWidget(self)
if self._layout is not None:
self._layout.update()
def getCanvas(self):
return self._canvas
def layout(self):
return self._layout
def setParent(self, parent):
self._parent = parent
def parentWidget(self):
return self._parent

2
TermTk/__init__.py

@ -0,0 +1,2 @@
from .TTkCore import *
from .TTkWidgets import *

5
TermTk/libbpytop/__init__.py

@ -1,2 +1,3 @@
from .input import *
from .term import *
from .input import *
from .term import *
from .colors import *

16
TermTk/libbpytop/canvas..py

@ -1,16 +0,0 @@
#!/usr/bin/env python3
# Copyright 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
# Copyright 2020 Aristocratos (https://github.com/aristocratos/bpytop)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

171
TermTk/libbpytop/colors.py

@ -0,0 +1,171 @@
#!/usr/bin/env python3
# Copyright 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
# Copyright 2020 Aristocratos (https://github.com/aristocratos/bpytop)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os, sys, io, threading, signal, re, subprocess, logging, logging.handlers, argparse
import queue
from select import select
from time import time, sleep, strftime, localtime
from typing import List, Set, Dict, Tuple, Optional, Union, Any, Callable, ContextManager, Iterable, Type, NamedTuple
try: import fcntl, termios, tty, pwd
except Exception as e:
print(f'ERROR: {e}')
exit(1)
from . import Term
import TermTk as ttk
# Ansi Escape Codes:
# https://conemu.github.io/en/AnsiEscapeCodes.html
class Color:
'''Holds representations for a 24-bit color value
__init__(color, depth="fg", default=False)
-- color accepts 6 digit hexadecimal: string "#RRGGBB", 2 digit hexadecimal: string "#FF" or decimal RGB "255 255 255" as a string.
-- depth accepts "fg" or "bg"
__call__(*args) joins str arguments to a string and apply color
__str__ returns escape sequence to set color
__iter__ returns iteration over red, green and blue in integer values of 0-255.
* Values: .hexa: str | .dec: Tuple[int, int, int] | .red: int | .green: int | .blue: int | .depth: str | .escape: str
'''
hexa: str; dec: Tuple[int, int, int]; red: int; green: int; blue: int; depth: str; escape: str; default: bool
def __init__(self, color: str, depth: str = "fg", default: bool = False):
self.depth = depth
self.default = default
try:
if not color:
self.dec = (-1, -1, -1)
self.hexa = ""
self.red = self.green = self.blue = -1
self.escape = "\033[49m" if depth == "bg" and default else ""
return
elif color.startswith("#"):
self.hexa = color
if len(self.hexa) == 3:
self.hexa += self.hexa[1:3] + self.hexa[1:3]
c = int(self.hexa[1:3], base=16)
self.dec = (c, c, c)
elif len(self.hexa) == 7:
self.dec = (int(self.hexa[1:3], base=16), int(self.hexa[3:5], base=16), int(self.hexa[5:7], base=16))
else:
raise ValueError(f'Incorrectly formatted hexadecimal rgb string: {self.hexa}')
else:
c_t = tuple(map(int, color.split(" ")))
if len(c_t) == 3:
self.dec = c_t #type: ignore
else:
raise ValueError(f'RGB dec should be "0-255 0-255 0-255"')
ct = self.dec[0] + self.dec[1] + self.dec[2]
if ct > 255*3 or ct < 0:
raise ValueError(f'RGB values out of range: {color}')
except Exception as e:
ttk.TTkLog.error(str(e))
self.escape = ""
return
if self.dec and not self.hexa: self.hexa = f'{hex(self.dec[0]).lstrip("0x").zfill(2)}{hex(self.dec[1]).lstrip("0x").zfill(2)}{hex(self.dec[2]).lstrip("0x").zfill(2)}'
if self.dec and self.hexa:
self.red, self.green, self.blue = self.dec
self.escape = f'\033[{38 if self.depth == "fg" else 48};2;{";".join(str(c) for c in self.dec)}m'
if ttk.TTkCfg.color_depth is not ttk.TTkCfg.DEP_24:
self.escape = f'{self.truecolor_to_256(rgb=self.dec, depth=self.depth)}'
def __str__(self) -> str:
return self.escape
def __repr__(self) -> str:
return repr(self.escape)
def __iter__(self) -> Iterable:
for c in self.dec: yield c
def __call__(self, *args: str) -> str:
if len(args) < 1: return ""
return f'{self.escape}{"".join(args)}{getattr(Term, self.depth)}'
@staticmethod
def truecolor_to_256(rgb: Tuple[int, int, int], depth: str="fg") -> str:
out: str = ""
pre: str = f'\033[{"38" if depth == "fg" else "48"};5;'
greyscale: Tuple[int, int, int] = ( rgb[0] // 11, rgb[1] // 11, rgb[2] // 11 )
if greyscale[0] == greyscale[1] == greyscale[2]:
out = f'{pre}{232 + greyscale[0]}m'
else:
out = f'{pre}{round(rgb[0] / 51) * 36 + round(rgb[1] / 51) * 6 + round(rgb[2] / 51) + 16}m'
return out
@staticmethod
def escape_color(hexa: str = "", r: int = 0, g: int = 0, b: int = 0, depth: str = "fg") -> str:
"""Returns escape sequence to set color
* accepts either 6 digit hexadecimal hexa="#RRGGBB", 2 digit hexadecimal: hexa="#FF"
* or decimal RGB: r=0-255, g=0-255, b=0-255
* depth="fg" or "bg"
"""
dint: int = 38 if depth == "fg" else 48
color: str = ""
if hexa:
try:
if len(hexa) == 3:
c = int(hexa[1:], base=16)
if ttk.TTkCfg.color_depth is ttk.TTkCfg.DEP_24:
color = f'\033[{dint};2;{c};{c};{c}m'
else:
color = f'{Color.truecolor_to_256(rgb=(c, c, c), depth=depth)}'
elif len(hexa) == 7:
if ttk.TTkCfg.color_depth is ttk.TTkCfg.DEP_24:
color = f'\033[{dint};2;{int(hexa[1:3], base=16)};{int(hexa[3:5], base=16)};{int(hexa[5:7], base=16)}m'
else:
color = f'{Color.truecolor_to_256(rgb=(int(hexa[1:3], base=16), int(hexa[3:5], base=16), int(hexa[5:7], base=16)), depth=depth)}'
except ValueError as e:
ttk.TTkLog.error(f'{e}')
else:
if ttk.TTkCfg.color_depth is ttk.TTkCfg.DEP_24:
color = f'\033[{dint};2;{r};{g};{b}m'
else:
color = f'{Color.truecolor_to_256(rgb=(r, g, b), depth=depth)}'
return color
@classmethod
def fg(cls, *args) -> str:
if len(args) > 2: return cls.escape_color(r=args[0], g=args[1], b=args[2], depth="fg")
else: return cls.escape_color(hexa=args[0], depth="fg")
@classmethod
def bg(cls, *args) -> str:
if len(args) > 2: return cls.escape_color(r=args[0], g=args[1], b=args[2], depth="bg")
else: return cls.escape_color(hexa=args[0], depth="bg")
#class Colors:
# '''Standard colors for menus and dialogs'''
# default = Color("#cc")
# white = Color("#ff")
# red = Color("#bf3636")
# green = Color("#68bf36")
# blue = Color("#0fd7ff")
# yellow = Color("#db8b00")
# black_bg = Color("#00", depth="bg")
# null = Color("")

13
TermTk/libbpytop/input.py

@ -27,7 +27,7 @@ except Exception as e:
print(f'ERROR: {e}')
exit(1)
from TermTk.TTk import TTkLog
import TermTk as ttk
class MouseEvent:
# Keys
@ -130,9 +130,7 @@ class Input:
# ttk.debug("INPUT: "+input_key.replace("\033","<ESC>"))
mevt = None
kevt = None
if input_key == "\033" or input_key == "q": #* Key is "escape" key if only containing \033
break
elif len(input_key) == 1:
if len(input_key) == 1:
# Key Pressed
kevt = KeyEvent(0, input_key)
elif input_key.startswith("\033[<"):
@ -140,7 +138,7 @@ class Input:
m = mouse_re.match(input_key)
if not m:
# TODO: Return Error
TTkLog.error("UNHANDLED: "+input_key.replace("\033","<ESC>"))
ttk.TTkLog.error("UNHANDLED: "+input_key.replace("\033","<ESC>"))
continue
code = int(m.group(1))
x = int(m.group(2))
@ -174,10 +172,11 @@ class Input:
evt = MouseEvent.Down
mevt = MouseEvent(x, y, key, evt, m.group(0).replace("\033", "<ESC>"))
else:
TTkLog.error("UNHANDLED: "+input_key.replace("\033","<ESC>"))
ttk.TTkLog.error("UNHANDLED: "+input_key.replace("\033","<ESC>"))
input_key = ""
if callback is not None:
callback(kevt, mevt)
if not callback(kevt, mevt):
break
def main():
print("Retrieve Keyboard, Mouse press/drag/wheel Events")

51
TermTk/libbpytop/term.py

@ -26,8 +26,36 @@ except Exception as e:
print(f'ERROR: {e}')
exit(1)
class Mv:
"""Class with collection of cursor movement functions: .t[o](line, column) | .r[ight](columns) | .l[eft](columns) | .u[p](lines) | .d[own](lines) | .save() | .restore()"""
@staticmethod
def to(line: int, col: int) -> str:
return f'\033[{line};{col}f' #* Move cursor to line, column
@staticmethod
def right(x: int) -> str: #* Move cursor right x columns
return f'\033[{x}C'
@staticmethod
def left(x: int) -> str: #* Move cursor left x columns
return f'\033[{x}D'
@staticmethod
def up(x: int) -> str: #* Move cursor up x lines
return f'\033[{x}A'
@staticmethod
def down(x: int) -> str: #* Move cursor down x lines
return f'\033[{x}B'
save: str = "\033[s" #* Save cursor position
restore: str = "\033[u" #* Restore saved cursor postion
t = to
r = right
l = left
u = up
d = down
class Term:
"""Terminal info and commands"""
title: str = "TermTk"
mouse: bool = True
width: int = 0
height: int = 0
fg: str = "" #* Default foreground color
@ -46,14 +74,30 @@ class Term:
@staticmethod
def init(mouse: bool = True, title: str = "TermTk"):
Term.push(Term.alt_screen, Term.clear, Term.hide_cursor, Term.title("BpyTOP"))
if mouse:
Term.title = title
Term.mouse = mouse
Term.push(Term.alt_screen, Term.clear, Term.hide_cursor, Term.escTitle(Term.title))
if Term.mouse:
Term.push(Term.mouse_on)
Term.echo(False)
@staticmethod
def stop():
Term.push(Term.mouse_off, Term.mouse_direct_off)
Term.push(Term.clear, Term.normal_screen, Term.show_cursor, Term.escTitle())
Term.echo(True)
@staticmethod
def cont():
Term.push(Term.alt_screen, Term.clear, Term.hide_cursor, Term.escTitle(Term.title))
if Term.mouse:
Term.push(Term.mouse_on)
Term.echo(False)
@staticmethod
def exit():
Term.push(Term.mouse_off, Term.mouse_direct_off)
Term.push(Term.clear, Term.normal_screen, Term.show_cursor, Term.escTitle())
Term.echo(True)
@ -77,7 +121,7 @@ class Term:
print(*args, sep="", end="", flush=True)
@staticmethod
def title(text: str = "") -> str:
def escTitle(text: str = "") -> str:
out: str = f'{os.environ.get("TERMINAL_TITLE", "")}'
if out and text: out += " "
if text: out += f'{text}'
@ -95,4 +139,3 @@ class Term:
# Dummy call to retrieve the terminal size
Term._sigWinCh(signal.SIGWINCH, None)
signal.signal(signal.SIGWINCH, Term._sigWinCh)

23
docs/TODO.md

@ -1,9 +1,11 @@
# TODO
- [ ] Follow [PEP 8](https://www.python.org/dev/peps/pep-0008/) coding style
- [ ] Terminal Helper
## Terminal Helper
- [ ] Events
- [x] Window : SIGWINCH triggered when the terminal is resized
- [ ] Input Class
## Input Class
- [ ] Return Error if Mouse RE does not match
- [x] Handle the Paste Buffer
- [ ] Investigate the middle mouse button paste
@ -14,9 +16,20 @@
https://www.pythontutorial.net/tkinter/tkinter-event-binding/
- [x] Keyboard
- [x] Mouse
- [ ] Canvas Class
- [ ] Logs
## Canvas Class
- [ ] Have a look to the Unicode chartable: https://www.utf8-chartable.de/unicode-utf8-table.pl
## Signal Slots
- [ ] Implement Signal Slots
## Logs
- [x] Log Class
- [ ] Log helpers
- [x] File and Stdout logger
- [ ] logger auto integration
- [ ] stdout until mainLoop
- [ ] stdout until mainLoop
## Widgets
### Layout
- [ ] Add Weight in V and H Layout

33
tests/tailSession.sh

@ -0,0 +1,33 @@
#!/usr/bin/env bash
esc=$(printf '\033')
_RST_=${esc}'[0m' # resets color and format
# Regular Colors
Black=${esc}'[38;5;0m'
Red=${esc}'[38;5;1m'
Green=${esc}'[38;5;2m'
Yellow=${esc}'[38;5;3m'
Blue=${esc}'[38;5;4m'
Magenta=${esc}'[38;5;5m'
Cyan=${esc}'[38;5;6m'
White=${esc}'[38;5;7m'
# Background
On_Black=${esc}'[48;5;0m'
On_Red=${esc}'[48;5;1m'
On_Green=${esc}'[48;5;2m'
On_Yellow=${esc}'[48;5;3m'
On_Blue=${esc}'[48;5;4m'
On_Magenta=${esc}'[48;5;5m'
On_Cyan=${esc}'[48;5;6m'
On_White=${esc}'[48;5;7m'
while read -r line; do
echo "$line" |
sed "s,/home.*/TermTk/,TermTk/," |
sed "s,^\(INFO:\),${Green}\1${_RST_}," |
sed "s,^\(ERROR:\),${Red}\1${_RST_}," |
sed "s,^\(DEBUG:\),${Blue}\1${_RST_},"
done < <(tail -F session.log)

65
tests/test.draw.001.py

@ -0,0 +1,65 @@
#!/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 logging
import time
sys.path.append(os.path.join(sys.path[0],'..'))
from TermTk.libbpytop import Term, Mv
from TermTk import TTkLog
def message_handler(mode, context, message):
log = logging.debug
if mode == TTkLog.InfoMsg: log = logging.info
elif mode == TTkLog.WarningMsg: log = logging.warning
elif mode == TTkLog.CriticalMsg: log = logging.critical
elif mode == TTkLog.FatalMsg: log = logging.fatal
elif mode == TTkLog.ErrorMsg: log = logging.error
log(f"{context.file} {message}")
logging.basicConfig(level=logging.DEBUG,
filename='session.log',
format='%(levelname)s:(%(threadName)-9s) %(message)s',)
TTkLog.installMessageHandler(message_handler)
Term.init(mouse=False)
Term.push(
Mv.t(2,4) +
"Test Text 3"
)
time.sleep(1)
Term.push(
Mv.d(1) + Mv.l(3) +
"Test Text 2"
)
time.sleep(1)
Term.push(
Mv.d(1) + Mv.l(3) +
"Test Text 1"
)
time.sleep(1)
Term.exit()

74
tests/test.draw.002.py

@ -0,0 +1,74 @@
#!/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 logging
import time
sys.path.append(os.path.join(sys.path[0],'..'))
from TermTk.libbpytop import Term, Mv, Color
from TermTk import TTkLog
def message_handler(mode, context, message):
log = logging.debug
if mode == TTkLog.InfoMsg: log = logging.info
elif mode == TTkLog.WarningMsg: log = logging.warning
elif mode == TTkLog.CriticalMsg: log = logging.critical
elif mode == TTkLog.FatalMsg: log = logging.fatal
elif mode == TTkLog.ErrorMsg: log = logging.error
log(f"{context.file} {message}")
logging.basicConfig(level=logging.DEBUG,
filename='session.log',
format='%(levelname)s:(%(threadName)-9s) %(message)s',)
TTkLog.installMessageHandler(message_handler)
Term.init(mouse=False)
TTkLog.info("Starting")
Term.push(
Mv.t(2,4) + # Cursor x:2, y:4
Color.fg("#ff0000") +
"Test Text 3"
)
time.sleep(1)
TTkLog.info("next : 2")
Term.push(
Mv.d(1) + Mv.l(3) + # Cursor 1 Down, 3 Left
Color.bg("#550088") +
"Test Text 2"
)
time.sleep(1)
TTkLog.info("next : 1")
Term.push(
Mv.d(1) + Mv.l(3) + # Cursor 1 Down, 3 Left
Color.fg("#00ff00") +
Color.bg("#555500") +
"Test Text 1"
)
time.sleep(1)
TTkLog.info("Ending")
Term.exit()

0
tests/test.draw.py

35
tests/test.input.py

@ -1,9 +1,33 @@
#!/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 logging
sys.path.append(os.path.join(sys.path[0],'..'))
from TermTk import TTkLog
import TermTk.libbpytop as lbt
from TermTk.TTk import TTkLog
def message_handler(mode, context, message):
log = logging.debug
@ -25,10 +49,13 @@ lbt.Term.push(lbt.Term.mouse_on)
lbt.Term.echo(False)
def keyCallback(kevt=None, mevt=None):
if kevt is not None:
TTkLog.info(f"Key Event: {kevt}")
if mevt is not None:
TTkLog.info(f"Mouse Event: {mevt}")
if kevt is not None:
TTkLog.info(f"Key Event: {kevt}")
if kevt.key == "q":
return False
return True
def winCallback(width, height):
TTkLog.info(f"Resize: w:{width}, h:{height}")
@ -37,4 +64,4 @@ lbt.Term.registerResizeCb(winCallback)
lbt.Input.get_key(keyCallback)
lbt.Term.push(lbt.Term.mouse_off, lbt.Term.mouse_direct_off)
lbt.Term.echo(True)
lbt.Term.echo(True)

35
tests/test.ui.001.py

@ -0,0 +1,35 @@
#!/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
sys.path.append(os.path.join(sys.path[0],'..'))
import TermTk as ttk
ttk.TTkLog.use_default_file_logging()
root = ttk.TTk()
ttk.TTkFrame(parent=root, x=5, y=3, width=20, height=15, border=True)
# ttk.Button(root, text="Hello World").grid()
root.mainloop()

47
tests/test.ui.002.py

@ -0,0 +1,47 @@
#!/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
sys.path.append(os.path.join(sys.path[0],'..'))
import TermTk as ttk
ttk.TTkLog.use_default_file_logging()
root = ttk.TTk()
root._layout = ttk.TTkHBoxLayout()
ttk.TTkFrame(parent=root,border=True)
rightframe = ttk.TTkFrame(parent=root)
rightframe._layout = ttk.TTkVBoxLayout()
ttk.TTkFrame(parent=rightframe, border=True)
centerrightframe=ttk.TTkFrame(parent=rightframe)
ttk.TTkFrame(parent=rightframe, border=True)
centerrightframe._layout = ttk.TTkHBoxLayout()
ttk.TTkFrame(parent=centerrightframe, border=True)
ttk.TTkFrame(parent=centerrightframe, border=True)
ttk.TTkFrame(parent=centerrightframe, border=True)
root.mainloop()

63
tests/test.ui.102..py

@ -0,0 +1,63 @@
#!/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 logging
import time
sys.path.append(os.path.join(sys.path[0],'..'))
from TermTk import TTkLog
from TermTk import TTk
class Demo1:
def __init__(self, master):
self.master = master
self.frame = ttk.Frame(self.master)
self.button1 = ttk.Button(self.frame, text = 'New Window', width = 25, command = self.new_window)
self.button1.pack()
self.frame.pack()
def new_window(self):
self.newWindow = ttk.Toplevel(self.master)
self.app = Demo2(self.newWindow)
class Demo2:
def __init__(self, master):
self.master = master
self.frame = ttk.Frame(self.master)
self.quitButton = ttk.Button(self.frame, text = 'Quit', width = 25, command = self.close_windows)
self.quitButton.pack()
self.frame.pack()
def close_windows(self):
self.master.destroy()
def main():
root = ttk.TTk()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()

0
tests/utf-8.txt

Loading…
Cancel
Save