You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

322 lines
12 KiB

#!/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 TTkCfg, TTkGlbl
from TermTk.TTkCore.constant import TTkK
class TTkHelper:
# TODO: Add Setter/Getter
_focusWidget = None
_rootCanvas = None
_rootWidget = None
_updateWidget = []
_updateBuffer = []
_cursorPos = [0,0]
_cursor = False
_cursorType = lbt.Term.cursor_blinking_block
class _Overlay():
__slots__ = ('_widget','_x','_y')
def __init__(self,x,y,widget):
self._widget = widget
widget.move(x,y)
_savedFocus = None
_overlay = []
class _Shortcut():
__slots__ = ('_letter','_widget')
def __init__(self, letter, widget):
self._letter = letter.lower()
self._widget = widget
_shortcut = []
@staticmethod
def addShortcut(widget, letter):
TTkHelper._shortcut.append(TTkHelper._Shortcut(letter, widget))
@staticmethod
def isParent(parent, widget):
if parent==widget: return True
if widget.parentWidget() is None: return False
return TTkHelper.isParent(parent,widget.parentWidget())
@staticmethod
def execShortcut(letter, widget=None):
if not isinstance(letter, str): return
for sc in TTkHelper._shortcut:
if sc._letter == letter.lower() and sc._widget.isVisible():
if not widget or TTkHelper.isParent(widget, sc._widget):
sc._widget.shortcutEvent()
return
@staticmethod
def addUpdateWidget(widget):
if not widget.isVisible(): return
if widget not in TTkHelper._updateWidget:
TTkHelper._updateWidget.append(widget)
@staticmethod
def addUpdateBuffer(canvas):
if canvas is not TTkHelper._rootCanvas:
if canvas not in TTkHelper._updateBuffer:
TTkHelper._updateBuffer.append(canvas)
@staticmethod
def registerRootWidget(widget):
TTkHelper._rootCanvas = widget.getCanvas()
TTkHelper._rootWidget = widget
TTkHelper._rootCanvas.enableDoubleBuffer()
TTkHelper._updateBuffer = []
TTkHelper._updateWidget = []
@staticmethod
def rootOverlay(widget):
if widget is None:
return None
if not TTkHelper._overlay:
return None
overlayWidgets = [o._widget for o in TTkHelper._overlay]
while widget is not None:
if widget in overlayWidgets:
return widget
widget = widget.parentWidget()
return None
@staticmethod
def isOverlay(widget):
return TTkHelper.rootOverlay(widget) is not None
@staticmethod
def overlay(caller, widget, x, y):
wx, wy = TTkHelper.absPos(caller)
w,h = widget.size()
if not TTkHelper._savedFocus:
TTkHelper._savedFocus = TTkHelper._focusWidget
# Try to keep the overlay widget inside the terminal
wx = max(0, wx+x if wx+x+w < TTkGlbl.term_w else TTkGlbl.term_w-w )
wy = max(0, wy+y if wy+y+h < TTkGlbl.term_h else TTkGlbl.term_h-h )
TTkHelper._overlay.append(TTkHelper._Overlay(wx,wy,widget))
TTkHelper._rootWidget.rootLayout().addWidget(widget)
widget.setFocus()
widget.raiseWidget()
@staticmethod
def getOverlay():
if TTkHelper._overlay:
return TTkHelper._overlay[-1]._widget
return None
@staticmethod
def removeOverlay():
for widget in TTkHelper._overlay:
TTkHelper._rootWidget.rootLayout().removeWidget(widget._widget)
TTkHelper._overlay = []
if TTkHelper._focusWidget:
TTkHelper._focusWidget.clearFocus()
if TTkHelper._savedFocus:
bk = TTkHelper._savedFocus
TTkHelper._savedFocus = None
bk.setFocus()
@staticmethod
def removeSingleOverlay(widget):
if len(TTkHelper._overlay) <= 1:
return TTkHelper.removeOverlay()
rootWidget = TTkHelper.rootOverlay(widget)
rootWidget
for o in TTkHelper._overlay:
if o._widget == rootWidget:
TTkHelper._overlay.remove(o)
TTkHelper._rootWidget.rootLayout().removeWidget(rootWidget)
TTkHelper._overlay[-1]._widget.setFocus()
@staticmethod
def paintAll():
'''
_updateBuffer = list widgets that require a repaint [paintEvent]
_updateWidget = list widgets that need to be pushed below
'''
if TTkHelper._rootCanvas is None:
return
# Build a list of buffers to be repainted
updateBuffers = TTkHelper._updateBuffer.copy()
updateWidgets = TTkHelper._updateWidget.copy()
# TTkLog.debug(f"{len(TTkHelper._updateBuffer)} {len(TTkHelper._updateWidget)}")
for widget in TTkHelper._updateWidget:
if not widget.isVisible(): continue
parent = widget.parentWidget()
while parent is not None:
if parent not in updateBuffers:
updateBuffers.append(parent)
if parent not in updateWidgets:
updateWidgets.append(parent)
parent = parent.parentWidget()
TTkHelper._updateBuffer = []
TTkHelper._updateWidget = []
# Paint all the canvas
for widget in updateBuffers:
if not widget.isVisible(): continue
# Resize the canvas just before the paintEvent
# to avoid too many allocations
widget.getCanvas().updateSize()
widget.getCanvas().clean()
widget.paintEvent()
# Compose all the canvas to the parents
# From the deepest childs to the bottom
pushToTerminal = False
sortedUpdateWidget = [ (w, TTkHelper.widgetDepth(w)) for w in updateWidgets]
sortedUpdateWidget = sorted(sortedUpdateWidget, key=lambda w: -w[1])
for w in sortedUpdateWidget:
widget = w[0]
if not widget.isVisible(): continue
pushToTerminal = True
widget.paintChildCanvas()
if pushToTerminal:
if TTkHelper._cursor:
lbt.Term.hideCursor()
if TTkCfg.doubleBuffer:
TTkHelper._rootCanvas.pushToTerminalBuffered(0, 0, TTkGlbl.term_w, TTkGlbl.term_h)
else:
TTkHelper._rootCanvas.pushToTerminal(0, 0, TTkGlbl.term_w, TTkGlbl.term_h)
if TTkHelper._cursor:
x,y = TTkHelper._cursorPos
lbt.Term.push(lbt.Mv.to(y+1,x+1))
lbt.Term.showCursor(TTkHelper._cursorType)
@staticmethod
def widgetDepth(widget) -> int:
if widget is None:
return 0
return 1 + TTkHelper.widgetDepth(widget.parentWidget())
@staticmethod
def absPos(widget) -> (int,int):
ppos = TTkHelper.absParentPos(widget)
wpos = widget.pos()
return (wpos[0]+ppos[0], wpos[1]+ppos[1])
@staticmethod
def absParentPos(widget) -> (int,int):
if widget is None or widget.parentWidget() is None:
return (0, 0)
return TTkHelper.absPos(widget.parentWidget())
def _iterWidgets(item):
for child in item.children():
if child.layoutItemType == TTkK.WidgetItem:
if not child.widget().isVisible(): continue
yield child.widget()
for i in TTkHelper._iterWidgets(child.widget().rootLayout()):
yield i
if child.layoutItemType == TTkK.LayoutItem:
for i in TTkHelper._iterWidgets(child):
yield i
def nextFocus(widget):
rootWidget = TTkHelper.rootOverlay(widget)
if not rootWidget:
rootWidget = TTkHelper._rootWidget
if widget == rootWidget:
widget = None
first = None
for w in TTkHelper._iterWidgets(rootWidget.rootLayout()):
if not first and w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus:
first = w
# TTkLog.debug(f"{w._name} {widget}")
if widget:
if w == widget:
widget=None
continue
if w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus:
w.setFocus()
w.update()
return
if first:
first.setFocus()
first.update()
def prevFocus(widget):
rootWidget = TTkHelper.rootOverlay(widget)
if not rootWidget:
rootWidget = TTkHelper._rootWidget
if widget == rootWidget:
widget = None
prev = None
for w in TTkHelper._iterWidgets(rootWidget.rootLayout()):
# TTkLog.debug(f"{w._name} {widget}")
if w == widget:
widget=None
if prev:
break
if w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus:
prev = w
if prev:
prev.setFocus()
prev.update()
@staticmethod
def setFocus(widget):
TTkHelper._focusWidget = widget
@staticmethod
def getFocus():
return TTkHelper._focusWidget
@staticmethod
def clearFocus():
TTkHelper._focusWidget = None
@staticmethod
def showCursor(cursorType = TTkK.Cursor_Blinking_Block):
if cursorType == TTkK.Cursor_Blinking_Block : TTkHelper._cursorType = lbt.Term.cursor_blinking_block
elif cursorType == TTkK.Cursor_Blinking_Block_Also : TTkHelper._cursorType = lbt.Term.cursor_blinking_block_also
elif cursorType == TTkK.Cursor_Steady_Block : TTkHelper._cursorType = lbt.Term.cursor_steady_block
elif cursorType == TTkK.Cursor_Blinking_Underline : TTkHelper._cursorType = lbt.Term.cursor_blinking_underline
elif cursorType == TTkK.Cursor_Steady_Underline : TTkHelper._cursorType = lbt.Term.cursor_steady_underline
elif cursorType == TTkK.Cursor_Blinking_Bar : TTkHelper._cursorType = lbt.Term.cursor_blinking_bar
elif cursorType == TTkK.Cursor_Steady_Bar : TTkHelper._cursorType = lbt.Term.cursor_steady_bar
lbt.Term.showCursor(TTkHelper._cursorType)
TTkHelper._cursor = True
@staticmethod
def hideCursor():
lbt.Term.hideCursor()
TTkHelper._cursorType = lbt.Term.cursor_blinking_block
TTkHelper._cursor = False
@staticmethod
def moveCursor(widget, x, y):
xx, yy = TTkHelper.absPos(widget)
TTkHelper._cursorPos = [xx+x,yy+y]
lbt.Term.push(lbt.Mv.to(yy+y+1,xx+x+1))
class Color(lbt.Color): pass
class Mv(lbt.Mv): pass