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.
448 lines
15 KiB
448 lines
15 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. |
|
|
|
from TermTk.TTkCore.TTkTerm.colors import TTkTermColor |
|
from TermTk.TTkCore.TTkTerm.term import TTkTerm |
|
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 = [] |
|
_mousePos = (0,0) |
|
_cursorPos = [0,0] |
|
_cursor = False |
|
_cursorType = TTkTerm.Cursor.BLINKING_BLOCK |
|
class _Overlay(): |
|
__slots__ = ('_widget','_prevFocus','_x','_y','_modal') |
|
def __init__(self,x,y,widget,prevFocus,modal): |
|
self._widget = widget |
|
self._prevFocus = prevFocus |
|
self._modal = modal |
|
widget.move(x,y) |
|
_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 execShortcut(letter, widget=None): |
|
if not isinstance(letter, str): return |
|
for sc in TTkHelper._shortcut: |
|
if sc._letter == letter.lower() and sc._widget.isVisibleAndParent(): |
|
if not widget or TTkHelper.isParent(widget, sc._widget): |
|
sc._widget.shortcutEvent() |
|
return |
|
|
|
@staticmethod |
|
def updateAll(): |
|
if TTkHelper._rootWidget: |
|
TTkHelper._rootWidget.update(repaint=True, updateLayout=True) |
|
for w in TTkHelper._rootWidget.layout().iterWidgets(): |
|
w.update(repaint=True, updateLayout=True) |
|
|
|
@staticmethod |
|
def addUpdateWidget(widget): |
|
if not widget.isVisibleAndParent(): 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 quit(): |
|
if TTkHelper._rootWidget: |
|
TTkHelper._rootWidget.quit() |
|
|
|
@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 getLastModal(): |
|
modal = None |
|
for o in TTkHelper._overlay: |
|
if o._modal: |
|
modal = o |
|
return modal |
|
|
|
@staticmethod |
|
def checkModalOverlay(widget): |
|
#if not TTkHelper._overlay: |
|
# # There are no Overlays |
|
# return True |
|
|
|
if not (lastModal := TTkHelper.getLastModal()): |
|
return True |
|
|
|
# if not TTkHelper._overlay[-1]._modal: |
|
# # The last window is not modal |
|
# return True |
|
if not (rootWidget := TTkHelper.rootOverlay(widget)): |
|
# This widget is not overlay |
|
return False |
|
if rootWidget in [ o._widget for o in TTkHelper._overlay[TTkHelper._overlay.index(lastModal):]]: |
|
return True |
|
# if TTkHelper._overlay[-1]._widget == rootWidget: |
|
# return True |
|
return False |
|
|
|
@staticmethod |
|
def isOverlay(widget): |
|
return TTkHelper.rootOverlay(widget) is not None |
|
|
|
@staticmethod |
|
def overlay(caller, widget, x, y, modal=False, forceBoundaries=True): |
|
if not caller: |
|
caller = TTkHelper._rootWidget |
|
wx, wy = TTkHelper.absPos(caller) |
|
w,h = widget.size() |
|
# Try to keep the overlay widget inside the terminal |
|
if forceBoundaries: |
|
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 ) |
|
else: |
|
wx += x |
|
wy += y |
|
TTkHelper._overlay.append(TTkHelper._Overlay(wx,wy,widget,TTkHelper._focusWidget,modal)) |
|
TTkHelper._rootWidget.rootLayout().addWidget(widget) |
|
widget.setFocus() |
|
widget.raiseWidget() |
|
for w in widget.rootLayout().iterWidgets(onlyVisible=True): |
|
w.update() |
|
|
|
@staticmethod |
|
def getOverlay(): |
|
if TTkHelper._overlay: |
|
return TTkHelper._overlay[-1]._widget |
|
return None |
|
|
|
@staticmethod |
|
def removeOverlay(): |
|
if not TTkHelper._overlay: |
|
return |
|
bkFocus = None |
|
# Remove the first element also if it is modal |
|
TTkHelper._overlay[-1]._modal = False |
|
while TTkHelper._overlay: |
|
if TTkHelper._overlay[-1]._modal: |
|
break |
|
owidget = TTkHelper._overlay.pop() |
|
bkFocus = owidget._prevFocus |
|
TTkHelper._rootWidget.rootLayout().removeWidget(owidget._widget) |
|
if TTkHelper._focusWidget: |
|
TTkHelper._focusWidget.clearFocus() |
|
if bkFocus: |
|
bkFocus.setFocus() |
|
|
|
@staticmethod |
|
def removeOverlayAndChild(widget): |
|
if len(TTkHelper._overlay) <= 1: |
|
return TTkHelper.removeOverlay() |
|
rootWidget = TTkHelper.rootOverlay(widget) |
|
bkFocus = None |
|
found = False |
|
newOverlay = [] |
|
for o in TTkHelper._overlay: |
|
if o._widget == rootWidget: |
|
found = True |
|
bkFocus = o._prevFocus |
|
if not found: |
|
newOverlay.append(o) |
|
else: |
|
TTkHelper._rootWidget.rootLayout().removeWidget(o._widget) |
|
TTkHelper._overlay = newOverlay |
|
if bkFocus: |
|
bkFocus.setFocus() |
|
if not found: |
|
TTkHelper.removeOverlay() |
|
|
|
@staticmethod |
|
def removeOverlayChild(widget): |
|
rootWidget = TTkHelper.rootOverlay(widget) |
|
found = False |
|
newOverlay = [] |
|
for o in TTkHelper._overlay: |
|
if o._widget == rootWidget: |
|
found = True |
|
newOverlay.append(o) |
|
continue |
|
if not found: |
|
newOverlay.append(o) |
|
else: |
|
TTkHelper._rootWidget.rootLayout().removeWidget(o._widget) |
|
TTkHelper._overlay = newOverlay |
|
if not found: |
|
TTkHelper.removeOverlay() |
|
|
|
@staticmethod |
|
def setMousePos(pos): |
|
TTkHelper._mousePos = pos |
|
# update the position of the Drag and Drop Widget |
|
if TTkHelper._dnd: |
|
hsx, hsy = TTkHelper._dnd['d'].hotSpot() |
|
TTkHelper._dnd['d'].pixmap().move(pos[0]-hsx, pos[1]-hsy) |
|
|
|
@staticmethod |
|
def mousePos(): |
|
return TTkHelper._mousePos |
|
|
|
@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.isVisibleAndParent(): 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.isVisibleAndParent(): 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 children 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.isVisibleAndParent(): continue |
|
pushToTerminal = True |
|
widget.paintChildCanvas() |
|
|
|
if pushToTerminal: |
|
if TTkHelper._cursor: |
|
TTkTerm.Cursor.hide() |
|
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 |
|
TTkTerm.push(TTkTerm.Cursor.moveTo(y+1,x+1)) |
|
TTkTerm.Cursor.show(TTkHelper._cursorType) |
|
|
|
@staticmethod |
|
def rePaintAll(): |
|
if TTkHelper._rootCanvas and TTkHelper._rootWidget: |
|
TTkTerm.push(TTkTerm.CLEAR) |
|
TTkHelper._rootCanvas.cleanBuffers() |
|
TTkHelper._rootWidget.update() |
|
|
|
@staticmethod |
|
def widgetDepth(widget) -> int: |
|
if widget is None: |
|
return 0 |
|
return 1 + TTkHelper.widgetDepth(widget.parentWidget()) |
|
|
|
@staticmethod |
|
def isParent(widget, parent): |
|
if p := widget.parentWidget(): |
|
if p == parent: |
|
return True |
|
return TTkHelper.isParent(p, parent) |
|
return False |
|
|
|
@staticmethod |
|
def absPos(widget) -> (int,int): |
|
wx, wy = 0,0 |
|
layout = widget.widgetItem() |
|
while layout: |
|
px, py = layout.pos() |
|
ox, oy = layout.offset() |
|
wx, wy = wx+px+ox, wy+py+oy |
|
layout = layout.parent() |
|
return (wx, wy) |
|
|
|
@staticmethod |
|
def nextFocus(widget): |
|
rootWidget = TTkHelper.rootOverlay(widget) |
|
if not rootWidget: |
|
rootWidget = TTkHelper._rootWidget |
|
if widget == rootWidget: |
|
widget = None |
|
first = None |
|
for w in rootWidget.rootLayout().iterWidgets(): |
|
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() |
|
|
|
@staticmethod |
|
def prevFocus(widget): |
|
rootWidget = TTkHelper.rootOverlay(widget) |
|
if not rootWidget: |
|
rootWidget = TTkHelper._rootWidget |
|
if widget == rootWidget: |
|
widget = None |
|
prev = None |
|
for w in rootWidget.rootLayout().iterWidgets(): |
|
# 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 = TTkTerm.Cursor.BLINKING_BLOCK |
|
elif cursorType == TTkK.Cursor_Blinking_Block_Also : TTkHelper._cursorType = TTkTerm.Cursor.BLINKING_BLOCK_ALSO |
|
elif cursorType == TTkK.Cursor_Steady_Block : TTkHelper._cursorType = TTkTerm.Cursor.STEADY_BLOCK |
|
elif cursorType == TTkK.Cursor_Blinking_Underline : TTkHelper._cursorType = TTkTerm.Cursor.BLINKING_UNDERLINE |
|
elif cursorType == TTkK.Cursor_Steady_Underline : TTkHelper._cursorType = TTkTerm.Cursor.STEADY_UNDERLINE |
|
elif cursorType == TTkK.Cursor_Blinking_Bar : TTkHelper._cursorType = TTkTerm.Cursor.BLINKING_BAR |
|
elif cursorType == TTkK.Cursor_Steady_Bar : TTkHelper._cursorType = TTkTerm.Cursor.STEADY_BAR |
|
TTkTerm.Cursor.show(TTkHelper._cursorType) |
|
TTkHelper._cursor = True |
|
@staticmethod |
|
def hideCursor(): |
|
TTkTerm.Cursor.hide() |
|
TTkHelper._cursorType = TTkTerm.Cursor.BLINKING_BLOCK |
|
TTkHelper._cursor = False |
|
@staticmethod |
|
def moveCursor(widget, x, y): |
|
xx, yy = TTkHelper.absPos(widget) |
|
TTkHelper._cursorPos = [xx+x,yy+y] |
|
TTkTerm.push(TTkTerm.Cursor.moveTo(yy+y+1,xx+x+1)) |
|
|
|
class Color(TTkTermColor): pass |
|
|
|
# Drag and Drop related helper routines |
|
_dnd = None |
|
|
|
@staticmethod |
|
def dndInit(drag): |
|
TTkHelper._dnd = { |
|
'd' : drag, |
|
'w' : None |
|
} |
|
TTkHelper._rootWidget.rootLayout().addWidget(drag.pixmap()) |
|
drag.pixmap().raiseWidget() |
|
|
|
@staticmethod |
|
def dndGetDrag(): |
|
return TTkHelper._dnd['d'] if TTkHelper._dnd else None |
|
|
|
@staticmethod |
|
def dndWidget(): |
|
return TTkHelper._dnd['w'] if TTkHelper._dnd else None |
|
|
|
@staticmethod |
|
def dndEnter(widget): |
|
TTkHelper._dnd['w'] = widget |
|
|
|
@staticmethod |
|
def isDnD(): |
|
return TTkHelper._dnd is not None |
|
|
|
@staticmethod |
|
def dndEnd(): |
|
if TTkHelper._dnd: |
|
TTkHelper._rootWidget.rootLayout().removeWidget(TTkHelper._dnd['d'].pixmap()) |
|
TTkHelper._dnd = None |
|
TTkHelper._rootWidget.update()
|
|
|