Browse Source

feat: reworked the focus handler (#537)

pull/540/head
Pier CeccoPierangioliEugenio 4 months ago committed by GitHub
parent
commit
89383239df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 0
      demo/showcase/date_time.py
  2. 10
      demo/showcase/dndtabs.py
  3. 70
      docs/MDNotes/internals/focus.keypress.md
  4. 6
      libs/pyTermTk/TermTk/TTkAbstract/abstractscrollview.py
  5. 227
      libs/pyTermTk/TermTk/TTkCore/helper.py
  6. 35
      libs/pyTermTk/TermTk/TTkCore/ttk.py
  7. 46
      libs/pyTermTk/TermTk/TTkLayouts/layout.py
  8. 2
      libs/pyTermTk/TermTk/TTkWidgets/Fancy/tableview.py
  9. 9
      libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py
  10. 4
      libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablewidget.py
  11. 2
      libs/pyTermTk/TermTk/TTkWidgets/combobox.py
  12. 97
      libs/pyTermTk/TermTk/TTkWidgets/container.py
  13. 15
      libs/pyTermTk/TermTk/TTkWidgets/datetime_date_form.py
  14. 2
      libs/pyTermTk/TermTk/TTkWidgets/datetime_datetime.py
  15. 393
      libs/pyTermTk/TermTk/TTkWidgets/rootcontainer.py
  16. 73
      libs/pyTermTk/TermTk/TTkWidgets/tabwidget.py
  17. 45
      libs/pyTermTk/TermTk/TTkWidgets/widget.py
  18. 430
      tests/pytest/widgets/test_add_remove_widget.py
  19. 202
      tests/pytest/widgets/test_focus_01.py
  20. 113
      tests/pytest/widgets/test_focus_02_first_focus.py
  21. 556
      tests/pytest/widgets/test_focus_02_tab.py
  22. 2
      tests/t.ui/test.ui.001.window.01.py
  23. 62
      tests/t.ui/test.ui.037.prototype.01.rootContainer.py
  24. 69
      tests/t.ui/test.ui.037.prototype.02.modal.py
  25. 46
      tests/t.ui/test.ui.038.tab_focus.py

0
demo/showcase/date_time.py

10
demo/showcase/dndtabs.py

@ -29,7 +29,7 @@ import TermTk as ttk
def demoDnDTabs(root=None, border=True): def demoDnDTabs(root=None, border=True):
vsplitter = ttk.TTkSplitter(parent=root, orientation=ttk.TTkK.VERTICAL) vsplitter = ttk.TTkSplitter(parent=root, orientation=ttk.TTkK.VERTICAL)
tabWidget1 = ttk.TTkTabWidget(parent=vsplitter, border=border) tabWidget1 = ttk.TTkTabWidget(parent=vsplitter, border=True)
hsplitter = ttk.TTkSplitter(parent=vsplitter) hsplitter = ttk.TTkSplitter(parent=vsplitter)
tabWidget2 = ttk.TTkTabWidget(parent=hsplitter, border=False, barType=ttk.TTkBarType.DEFAULT_2) tabWidget2 = ttk.TTkTabWidget(parent=hsplitter, border=False, barType=ttk.TTkBarType.DEFAULT_2)
tabWidget3 = ttk.TTkTabWidget(parent=hsplitter, border=False, barType=ttk.TTkBarType.NERD_1) tabWidget3 = ttk.TTkTabWidget(parent=hsplitter, border=False, barType=ttk.TTkBarType.NERD_1)
@ -44,8 +44,12 @@ def demoDnDTabs(root=None, border=True):
tabWidget1.addTab(ttk.TTkTestWidgetSizes(border=True, title="Frame1.6"), "Label 1.6") tabWidget1.addTab(ttk.TTkTestWidgetSizes(border=True, title="Frame1.6"), "Label 1.6")
tabWidget1.addTab(ttk.TTkTestWidget( border=True, title="Frame1.7"), "Label Test 1.7") tabWidget1.addTab(ttk.TTkTestWidget( border=True, title="Frame1.7"), "Label Test 1.7")
tabWidget1.addTab(ttk.TTkTestWidgetSizes(border=True, title="Frame1.8"), "Label 1.8") tabWidget1.addTab(ttk.TTkTestWidgetSizes(border=True, title="Frame1.8"), "Label 1.8")
tabWidget2.addTab(ttk.TTkTestWidget( border=True, title="Frame1.9"), "Label Test 1.9")
tabWidget3.addTab(ttk.TTkTestWidgetSizes(border=True, title="Frame1.10"), "Label 1.10") tabWidget2.addTab(ttk.TTkTestWidgetSizes(border=True, title="Frame1.10"), "Label 2.1")
tabWidget2.addTab(ttk.TTkTestWidget( border=True, title="Frame1.9"), "Label Test 2.2")
tabWidget3.addTab(ttk.TTkTestWidgetSizes(border=True, title="Frame1.10"), "Label 3.1")
tabWidget3.addTab(ttk.TTkTestWidget( border=True, title="Frame1.9"), "Label Test 3.2")
fileMenu1 = tabWidget1.addMenu("XX") fileMenu1 = tabWidget1.addMenu("XX")
fileMenu1.addMenu("Open") fileMenu1.addMenu("Open")

70
docs/MDNotes/internals/focus.keypress.md

@ -0,0 +1,70 @@
# Current
## Current Status of the focus/keypress logic
```
<INPUT>
└─▶ KeyEvent
└─▶ TTk._key_event()
try 1 ─▶ send KeyEvent to Focus Widget
try 2 ─▶ send KeyEvent to Shortcut Engine
try 3 ─▶ Check and handle for next Focus
try 4 ─▶ check and handle for prev focus
```
## Reworked Status of the focus/keypress logic
1) Add default KeyPress handler
This handler is supposed to switch the focus (next/prev) or return False
1) Add focus proxy/orchestrator/helper in TTkContainer (New Class? or internally Managed?)
####ß Require (so far)
* Next Focus
* Prev Focus
* First Focus
* Last Focus
* Get Focussed
* Focus Widget
* UnFocus Widget
* Add Widget(s)
* Remove Widget(s)
* Insert Widget(s)
1) Key Propagation
```
<INPUT>
└─▶ KeyEvent
└─▶ TTk.keyEvent(kevt)
try 1 ─▶ send KeyEvent to
└─▶ super().keyEvent(kevt) (TTkContainer)
try 1 : send key event to the focussed
if return False;
try 2 : if Tab/Right focus Next
try 3 : if ^Tab/Left focus Prev
If nothing execute return False
if not handled, the tab/direction key switch reached the last/first widget:
try 3 ─▶ Tab/Right focus the first
try 4 ─▶ ^Tab/Left focus the last
```
2) Focus Propagation
```
```
# TODO
[x] - Implement root handler to handle overlay widgets where the focus switch should be contained in the overlay
[x] - Remove nextFocus,prevFocus from the helper
[ ] - Investigate other widgets focus propagation
[ ] - Switch Focus to the menu
[ ] - Type TTkLayout and add docstrings
[ ] - Add deprecated methods in ttkhelper
[x] - Investigate lineedit of the combobox
[x] - Tab Widget: Adapt to the new logic
[x] - DateTime: Adapt to the new logic
[ ] - Tab Widget: Apply Highlight colors

6
libs/pyTermTk/TermTk/TTkAbstract/abstractscrollview.py

@ -356,7 +356,7 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf
self._viewOffsetY == y: # Nothong to do self._viewOffsetY == y: # Nothong to do
return return
self._excludeEvent = True self._excludeEvent = True
for widget in self.iterWidgets(recurse=False): for widget in self.iterWidgets():
widget.viewMoveTo(x,y) widget.viewMoveTo(x,y)
self._excludeEvent = False self._excludeEvent = False
self._viewOffsetX = x self._viewOffsetX = x
@ -397,7 +397,7 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf
# Override this function # Override this function
def viewFullAreaSize(self) -> tuple[int,int]: def viewFullAreaSize(self) -> tuple[int,int]:
w,h=0,0 w,h=0,0
for widget in self.iterWidgets(recurse=False): for widget in self.iterWidgets():
ww,wh = widget.viewFullAreaSize() ww,wh = widget.viewFullAreaSize()
w = max(w,ww) w = max(w,ww)
h = max(h,wh) h = max(h,wh)
@ -406,7 +406,7 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf
# Override this function # Override this function
def viewDisplayedSize(self) -> tuple[int,int]: def viewDisplayedSize(self) -> tuple[int,int]:
w,h=0,0 w,h=0,0
for widget in self.iterWidgets(recurse=False): for widget in self.iterWidgets():
ww,wh = widget.viewDisplayedSize() ww,wh = widget.viewDisplayedSize()
w = max(w,ww) w = max(w,ww)
h = max(h,wh) h = max(h,wh)

227
libs/pyTermTk/TermTk/TTkCore/helper.py

@ -45,7 +45,6 @@ class TTkHelper:
This is a collection of helper utilities to be used all around TermTk This is a collection of helper utilities to be used all around TermTk
''' '''
_focusWidget: Optional[TTkWidget] = None
_rootCanvas: Optional[TTkCanvas] = None _rootCanvas: Optional[TTkCanvas] = None
_rootWidget: Optional[TTk] = None _rootWidget: Optional[TTk] = None
_updateWidget:Set[TTkWidget] = set() _updateWidget:Set[TTkWidget] = set()
@ -55,14 +54,6 @@ class TTkHelper:
_cursor: bool = False _cursor: bool = False
_cursorType: str = TTkTerm.Cursor.BLINKING_BLOCK _cursorType: str = TTkTerm.Cursor.BLINKING_BLOCK
_cursorWidget: Optional[TTkWidget] = None _cursorWidget: Optional[TTkWidget] = None
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: list[TTkHelper._Overlay] = []
@staticmethod @staticmethod
def updateAll() -> None: def updateAll() -> None:
@ -126,165 +117,43 @@ class TTkHelper:
def getTerminalSize() -> Tuple[int, int]: def getTerminalSize() -> Tuple[int, int]:
return TTkGlbl.term_w, TTkGlbl.term_h return TTkGlbl.term_w, TTkGlbl.term_h
@staticmethod
def rootOverlay(widget: Optional[TTkWidget]) -> Optional[TTkWidget]:
if not widget:
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 focusLastModal() -> None:
if modal := TTkHelper.getLastModal():
modal._widget.setFocus()
@staticmethod
def getLastModal() -> Optional[TTkHelper._Overlay]:
modal = None
for o in TTkHelper._overlay:
if o._modal:
modal = o
return modal
@staticmethod @staticmethod
def checkModalOverlay(widget: TTkWidget) -> bool: def checkModalOverlay(widget: TTkWidget) -> bool:
#if not TTkHelper._overlay: if not TTkHelper._rootWidget:
# # 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 return False
if rootWidget in [ o._widget for o in TTkHelper._overlay[TTkHelper._overlay.index(lastModal):]]: return TTkHelper._rootWidget._checkModalOverlay(widget)
return True
# if TTkHelper._overlay[-1]._widget == rootWidget:
# return True
return False
@staticmethod
def isOverlay(widget: Optional[TTkWidget]) -> bool:
return TTkHelper.rootOverlay(widget) is not None
@staticmethod @staticmethod
def overlay(caller: Optional[TTkWidget], widget: TTkWidget, x:int, y:int, modal:bool=False, forceBoundaries:bool=True, toolWindow:bool=False) -> None: def overlay(caller: Optional[TTkWidget], widget: TTkWidget, x:int, y:int, modal:bool=False, forceBoundaries:bool=True, toolWindow:bool=False) -> None:
'''overlay''' '''overlay'''
if not TTkHelper._rootWidget: if not TTkHelper._rootWidget:
return return
if not caller: TTkHelper._rootWidget.overlay(
caller = TTkHelper._rootWidget caller=caller,
wx, wy = TTkHelper.absPos(caller) widget=widget,
w,h = widget.size() pos=(x,y),
modal=modal,
# Try to keep the overlay widget inside the terminal forceBoundaries=forceBoundaries,
if forceBoundaries: toolWindow=toolWindow
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 )
mw,mh = widget.minimumSize()
ww = min(w,max(mw, TTkGlbl.term_w))
wh = min(h,max(mh, TTkGlbl.term_h))
widget.resize(ww,wh)
else:
wx += x
wy += y
wi = widget.widgetItem()
wi.setLayer(wi.LAYER1)
if toolWindow:
# Forcing the layer to:
# TTkLayoutItem.LAYER1 = 0x40000000
widget.move(wx,wy)
else:
TTkHelper._overlay.append(TTkHelper._Overlay(wx,wy,widget,TTkHelper._focusWidget,modal))
TTkHelper._rootWidget.rootLayout().addWidget(widget)
widget.setFocus()
widget.raiseWidget()
if hasattr(widget,'rootLayout'):
for w in widget.rootLayout().iterWidgets(onlyVisible=True):
w.update()
@staticmethod
def getOverlay() -> Optional[TTkWidget]:
if TTkHelper._overlay:
return TTkHelper._overlay[-1]._widget
return None
@staticmethod @staticmethod
def removeOverlay() -> None: def removeOverlay() -> None:
if not TTkHelper._rootWidget: if not TTkHelper._rootWidget:
return return
if not TTkHelper._overlay: return TTkHelper._rootWidget._removeOverlay()
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 @staticmethod
def removeOverlayAndChild(widget: Optional[TTkWidget]) -> None: def removeOverlayAndChild(widget: Optional[TTkWidget]) -> None:
if not TTkHelper._rootWidget or not widget: if not TTkHelper._rootWidget:
return
if not TTkHelper.isOverlay(widget):
return return
if len(TTkHelper._overlay) <= 1: return TTkHelper._rootWidget._removeOverlayAndChild(widget=widget)
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 @staticmethod
def removeOverlayChild(widget: TTkWidget) -> None: def removeOverlayChild(widget: TTkWidget) -> None:
if not TTkHelper._rootWidget: if not TTkHelper._rootWidget:
return return
rootWidget = TTkHelper.rootOverlay(widget) return TTkHelper._rootWidget._removeOverlayChild(widget=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 @staticmethod
def setMousePos(pos: Tuple[int, int]) -> None: def setMousePos(pos: Tuple[int, int]) -> None:
@ -419,74 +288,6 @@ class TTkHelper:
layout = layout.parent() layout = layout.parent()
return (wx, wy) return (wx, wy)
@staticmethod
def nextFocus(widget: TTkWidget) -> None:
from TermTk.TTkWidgets.container import TTkContainer
if not TTkHelper._rootWidget:
return
rootWidget = TTkHelper.rootOverlay(widget)
checkWidget:Optional[TTkWidget] = widget
if not rootWidget:
rootWidget = TTkHelper._rootWidget
if checkWidget == rootWidget:
checkWidget = None
if not isinstance(rootWidget, TTkContainer):
return
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 checkWidget:
if w == checkWidget:
checkWidget=None
continue
if w.isEnabled() and w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus:
w.setFocus()
w.update()
return
if first:
first.setFocus()
first.update()
@staticmethod
def prevFocus(widget: TTkWidget) -> None:
from TermTk.TTkWidgets.container import TTkContainer
if not TTkHelper._rootWidget:
return
rootWidget = TTkHelper.rootOverlay(widget)
checkWidget:Optional[TTkWidget] = widget
if not rootWidget:
rootWidget = TTkHelper._rootWidget
if checkWidget == rootWidget:
checkWidget = None
if not isinstance(rootWidget, TTkContainer):
return
prev = None
for w in rootWidget.rootLayout().iterWidgets():
# TTkLog.debug(f"{w._name} {widget}")
if w == checkWidget:
checkWidget=None
if prev:
break
if w.isEnabled() and w.focusPolicy() & TTkK.TabFocus == TTkK.TabFocus:
prev = w
if prev:
prev.setFocus()
prev.update()
@staticmethod
def setFocus(widget: TTkWidget) -> None:
TTkHelper._focusWidget = widget
@staticmethod
def getFocus() -> Optional[TTkWidget]:
return TTkHelper._focusWidget
@staticmethod
def clearFocus() -> None:
TTkHelper._focusWidget = None
@staticmethod @staticmethod
def showCursor(cursorType: int = TTkK.Cursor_Blinking_Block) -> None: def showCursor(cursorType: int = TTkK.Cursor_Blinking_Block) -> None:
newType = { newType = {

35
libs/pyTermTk/TermTk/TTkCore/ttk.py

@ -30,7 +30,7 @@ import threading
import platform import platform
import contextlib import contextlib
from typing import Optional, Callable, List from typing import Optional, List
from TermTk.TTkCore.drivers import TTkSignalDriver from TermTk.TTkCore.drivers import TTkSignalDriver
from TermTk.TTkCore.TTkTerm.input import TTkInput from TermTk.TTkCore.TTkTerm.input import TTkInput
@ -44,10 +44,9 @@ from TermTk.TTkCore.cfg import TTkCfg, TTkGlbl
from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.helper import TTkHelper
from TermTk.TTkCore.timer import TTkTimer from TermTk.TTkCore.timer import TTkTimer
from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.color import TTkColor
from TermTk.TTkCore.shortcut import TTkShortcut
from TermTk.TTkWidgets.about import TTkAbout from TermTk.TTkWidgets.about import TTkAbout
from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkWidgets.widget import TTkWidget
from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.rootcontainer import _TTkRootContainer
class _TTkStderrHandler(io.TextIOBase): class _TTkStderrHandler(io.TextIOBase):
@ -105,7 +104,7 @@ class _MouseCursor():
self.updated.emit() self.updated.emit()
class TTk(TTkContainer): class TTk(_TTkRootContainer):
__slots__ = ( __slots__ = (
'_termMouse', '_termDirectMouse', '_termMouse', '_termDirectMouse',
'_title', '_title',
@ -288,7 +287,7 @@ class TTk(TTkContainer):
@pyTTkSlot(str) @pyTTkSlot(str)
def _processPaste(self, txt:str): def _processPaste(self, txt:str):
if focusWidget := TTkHelper.getFocus(): if focusWidget := self._getFocusWidget():
while focusWidget and not focusWidget.pasteEvent(txt): while focusWidget and not focusWidget.pasteEvent(txt):
focusWidget = focusWidget.parentWidget() focusWidget = focusWidget.parentWidget()
@ -296,7 +295,7 @@ class TTk(TTkContainer):
def _processInput(self, kevt, mevt): def _processInput(self, kevt, mevt):
with self._drawMutex: with self._drawMutex:
if kevt is not None: if kevt is not None:
self._key_event(kevt) self.keyEvent(kevt)
if mevt is not None: if mevt is not None:
self._mouse_event(mevt) self._mouse_event(mevt)
@ -320,7 +319,7 @@ class TTk(TTkContainer):
# Mouse Events forwarded straight to the Focus widget: # Mouse Events forwarded straight to the Focus widget:
# - Drag # - Drag
# - Release # - Release
focusWidget = TTkHelper.getFocus() focusWidget = self._getFocusWidget()
if ( focusWidget is not None and if ( focusWidget is not None and
( mevt.evt == TTkK.Drag or ( mevt.evt == TTkK.Drag or
mevt.evt == TTkK.Release ) and mevt.evt == TTkK.Release ) and
@ -343,32 +342,12 @@ class TTk(TTkContainer):
TTkHelper.dndEnter(None) TTkHelper.dndEnter(None)
if mevt.evt == TTkK.Press and focusWidget: if mevt.evt == TTkK.Press and focusWidget:
focusWidget.clearFocus() focusWidget.clearFocus()
TTkHelper.focusLastModal() self._focusLastModal()
# Clean the Drag and Drop in case of mouse release # Clean the Drag and Drop in case of mouse release
if mevt.evt == TTkK.Release: if mevt.evt == TTkK.Release:
TTkHelper.dndEnd() TTkHelper.dndEnd()
def _key_event(self, kevt):
keyHandled = False
# TTkLog.debug(f"Key: {kevt}")
focusWidget = TTkHelper.getFocus()
# TTkLog.debug(f"{focusWidget}")
if focusWidget is not None:
keyHandled = focusWidget.keyEvent(kevt)
if not keyHandled:
TTkShortcut.processKey(kevt, focusWidget)
# Handle Next Focus Key Binding
if not keyHandled and \
((kevt.key == TTkK.Key_Tab and kevt.mod == TTkK.NoModifier) or
( kevt.key == TTkK.Key_Right or kevt.key == TTkK.Key_Down)):
TTkHelper.nextFocus(focusWidget if focusWidget else self)
# Handle Prev Focus Key Binding
if not keyHandled and \
((kevt.key == TTkK.Key_Tab and kevt.mod == TTkK.ShiftModifier) or
( kevt.key == TTkK.Key_Left or kevt.key == TTkK.Key_Up)):
TTkHelper.prevFocus(focusWidget if focusWidget else self)
def _time_event(self): def _time_event(self):
# Event.{wait and clear} should be atomic, # Event.{wait and clear} should be atomic,
# BUTt: ( y ) # BUTt: ( y )

46
libs/pyTermTk/TermTk/TTkLayouts/layout.py

@ -24,11 +24,18 @@
**Layout** [:ref:`Tutorial <Layout-Tutorial_Intro>`] **Layout** [:ref:`Tutorial <Layout-Tutorial_Intro>`]
''' '''
from __future__ import annotations
__all__ = ['TTkLayoutItem', 'TTkLayout'] __all__ = ['TTkLayoutItem', 'TTkLayout']
from typing import TYPE_CHECKING,Generator
from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.constant import TTkK
if TYPE_CHECKING:
from TermTk.TTkWidgets.widget import TTkWidget
class TTkLayoutItem(): class TTkLayoutItem():
''' :py:class:`~TTkLayoutItem` is the base class of layout Items inherited by :py:class:`~TTkLayout`, :py:class:`~TTkWidgetItem`, and all the derived layout managers. ''' :py:class:`~TTkLayoutItem` is the base class of layout Items inherited by :py:class:`~TTkLayout`, :py:class:`~TTkWidgetItem`, and all the derived layout managers.
@ -220,13 +227,29 @@ class TTkLayout(TTkLayoutItem):
else: else:
return self._parent.parentWidget() return self._parent.parentWidget()
def iterWidgets(self, onlyVisible=True, recurse=True): def iterWidgets(
for child in self._items: self,
onlyVisible: bool = True,
recurse: bool = True,
reverse: bool = False) -> Generator[TTkWidget, None, None]:
'''
Iterate over all widgets in the layout.
:param onlyVisible: if True, only yield visible widgets
:type onlyVisible: bool
:param recurse: if True, recursively iterate through nested layouts
:type recurse: bool
:param reverse: if True, iterate in reverse order
:type reverse: bool
:return: generator yielding widgets
:rtype: Generator[:py:class:`TTkWidget`, None, None]
'''
items = reversed(self._items) if reverse else self._items
for child in items:
if child._layoutItemType == TTkK.WidgetItem: if child._layoutItemType == TTkK.WidgetItem:
if onlyVisible and not child.widget().isVisible(): continue if onlyVisible and not child.widget().isVisible(): continue
yield child.widget() yield child.widget()
if recurse and hasattr(cw:=child.widget(),'rootLayout'):
yield from cw.rootLayout().iterWidgets(onlyVisible, recurse)
if child._layoutItemType == TTkK.LayoutItem and recurse: if child._layoutItemType == TTkK.LayoutItem and recurse:
yield from child.iterWidgets(onlyVisible, recurse) yield from child.iterWidgets(onlyVisible, recurse)
@ -237,14 +260,10 @@ class TTkLayout(TTkLayoutItem):
def zSortedItems(self): return self._zSortedItems def zSortedItems(self): return self._zSortedItems
def replaceItem(self, item, index): def replaceItem(self, item, index):
self._items[index] = item if index < 0 or index >= len(self._items):
self._zSortItems() raise ValueError(f"The {index=} is not inside the items list")
self.update() self.removeItem(item=self._items[index])
item.setParent(self) self.insertItem(item=item,index=index)
if item._layoutItemType == TTkK.WidgetItem:
item.widget().setParent(self.parentWidget())
if self.parentWidget():
self.parentWidget().update(repaint=True, updateLayout=True)
def addItem(self, item): def addItem(self, item):
self.insertItems(len(self._items),[item]) self.insertItems(len(self._items),[item])
@ -403,7 +422,8 @@ class TTkWidgetItem(TTkLayoutItem):
TTkLayoutItem.__init__(self, layoutItemType=TTkK.LayoutItemTypes.WidgetItem, **kwargs) TTkLayoutItem.__init__(self, layoutItemType=TTkK.LayoutItemTypes.WidgetItem, **kwargs)
self._widget = widget self._widget = widget
def widget(self): return self._widget def widget(self) -> TTkWidget:
return self._widget
def isVisible(self): return self._widget.isVisible() def isVisible(self): return self._widget.isVisible()

2
libs/pyTermTk/TermTk/TTkWidgets/Fancy/tableview.py

@ -443,7 +443,7 @@ class TTkFancyTableView(TTkAbstractScrollView):
self._viewOffsetY == y: # Nothong to do self._viewOffsetY == y: # Nothong to do
return return
self._excludeEvent = True self._excludeEvent = True
for widget in self.layout().iterWidgets(recurse=False): for widget in self.layout().iterWidgets():
widget.viewMoveTo(x,y) widget.viewMoveTo(x,y)
self._excludeEvent = False self._excludeEvent = False
self._viewOffsetX = x self._viewOffsetX = x

9
libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py

@ -507,7 +507,7 @@ class _DateTime_DateProxy(TTkDate, TTkTableProxyEditWidget, _DateTime_KeyGeneric
return self.newKeyEvent(evt,super().keyEvent) return self.newKeyEvent(evt,super().keyEvent)
class _DateTime_DateTimeProxy(TTkDateTime, TTkTableProxyEditWidget): class _DateTime_DateTimeProxy(TTkDateTime, TTkTableProxyEditWidget, _DateTime_KeyGeneric):
''' DateTime editor for table cells ''' DateTime editor for table cells
Extends :py:class:`TTkDateTime` with table-specific signals Extends :py:class:`TTkDateTime` with table-specific signals
@ -565,15 +565,12 @@ class _DateTime_DateTimeProxy(TTkDateTime, TTkTableProxyEditWidget):
def keyEvent(self, evt: TTkKeyEvent) -> bool: def keyEvent(self, evt: TTkKeyEvent) -> bool:
''' Handle keyboard events for navigation ''' Handle keyboard events for navigation
Always triggers right navigation on any key event.
:param evt: The keyboard event :param evt: The keyboard event
:type evt: TTkKeyEvent :type evt: TTkKeyEvent
:return: Always returns True :return: True if event was handled, False otherwise
:rtype: bool :rtype: bool
''' '''
self.leavingTriggered.emit(TTkTableEditLeaving.RIGHT) return self.newKeyEvent(evt,super().keyEvent)
return True
class _TextPickerProxy(TTkTextPicker, TTkTableProxyEditWidget): class _TextPickerProxy(TTkTextPicker, TTkTableProxyEditWidget):
''' Rich text editor for table cells ''' Rich text editor for table cells

4
libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablewidget.py

@ -1233,6 +1233,10 @@ class TTkTableWidget(TTkAbstractScrollView):
self.update() self.update()
def keyEvent(self, evt:TTkKeyEvent) -> bool: def keyEvent(self, evt:TTkKeyEvent) -> bool:
if _pw:=self._edit_proxy_widget:
if _pw.widget.keyEvent(evt=evt):
return True
# rows = self._tableModel.rowCount() # rows = self._tableModel.rowCount()
cols = self._tableModel.columnCount() cols = self._tableModel.columnCount()
if self._currentPos: if self._currentPos:

2
libs/pyTermTk/TermTk/TTkWidgets/combobox.py

@ -441,7 +441,7 @@ class TTkComboBox(TTkContainer):
( evt.type == TTkK.SpecialKey and evt.key in [TTkK.Key_Enter,TTkK.Key_Down] ): ( evt.type == TTkK.SpecialKey and evt.key in [TTkK.Key_Enter,TTkK.Key_Down] ):
self._pressEvent() self._pressEvent()
return True return True
return False return super().keyEvent(evt=evt)
def focusInEvent(self) -> None: def focusInEvent(self) -> None:
if self._editable: if self._editable:

97
libs/pyTermTk/TermTk/TTkWidgets/container.py

@ -20,15 +20,19 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import annotations
__all__ = ['TTkContainer', 'TTkPadding'] __all__ = ['TTkContainer', 'TTkPadding']
from typing import NamedTuple, Optional from typing import NamedTuple, Optional, List
from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.helper import TTkHelper
from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot
from TermTk.TTkCore.shortcut import TTkShortcut
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent
from TermTk.TTkLayouts.layout import TTkLayout from TermTk.TTkLayouts.layout import TTkLayout
from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkWidgets.widget import TTkWidget
@ -151,6 +155,84 @@ class TTkContainer(TTkWidget):
self._layout.setParent(self) self._layout.setParent(self)
self.update(updateLayout=True) self.update(updateLayout=True)
def _getFocusWidget(self) -> Optional[TTkWidget]:
if (_pw:=self.parentWidget()):
return _pw._getFocusWidget()
return None
def _setFocusWidget(self, widget:Optional[TTkWidget]) -> None:
if not (_pw:=self.parentWidget()):
return
_pw._setFocusWidget(widget)
self.update()
def _getFirstFocus(self, widget:Optional[TTkWidget], focusPolicy:TTkK.FocusPolicy) -> Optional[TTkWidget]:
widgets = list(self.layout().iterWidgets(onlyVisible=True,recurse=True,reverse=False))
if widget:
if widget not in (_lw:=widgets):
return None
index_widget = _lw.index(widget)
widgets = _lw[index_widget+1:]
for _w in widgets:
if focusPolicy & _w.focusPolicy():
return _w
if isinstance(_w,TTkContainer) and (_fw:=_w._getFirstFocus(widget=None,focusPolicy=focusPolicy)):
return _fw
return None
def _getLastFocus(self, widget:Optional[TTkWidget], focusPolicy:TTkK.FocusPolicy) -> Optional[TTkWidget]:
widgets = list(self.layout().iterWidgets(onlyVisible=True,recurse=True,reverse=True))
if widget:
if widget not in (_lw:=widgets):
return None
index_widget = _lw.index(widget)
widgets = _lw[index_widget+1:]
for _w in widgets:
if isinstance(_w,TTkContainer) and (_fw:=_w._getLastFocus(widget=None,focusPolicy=focusPolicy)):
return _fw
if focusPolicy & _w.focusPolicy():
return _w
return None
def _focusChildWidget(self) -> Optional[TTkWidget]:
if not (_fw := self._getFocusWidget()):
return None
while (_pw:=_fw.parentWidget()) and _pw is not self:
_fw = _pw
if _pw is self:
return _fw
return None
def keyEvent(self, evt:TTkKeyEvent) -> bool:
if (_cfw := self._focusChildWidget()) is not None:
if _cfw.keyEvent(evt):
return True
if TTkShortcut.processKey(evt, _cfw):
return True
# Handle Next Focus Key Binding
if ( (evt.key == TTkK.Key_Tab and evt.mod == TTkK.NoModifier) or
(evt.key in (TTkK.Key_Right, TTkK.Key_Down ) ) ) :
if _nfw:=self._getFirstFocus(widget=_cfw,focusPolicy=TTkK.FocusPolicy.TabFocus):
_nfw.setFocus()
return True
if ( (evt.key == TTkK.Key_Tab and evt.mod == TTkK.ShiftModifier) or
(evt.key in ( TTkK.Key_Left, TTkK.Key_Up ) ) ) :
if self._getFocusWidget() is self:
return False
if _pfw:=self._getLastFocus(widget=_cfw,focusPolicy=TTkK.FocusPolicy.TabFocus):
_pfw.setFocus()
return True
if _cfw and TTkK.FocusPolicy.TabFocus & self.focusPolicy():
self.setFocus()
return True
return False
def addWidget(self, widget:TTkWidget) -> None: def addWidget(self, widget:TTkWidget) -> None:
''' '''
.. warning:: .. warning::
@ -165,7 +247,8 @@ class TTkContainer(TTkWidget):
parentWidget.layout().addWidget(childWidget) parentWidget.layout().addWidget(childWidget)
''' '''
TTkLog.error("<TTkWidget>.addWidget(...) is deprecated, use <TTkWidget>.layout().addWidget(...)") TTkLog.error("<TTkWidget>.addWidget(...) is deprecated, use <TTkWidget>.layout().addWidget(...)")
if self.layout(): self.layout().addWidget(widget) if self.layout():
self.layout().addWidget(widget)
def removeWidget(self, widget:TTkWidget) -> None: def removeWidget(self, widget:TTkWidget) -> None:
''' '''
@ -181,7 +264,8 @@ class TTkContainer(TTkWidget):
parentWidget.layout().removeWidget(childWidget) parentWidget.layout().removeWidget(childWidget)
''' '''
TTkLog.error("<TTkWidget>.removeWidget(...) is deprecated, use <TTkWidget>.layout().removeWidget(...)") TTkLog.error("<TTkWidget>.removeWidget(...) is deprecated, use <TTkWidget>.layout().removeWidget(...)")
if self.layout(): self.layout().removeWidget(widget) if self.layout():
self.layout().removeWidget(widget)
# def forwardStyleTo(self, widget:TTkWidget): # def forwardStyleTo(self, widget:TTkWidget):
# widget._currentStyle |= self._currentStyle # widget._currentStyle |= self._currentStyle
@ -190,9 +274,9 @@ class TTkContainer(TTkWidget):
def _processForwardStyle(self) -> None: def _processForwardStyle(self) -> None:
if not self._forwardStyle: return if not self._forwardStyle: return
def _getChildren(): def _getChildren():
for w in self.rootLayout().iterWidgets(onlyVisible=True, recurse=False): for w in self.rootLayout().iterWidgets(onlyVisible=True):
yield w yield w
for w in self.layout().iterWidgets(onlyVisible=True, recurse=False): for w in self.layout().iterWidgets(onlyVisible=True):
yield w yield w
for w in _getChildren(): for w in _getChildren():
@ -395,4 +479,7 @@ class TTkContainer(TTkWidget):
for w in self.rootLayout().iterWidgets(onlyVisible=False, recurse=True): for w in self.rootLayout().iterWidgets(onlyVisible=False, recurse=True):
if w._name == name: if w._name == name:
return w return w
if isinstance(w, TTkContainer):
if _w:=w.getWidgetByName(name):
return _w
return None return None

15
libs/pyTermTk/TermTk/TTkWidgets/datetime_date_form.py

@ -550,7 +550,15 @@ class _TTkDateCal(TTkWidget):
'focus': {'color': TTkColor.fgbg("#888888","#000066")+TTkColor.UNDERLINE} 'focus': {'color': TTkColor.fgbg("#888888","#000066")+TTkColor.UNDERLINE}
} }
__slots__ = ('_state') __slots__ = ('_state', 'dateChanged')
dateChanged:pyTTkSignal
'''
This signal is emitted whenever the selected date changes.
:param date: The new selected date
:type date: :py:class:`datetime.date`
'''
_state:_TTkDateWidgetState _state:_TTkDateWidgetState
@ -563,6 +571,7 @@ class _TTkDateCal(TTkWidget):
:param state: Shared state object for date management :param state: Shared state object for date management
:type state: :py:class:`_TTkDateWidgetState` :type state: :py:class:`_TTkDateWidgetState`
''' '''
self.dateChanged = pyTTkSignal(datetime.date)
self._state = state self._state = state
super().__init__(**kwargs|{'size':(20,6)}) super().__init__(**kwargs|{'size':(20,6)})
self.setFocusPolicy(TTkK.ClickFocus | TTkK.TabFocus) self.setFocusPolicy(TTkK.ClickFocus | TTkK.TabFocus)
@ -597,6 +606,7 @@ class _TTkDateCal(TTkWidget):
return True return True
elif evt.key == ' ': elif evt.key == ' ':
self._state.setDate(self._state._highlighted) self._state.setDate(self._state._highlighted)
self.dateChanged.emit(self._state._highlighted)
self.update() self.update()
return True return True
return False return False
@ -625,6 +635,7 @@ class _TTkDateCal(TTkWidget):
if 0 <= y <= 5 and 0 <= x < 20: if 0 <= y <= 5 and 0 <= x < 20:
if _d := self._getDayFromPos(x,y): if _d := self._getDayFromPos(x,y):
self._state.setDate(_d) self._state.setDate(_d)
self.dateChanged.emit(_d)
self.update() self.update()
return True return True
@ -753,12 +764,12 @@ class TTkDateForm(TTkContainer):
date = datetime.date.today() date = datetime.date.today()
self._state = _TTkDateWidgetState(date=date) self._state = _TTkDateWidgetState(date=date)
self._state.highlightedChanged.connect(self.update) self._state.highlightedChanged.connect(self.update)
self.dateChanged = self._state.dateChanged
size = (20,8) size = (20,8)
super().__init__(**kwargs|{'size':size, 'minSize':size}) super().__init__(**kwargs|{'size':size, 'minSize':size})
self._calWidget = _TTkDateCal(parent=self, pos=(0,2), state=self._state) self._calWidget = _TTkDateCal(parent=self, pos=(0,2), state=self._state)
self._yearWidget = _TTkDateYear(parent=self, pos=(2,0), state=self._state) self._yearWidget = _TTkDateYear(parent=self, pos=(2,0), state=self._state)
self._monthWidget = _TTkDateMonth(parent=self, pos=(12,0), state=self._state) self._monthWidget = _TTkDateMonth(parent=self, pos=(12,0), state=self._state)
self.dateChanged = self._calWidget.dateChanged
def date(self) -> datetime.date: def date(self) -> datetime.date:
''' '''

2
libs/pyTermTk/TermTk/TTkWidgets/datetime_datetime.py

@ -132,4 +132,4 @@ class TTkDateTime(TTkContainer):
self._datetime = datetime self._datetime = datetime
self._dateWidget.setDate(datetime.date()) self._dateWidget.setDate(datetime.date())
self._timeWidget.setTime(datetime.time()) self._timeWidget.setTime(datetime.time())
self.datetimeChanged.emit(datetime) self.datetimeChanged.emit(datetime)

393
libs/pyTermTk/TermTk/TTkWidgets/rootcontainer.py

@ -0,0 +1,393 @@
# MIT License
#
# Copyright (c) 2025 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__ = []
from typing import Optional, List, Tuple
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.shortcut import TTkShortcut
from TermTk.TTkWidgets.container import TTkContainer
from TermTk.TTkWidgets.widget import TTkWidget
def _absPos(widget: TTkWidget) -> Tuple[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)
class _TTkOverlay():
__slots__ = ('_widget','_prevFocus','_modal')
_widget:TTkWidget
_prevFocus:Optional[TTkWidget]
_modal:bool
def __init__(
self,
pos:Tuple[int,int],
widget:TTkWidget,
prevFocus:Optional[TTkWidget],
modal:bool):
self._widget = widget
self._prevFocus = prevFocus
self._modal = modal
widget.move(*pos)
class _TTkRootContainer(TTkContainer):
''' _TTkRootContainer:
Internal root container class that manages the application's root widget hierarchy and focus navigation.
This class is not meant to be used directly by application code. It is instantiated internally by :py:class:`TTk`
to provide the top-level container for all widgets and handle keyboard-based focus traversal.
The root container manages focus cycling when Tab/Shift+Tab or arrow keys are pressed and no widget
consumes the event, ensuring focus loops back to the first/last focusable widget.
'''
__slots__ = (
'_focusWidget',
'_overlay')
_focusWidget:Optional[TTkWidget]
_overlay:List[_TTkOverlay]
def __init__(self, **kwargs) -> None:
self._focusWidget = None
self._overlay = []
super().__init__(**kwargs)
def _getFocusWidget(self) -> Optional[TTkWidget]:
'''
Returns the currently focused widget.
:return: the widget with focus, or None if no widget has focus
:rtype: :py:class:`TTkWidget` or None
'''
return self._focusWidget
def _setFocusWidget(self, widget:Optional[TTkWidget]) -> None:
'''
Sets the currently focused widget and triggers a repaint.
:param widget: the widget to receive focus, or None to clear focus
:type widget: :py:class:`TTkWidget` or None
'''
if self._focusWidget is widget:
return
self._focusWidget = widget
self.update()
def _loopFocus(self, container:TTkContainer, evt:TTkKeyEvent) -> bool:
if ( (evt.key == TTkK.Key_Tab and evt.mod == TTkK.NoModifier) or
(evt.key in (TTkK.Key_Right, TTkK.Key_Down ) ) ) :
if _nfw:=container._getFirstFocus(widget=None,focusPolicy=TTkK.FocusPolicy.TabFocus):
_nfw.setFocus()
return True
if ( (evt.key == TTkK.Key_Tab and evt.mod == TTkK.ShiftModifier) or
(evt.key in ( TTkK.Key_Left, TTkK.Key_Up ) ) ) :
if _pfw:=container._getLastFocus(widget=None,focusPolicy=TTkK.FocusPolicy.TabFocus):
_pfw.setFocus()
return True
return False
def _handleOverlay(self, evt:TTkKeyEvent) -> bool:
if not self._overlay:
return False
_overlay = self._overlay[-1]
_widget = _overlay._widget
if _widget.keyEvent(evt=evt):
return True
if isinstance(_widget, TTkContainer) and self._loopFocus(evt=evt, container=_widget):
return True
def overlay(self,
caller: Optional[TTkWidget],
widget: TTkWidget,
pos:Tuple[int,int],
modal:bool=False,
forceBoundaries:bool=True,
toolWindow:bool=False) -> None:
'''
Adds a widget as an overlay on top of the current widget hierarchy.
The overlay widget is positioned relative to the caller widget and automatically
adjusted to stay within the root container boundaries (if forceBoundaries is True).
Overlays can be modal (blocking interaction with underlying widgets) or non-modal.
:param caller: the widget relative to which the overlay is positioned, or None to use root
:type caller: :py:class:`TTkWidget` or None
:param widget: the widget to display as an overlay
:type widget: :py:class:`TTkWidget`
:param pos: the (x, y) position offset relative to the caller widget
:type pos: tuple[int, int]
:param modal: if True, blocks interaction with underlying widgets
:type modal: bool
:param forceBoundaries: if True, adjusts position and size to keep overlay within root boundaries
:type forceBoundaries: bool
:param toolWindow: if True, treats the overlay as a tool window without focus management
:type toolWindow: bool
'''
if not caller:
caller = self
x,y = pos
wx, wy = _absPos(caller)
w,h = widget.size()
rw,rh = self.rootLayout().size()
# Try to keep the overlay widget inside the rootContainer boundaries
if forceBoundaries:
wx = max(0, wx+x if wx+x+w < rw else rw-w )
wy = max(0, wy+y if wy+y+h < rh else rh-h )
mw,mh = widget.minimumSize()
ww = min(w,max(mw, rw))
wh = min(h,max(mh, rh))
widget.resize(ww,wh)
else:
wx += x
wy += y
wi = widget.widgetItem()
# Forcing the layer to:
# TTkLayoutItem.LAYER1 = 0x40000000
wi.setLayer(wi.LAYER1)
if toolWindow:
widget.move(wx,wy)
else:
_fw = self._getFocusWidget()
self._overlay.append(_TTkOverlay(
pos=(wx,wy),
widget=widget,
prevFocus=_fw,
modal=modal))
self.rootLayout().addWidget(widget)
widget.raiseWidget()
widget.setFocus()
if isinstance(widget, TTkContainer):
for w in widget.rootLayout().iterWidgets(onlyVisible=True):
w.update()
def _removeOverlay(self) -> None:
'''
Removes the topmost overlay widget and restores focus to the previous widget.
If the overlay is modal, it must be explicitly removed before any underlying
overlays can be removed. Focus is restored to the widget that had focus before
the overlay was added.
'''
if not self._overlay:
return
bkFocus = None
# Remove the first element also if it is modal
self._overlay[-1]._modal = False
while self._overlay:
if self._overlay[-1]._modal:
break
owidget = self._overlay.pop()
bkFocus = owidget._prevFocus
self.rootLayout().removeWidget(owidget._widget)
if _fw:=self._getFocusWidget():
_fw.clearFocus()
if bkFocus:
bkFocus.setFocus()
def _removeOverlayAndChild(self, widget: Optional[TTkWidget]) -> None:
'''
Removes the specified overlay widget and all overlays added after it.
This method finds the root overlay containing the given widget and removes it
along with any child overlays that were added on top of it. Focus is restored
to the widget that had focus before the overlay stack was created.
:param widget: the widget whose root overlay (and children) should be removed
:type widget: :py:class:`TTkWidget` or None
'''
if not widget:
return
if not self._isOverlay(widget):
return
if len(self._overlay) <= 1:
return self._removeOverlay()
rootWidget = self._rootOverlay(widget)
bkFocus = None
found = False
newOverlay = []
for o in self._overlay:
if o._widget == rootWidget:
found = True
bkFocus = o._prevFocus
if not found:
newOverlay.append(o)
else:
self.rootLayout().removeWidget(o._widget)
self._overlay = newOverlay
if bkFocus:
bkFocus.setFocus()
if not found:
self._removeOverlay()
def _removeOverlayChild(self, widget: TTkWidget) -> None:
'''
Removes all overlay widgets that were added after the specified widget.
This method preserves the overlay containing the given widget but removes
all overlays that were added on top of it. If the widget is not found in
the overlay stack, removes the topmost overlay.
:param widget: the widget whose child overlays should be removed
:type widget: :py:class:`TTkWidget`
'''
rootWidget = self._rootOverlay(widget)
found = False
newOverlay = []
for o in self._overlay:
if o._widget == rootWidget:
found = True
newOverlay.append(o)
continue
if not found:
newOverlay.append(o)
else:
self.rootLayout().removeWidget(o._widget)
self._overlay = newOverlay
if not found:
self._removeOverlay()
def _focusLastModal(self) -> None:
'''
Sets focus to the last modal overlay widget in the stack.
This method is used internally to ensure modal overlays maintain focus
when interaction is attempted with underlying widgets.
'''
if modal := self._getLastModal():
modal._widget.setFocus()
def _checkModalOverlay(self, widget: TTkWidget) -> bool:
'''
Checks if a widget is allowed to receive input given the current modal overlay state.
:param widget: the widget to check for input permission
:type widget: :py:class:`TTkWidget`
:return: True if the widget can receive input, False if blocked by a modal overlay
:rtype: bool
'''
#if not TTkHelper._overlay:
# # There are no Overlays
# return True
if not (lastModal := self._getLastModal()):
return True
# if not TTkHelper._overlay[-1]._modal:
# # The last window is not modal
# return True
if not (rootWidget := self._rootOverlay(widget)):
# This widget is not overlay
return False
if rootWidget in [ o._widget for o in self._overlay[self._overlay.index(lastModal):]]:
return True
# if TTkHelper._overlay[-1]._widget == rootWidget:
# return True
return False
def _getLastModal(self) -> Optional[_TTkOverlay]:
'''
Returns the last modal overlay in the stack.
:return: the last modal overlay wrapper, or None if no modal overlays exist
:rtype: :py:class:`_TTkOverlay` or None
'''
modal = None
for o in self._overlay:
if o._modal:
modal = o
return modal
def _isOverlay(self, widget: Optional[TTkWidget]) -> bool:
'''
Checks if a widget is part of the overlay hierarchy.
:param widget: the widget to check
:type widget: :py:class:`TTkWidget` or None
:return: True if the widget is contained in any overlay, False otherwise
:rtype: bool
'''
return self._rootOverlay(widget) is not None
def _rootOverlay(self, widget: Optional[TTkWidget]) -> Optional[TTkWidget]:
'''
Finds the root overlay widget that contains the specified widget.
Traverses the widget's parent hierarchy to find which overlay (if any)
contains it as a child.
:param widget: the widget to search for in the overlay hierarchy
:type widget: :py:class:`TTkWidget` or None
:return: the root overlay widget containing the specified widget, or None if not in any overlay
:rtype: :py:class:`TTkWidget` or None
'''
if not widget:
return None
if not self._overlay:
return None
overlayWidgets = [o._widget for o in self._overlay]
while widget is not None:
if widget in overlayWidgets:
return widget
widget = widget.parentWidget()
return None
def keyEvent(self, evt:TTkKeyEvent) -> bool:
'''
Handles keyboard events for focus navigation.
Implements focus cycling behavior when Tab/Shift+Tab or arrow keys are pressed
and no child widget consumes the event. When the last focusable widget is reached,
focus cycles back to the first widget (and vice versa).
:param evt: the keyboard event
:type evt: :py:class:`TTkKeyEvent`
:return: True if the event was handled, False otherwise
:rtype: bool
'''
if self._handleOverlay(evt=evt):
return True
if super().keyEvent(evt=evt):
return True
# If this is reached after a tab focus event, it means that either
# no focus widgets are defined
# or the last/first focus is reached - the focus need to go to start from the opposite side
return self._loopFocus(evt=evt, container=self)

73
libs/pyTermTk/TermTk/TTkWidgets/tabwidget.py

@ -23,7 +23,7 @@
__all__ = ['TTkTabButton', 'TTkTabBar', 'TTkTabWidget', 'TTkBarType'] __all__ = ['TTkTabButton', 'TTkTabBar', 'TTkTabWidget', 'TTkBarType']
from enum import Enum from enum import Enum
from typing import List, Tuple from typing import List, Tuple, Optional
from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.helper import TTkHelper
@ -89,11 +89,15 @@ _tabStyle = {
} }
_tabStyleNormal = { _tabStyleNormal = {
'default': {'borderColor': TTkColor.RST}, 'default': {'borderColor': TTkColor.RST},
'disabled': {'borderColor': TTkColor.fg('#888888')},
'focus': {'borderColor': TTkColor.RST},
} }
_tabStyleFocussed = { _tabStyleFocussed = {
'default': {'borderColor': TTkColor.fg("#ffff00") + TTkColor.BOLD}, 'default': {'borderColor': TTkColor.fg("#ffff00") + TTkColor.BOLD},
'disabled': {'borderColor': TTkColor.fg('#888888')},
'focus': {'borderColor': TTkColor.fg("#ffff00") + TTkColor.BOLD},
} }
@ -188,7 +192,6 @@ class TTkTabButton(_TTkTabColorButton):
super().__init__(**kwargs) super().__init__(**kwargs)
self._closeButtonPressed = False self._closeButtonPressed = False
self._resetSize() self._resetSize()
self.setFocusPolicy(TTkK.ClickFocus)
def _resetSize(self): def _resetSize(self):
style = self.currentStyle() style = self.currentStyle()
@ -241,18 +244,18 @@ class TTkTabButton(_TTkTabColorButton):
self._closeButtonPressed = True self._closeButtonPressed = True
return True return True
return super().mouseReleaseEvent(evt) return super().mouseReleaseEvent(evt)
def mouseReleaseEvent(self, evt:TTkMouseEvent) -> bool: def mouseReleaseEvent(self, evt:TTkMouseEvent) -> bool:
x,y = evt.x,evt.y x,y = evt.x,evt.y
w,h = self.size() w,h = self.size()
offY = self._barType.offY() offY = self._barType.offY()
if parent:=self.parentWidget():
parent.setFocus()
if self._closable and y == offY and w-4<=x<w-1 and self._closeButtonPressed: if self._closable and y == offY and w-4<=x<w-1 and self._closeButtonPressed:
self._closeButtonPressed = False self._closeButtonPressed = False
self.closeClicked.emit() self.closeClicked.emit()
return True return False
self._closeButtonPressed = False self._closeButtonPressed = False
return False return False
def mouseDragEvent(self, evt:TTkMouseEvent) -> bool: def mouseDragEvent(self, evt:TTkMouseEvent) -> bool:
drag = TTkDrag() drag = TTkDrag()
self._closeButtonPressed = False self._closeButtonPressed = False
@ -363,7 +366,6 @@ class _TTkTabScrollerButton(_TTkTabColorButton):
self.resize(2, self._barType.vSize()) self.resize(2, self._barType.vSize())
self.setMinimumSize(2, self._barType.vSize()) self.setMinimumSize(2, self._barType.vSize())
self.setMaximumSize(2, self._barType.vSize()) self.setMaximumSize(2, self._barType.vSize())
self.setFocusPolicy(TTkK.ParentFocus)
def side(self): def side(self):
return self._side return self._side
@ -436,23 +438,41 @@ _labels= │◀│La│Label1║Label2║Label3│Label4│▶│
''' '''
class TTkTabBar(TTkContainer): class TTkTabBar(TTkContainer):
'''TTkTabBar''' '''TTkTabBar'''
classStyle = _tabStyle classStyle = _tabStyle
__slots__ = ( __slots__ = (
'_tabButtons', '_tabMovable', '_barType', '_tabButtons', '_tabMovable', '_barType',
'_highlighted', '_currentIndex','_lastIndex', '_highlighted', '_currentIndex', '_lastIndex',
'_leftScroller', '_rightScroller', '_leftScroller', '_rightScroller',
'_tabClosable', '_tabClosable',
'_sideEnd', '_sideEnd',
#Signals #Signals
'currentChanged', 'tabBarClicked', 'tabCloseRequested') 'currentChanged', 'tabBarClicked', 'tabCloseRequested')
currentChanged: pyTTkSignal
tabBarClicked: pyTTkSignal
tabCloseRequested: pyTTkSignal
_tabButtons:List[TTkTabButton]
_currentIndex:int
_lastIndex:int
_highlighted:int
_tabMovable:bool
_tabClosable:bool
_sideEnd:int
_barType:TTkBarType
_leftScroller:_TTkTabScrollerButton
_rightScroller:_TTkTabScrollerButton
def __init__(self, *, def __init__(self, *,
closable:bool=False, closable:bool=False,
small:bool=True, small:bool=True,
barType:TTkBarType=TTkBarType.NONE, barType:TTkBarType=TTkBarType.NONE,
**kwargs) -> None: **kwargs) -> None:
self.currentChanged = pyTTkSignal(int)
self.tabBarClicked = pyTTkSignal(int)
self.tabCloseRequested = pyTTkSignal(int)
self._tabButtons:list[TTkTabButton] = [] self._tabButtons:list[TTkTabButton] = []
self._currentIndex = -1 self._currentIndex = -1
self._lastIndex = -1 self._lastIndex = -1
@ -474,12 +494,7 @@ class TTkTabBar(TTkContainer):
self.layout().addWidget(self._leftScroller) self.layout().addWidget(self._leftScroller)
self.layout().addWidget(self._rightScroller) self.layout().addWidget(self._rightScroller)
# Signals self.setFocusPolicy(TTkK.ParentFocus)
self.currentChanged = pyTTkSignal(int)
self.tabBarClicked = pyTTkSignal(int)
self.tabCloseRequested = pyTTkSignal(int)
self.setFocusPolicy(TTkK.ClickFocus | TTkK.TabFocus)
def mergeStyle(self, style): def mergeStyle(self, style):
super().mergeStyle(style) super().mergeStyle(style)
@ -709,6 +724,8 @@ class TTkTabWidget(TTkFrame):
'tabData', 'setTabData', 'currentData', 'tabData', 'setTabData', 'currentData',
'currentIndex', 'setCurrentIndex', 'tabCloseRequested') 'currentIndex', 'setCurrentIndex', 'tabCloseRequested')
_tabWidgets:List[TTkWidget]
def __init__(self, *, def __init__(self, *,
closable:bool=False, closable:bool=False,
barType:TTkBarType=TTkBarType.NONE, barType:TTkBarType=TTkBarType.NONE,
@ -729,8 +746,7 @@ class TTkTabWidget(TTkFrame):
self._topRightLayout = None self._topRightLayout = None
self._tabBar.currentChanged.connect(self._tabChanged) self._tabBar.currentChanged.connect(self._tabChanged)
self.setFocusPolicy(self._tabBar.focusPolicy()) self.setFocusPolicy(TTkK.ClickFocus | TTkK.TabFocus)
self._tabBar.setFocusPolicy(TTkK.ParentFocus)
self._spacer = TTkSpacer(parent=self) self._spacer = TTkSpacer(parent=self)
@ -761,6 +777,8 @@ class TTkTabWidget(TTkFrame):
self.tabBarClicked = self._tabBar.tabBarClicked self.tabBarClicked = self._tabBar.tabBarClicked
self.tabCloseRequested = self._tabBar.tabCloseRequested self.tabCloseRequested = self._tabBar.tabCloseRequested
self.tabBarClicked.connect(self.setFocus)
self.focusChanged.connect(self._focusChanged) self.focusChanged.connect(self._focusChanged)
def _focusChanged(self, focus): def _focusChanged(self, focus):
@ -777,11 +795,11 @@ class TTkTabWidget(TTkFrame):
return self._tabWidgets.index(widget) return self._tabWidgets.index(widget)
return -1 return -1
def tabButton(self, index) -> TTkTabButton: def tabButton(self, index:int) -> TTkTabButton:
'''tabButton''' '''tabButton'''
return self._tabBar.tabButton(index) return self._tabBar.tabButton(index)
def widget(self, index): def widget(self, index:int) -> Optional[TTkWidget]:
'''widget''' '''widget'''
if 0 <= index < len(self._tabWidgets): if 0 <= index < len(self._tabWidgets):
return self._tabWidgets[index] return self._tabWidgets[index]
@ -795,7 +813,7 @@ class TTkTabWidget(TTkFrame):
return self._spacer return self._spacer
@pyTTkSlot(TTkWidget) @pyTTkSlot(TTkWidget)
def setCurrentWidget(self, widget): def setCurrentWidget(self, widget:TTkWidget) -> None:
'''setCurrentWidget''' '''setCurrentWidget'''
for i, w in enumerate(self._tabWidgets): for i, w in enumerate(self._tabWidgets):
if widget == w: if widget == w:
@ -803,7 +821,7 @@ class TTkTabWidget(TTkFrame):
break break
@pyTTkSlot(int) @pyTTkSlot(int)
def _tabChanged(self, index): def _tabChanged(self, index:int) -> None:
self._spacer.show() self._spacer.show()
for i, widget in enumerate(self._tabWidgets): for i, widget in enumerate(self._tabWidgets):
if index == i: if index == i:
@ -813,7 +831,16 @@ class TTkTabWidget(TTkFrame):
widget.hide() widget.hide()
def keyEvent(self, evt:TTkKeyEvent) -> bool: def keyEvent(self, evt:TTkKeyEvent) -> bool:
return self._tabBar.keyEvent(evt) if self.hasFocus() and self._tabBar.keyEvent(evt=evt):
return True
return super().keyEvent(evt)
def mousePressEvent(self, evt:TTkMouseEvent) -> bool:
return True
def mouseReleaseEvent(self, evt:TTkMouseEvent) -> bool:
return True
def mouseTapEvent(self, evt:TTkMouseEvent) -> bool:
return True
def _dropNewTab(self, x:int, y:int, data:_TTkNewTabWidgetDragData) -> None: def _dropNewTab(self, x:int, y:int, data:_TTkNewTabWidgetDragData) -> None:
w = data.widget() w = data.widget()

45
libs/pyTermTk/TermTk/TTkWidgets/widget.py

@ -43,7 +43,7 @@ from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
if TYPE_CHECKING: if TYPE_CHECKING:
from TermTk import TTkContainer from TermTk import TTkContainer
class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): class TTkWidget(TMouseEvents, TKeyEvents, TDragEvents):
''' Widget sizes: ''' Widget sizes:
:: ::
@ -110,7 +110,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
'_name', '_parent', '_name', '_parent',
'_x', '_y', '_width', '_height', '_x', '_y', '_width', '_height',
'_maxw', '_maxh', '_minw', '_minh', '_maxw', '_maxh', '_minw', '_minh',
'_focus','_focus_policy', '_focus_policy',
'_canvas', '_widgetItem', '_canvas', '_widgetItem',
'_visible', '_visible',
'_pendingMouseRelease', '_pendingMouseRelease',
@ -132,7 +132,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
_maxh:int _maxh:int
_minw:int _minw:int
_minh:int _minh:int
_focus:bool
_focus_policy:TTkK.FocusPolicy _focus_policy:TTkK.FocusPolicy
_canvas:TTkCanvas _canvas:TTkCanvas
_widgetItem:TTkWidgetItem _widgetItem:TTkWidgetItem
@ -229,7 +228,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
self._widgetCursorType = TTkK.Cursor_Blinking_Bar self._widgetCursorType = TTkK.Cursor_Blinking_Bar
self._name = name if name else self.__class__.__name__ self._name = name if name else self.__class__.__name__
self._parent:TTkWidget = parent self._parent = parent
self._pendingMouseRelease = False self._pendingMouseRelease = False
@ -239,7 +238,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
self._maxw, self._maxh = maxSize if maxSize else (maxWidth,maxHeight) self._maxw, self._maxh = maxSize if maxSize else (maxWidth,maxHeight)
self._minw, self._minh = minSize if minSize else (minWidth,minHeight) self._minw, self._minh = minSize if minSize else (minWidth,minHeight)
self._focus = False
self._focus_policy = TTkK.NoFocus self._focus_policy = TTkK.NoFocus
self._visible = visible self._visible = visible
@ -710,30 +708,29 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
@pyTTkSlot() @pyTTkSlot()
def setFocus(self) -> None: def setFocus(self) -> None:
'''Focus the widget''' '''Focus the widget'''
# TTkLog.debug(f"setFocus: {self._name} - {self._focus}") if not (_p:=self._parent):
if self._focus and self == TTkHelper.getFocus(): return return
tmp = TTkHelper.getFocus() if (_old_fw:=_p._getFocusWidget()) is self:
if tmp == self: return return
if tmp is not None: if _old_fw:
tmp.clearFocus() _old_fw.clearFocus()
TTkHelper.setFocus(self) _p._setFocusWidget(self)
self._focus = True self.focusChanged.emit(True)
self.focusChanged.emit(self._focus)
self.focusInEvent() self.focusInEvent()
TTkHelper.removeOverlayChild(self)
self._pushWidgetCursor()
self._processStyleEvent(TTkWidget._S_DEFAULT) self._processStyleEvent(TTkWidget._S_DEFAULT)
self._pushWidgetCursor()
TTkHelper.removeOverlayChild(self)
self.update()
def clearFocus(self) -> None: def clearFocus(self) -> None:
'''Remove the Focus state of this widget''' '''Remove the Focus state of this widget'''
# TTkLog.debug(f"clearFocus: {self._name} - {self._focus}") if not (_p:=self._parent) or _p._getFocusWidget() is not self:
if not self._focus and self != TTkHelper.getFocus(): return return
TTkHelper.clearFocus() _p._setFocusWidget(None)
self._focus = False self.focusChanged.emit(False)
self.focusChanged.emit(self._focus)
self.focusOutEvent() self.focusOutEvent()
self._processStyleEvent(TTkWidget._S_DEFAULT) self._processStyleEvent(TTkWidget._S_DEFAULT)
self.update(repaint=True, updateLayout=False) self.update()
def hasFocus(self) -> bool: def hasFocus(self) -> bool:
''' '''
@ -741,7 +738,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
:return: bool :return: bool
''' '''
return self._focus return bool((_p:=self._parent) and _p._getFocusWidget() is self)
def getCanvas(self) -> TTkCanvas: def getCanvas(self) -> TTkCanvas:
return self._canvas return self._canvas
@ -926,7 +923,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
def _pushWidgetCursor(self): def _pushWidgetCursor(self):
if ( self._widgetCursorEnabled and if ( self._widgetCursorEnabled and
self._visible and self._visible and
( self._focus or self == TTkHelper.cursorWidget() ) ): ( self.hasFocus() or self == TTkHelper.cursorWidget() ) ):
cx,cy = self._widgetCursor cx,cy = self._widgetCursor
ax, ay = TTkHelper.absPos(self) ax, ay = TTkHelper.absPos(self)
if ( self == TTkHelper.widgetAt(cx+ax, cy+ay) or if ( self == TTkHelper.widgetAt(cx+ax, cy+ay) or

430
tests/pytest/widgets/test_add_remove_widget.py

@ -0,0 +1,430 @@
# MIT License
#
# Copyright (c) 2025 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],'../../../libs/pyTermTk'))
import TermTk as ttk
def test_add_widget_to_container():
'''
Test that adding widgets to a container using addWidget() correctly sets the parent relationship.
Verifies that widgets initially have no parent, and after being added to a container,
their parentWidget() returns the container.
'''
container = ttk.TTkContainer()
widget1 = ttk.TTkWidget()
widget2 = ttk.TTkWidget()
assert widget1.parentWidget() is None
assert widget2.parentWidget() is None
container.addWidget(widget1)
assert widget1.parentWidget() is container
container.addWidget(widget2)
assert widget2.parentWidget() is container
def test_add_widget_to_container_02():
'''
Test that widgets added via parent= constructor parameter correctly establish
the parent-child relationship immediately upon construction.
'''
container = ttk.TTkContainer()
widget1 = ttk.TTkWidget(parent=container)
widget2 = ttk.TTkWidget(parent=container)
assert widget1.parentWidget() is container
assert widget2.parentWidget() is container
def test_remove_widget_from_container():
'''
Test that removing a widget from a container using removeWidget() correctly
unsets the parent relationship, returning the widget to an orphaned state.
'''
container = ttk.TTkContainer()
widget = ttk.TTkWidget()
container.addWidget(widget)
assert widget.parentWidget() is container
container.removeWidget(widget)
assert widget.parentWidget() is None
def test_remove_widget_from_container_02():
'''
Test that removing a widget that was added via parent= constructor parameter
correctly unsets the parent relationship.
'''
container = ttk.TTkContainer()
widget = ttk.TTkWidget(parent=container)
assert widget.parentWidget() is container
container.removeWidget(widget)
assert widget.parentWidget() is None
def test_gridlayout_add_remove():
'''
Test :py:class:`TTkGridLayout` add/remove operations using container.layout().addWidget().
Verifies that widgets added to specific grid positions have their parent correctly set
to the container, and that removing widgets unsets the parent relationship while
preserving other widgets' parents.
'''
container = ttk.TTkContainer()
layout = ttk.TTkGridLayout()
container.setLayout(layout)
widget1 = ttk.TTkWidget()
widget2 = ttk.TTkWidget()
widget3 = ttk.TTkWidget()
# Add widgets to grid using container.layout()
container.layout().addWidget(widget1, 0, 0)
container.layout().addWidget(widget2, 0, 1)
container.layout().addWidget(widget3, 1, 0)
assert widget1.parentWidget() is container
assert widget2.parentWidget() is container
assert widget3.parentWidget() is container
# Remove widget using container.layout()
container.layout().removeWidget(widget2)
assert widget2.parentWidget() is None
assert widget1.parentWidget() is container
assert widget3.parentWidget() is container
def test_hboxlayout_add_remove():
'''
Test :py:class:`TTkHBoxLayout` add/remove operations using container.layout().addWidget().
Verifies that widgets are correctly parented when added sequentially, and that
removing specific widgets from the middle of the layout updates their parent
relationships while preserving others.
'''
container = ttk.TTkContainer()
layout = ttk.TTkHBoxLayout()
container.setLayout(layout)
widgets = [ttk.TTkWidget() for _ in range(4)]
# Add all widgets using container.layout()
for widget in widgets:
container.layout().addWidget(widget)
assert widget.parentWidget() is container
# Remove middle widgets using container.layout()
container.layout().removeWidget(widgets[1])
container.layout().removeWidget(widgets[2])
assert widgets[0].parentWidget() is container
assert widgets[1].parentWidget() is None
assert widgets[2].parentWidget() is None
assert widgets[3].parentWidget() is container
def test_vboxlayout_add_remove():
'''
Test :py:class:`TTkVBoxLayout` add/remove operations using container.layout().addWidget().
Verifies parent relationships when adding button widgets vertically, and that
removing the first widget correctly unsets its parent while preserving others.
'''
container = ttk.TTkContainer()
layout = ttk.TTkVBoxLayout()
container.setLayout(layout)
widget1 = ttk.TTkButton(text='Button 1')
widget2 = ttk.TTkButton(text='Button 2')
widget3 = ttk.TTkButton(text='Button 3')
container.layout().addWidget(widget1)
container.layout().addWidget(widget2)
container.layout().addWidget(widget3)
assert widget1.parentWidget() is container
assert widget2.parentWidget() is container
assert widget3.parentWidget() is container
# Remove first widget using container.layout()
container.layout().removeWidget(widget1)
assert widget1.parentWidget() is None
assert widget2.parentWidget() is container
def test_nested_layouts():
'''
Test parent relationships with nested layouts using container.layout().addWidget().
Verifies that when a container with its own layout is added to another container,
the parent relationships form a proper hierarchy. Widgets remain parented to their
immediate container even when that container is removed from the root.
'''
root = ttk.TTkContainer()
root_layout = ttk.TTkVBoxLayout()
root.setLayout(root_layout)
# Create nested container
nested = ttk.TTkContainer()
nested_layout = ttk.TTkHBoxLayout()
nested.setLayout(nested_layout)
widget1 = ttk.TTkWidget()
widget2 = ttk.TTkWidget()
# Add nested container to root using root.layout()
root.layout().addWidget(nested)
assert nested.parentWidget() is root
# Add widgets to nested layout using nested.layout()
nested.layout().addWidget(widget1)
nested.layout().addWidget(widget2)
assert widget1.parentWidget() is nested
assert widget2.parentWidget() is nested
# Remove nested container using root.layout()
root.layout().removeWidget(nested)
assert nested.parentWidget() is None
assert widget1.parentWidget() is nested # Still parented to nested
def test_replace_widget_in_layout():
'''
Test that replacing a widget in the same layout position correctly maintains
parent relationships using container.layout() operations. The removed widget
should have no parent, while the replacement widget should be parented to the container.
'''
container = ttk.TTkContainer()
layout = ttk.TTkGridLayout()
container.setLayout(layout)
widget1 = ttk.TTkWidget()
widget2 = ttk.TTkWidget()
container.layout().addWidget(widget1, 0, 0)
assert widget1.parentWidget() is container
# Replace widget1 with widget2 using container.layout()
container.layout().removeWidget(widget1)
container.layout().addWidget(widget2, 0, 0)
assert widget1.parentWidget() is None
assert widget2.parentWidget() is container
def test_move_widget_between_containers():
'''
Test that moving a widget between containers correctly updates the parent relationship.
The widget should first be parented to the first container, then have no parent briefly,
then be parented to the second container.
'''
container1 = ttk.TTkContainer()
container2 = ttk.TTkContainer()
widget = ttk.TTkWidget()
container1.addWidget(widget)
assert widget.parentWidget() is container1
# Move to second container
container1.removeWidget(widget)
container2.addWidget(widget)
assert widget.parentWidget() is container2
def test_complex_layout_operations():
'''
Test complex add/remove operations across multiple layout types using container.layout().addWidget().
Creates a hierarchy with :py:class:`TTkVBoxLayout`, :py:class:`TTkHBoxLayout`, and :py:class:`TTkGridLayout`.
Verifies that all parent relationships are correctly maintained throughout the hierarchy,
and that removing a section of the layout tree properly updates parent relationships.
'''
root = ttk.TTkContainer()
vbox = ttk.TTkVBoxLayout()
root.setLayout(vbox)
# Create horizontal section
hbox_container = ttk.TTkContainer()
hbox = ttk.TTkHBoxLayout()
hbox_container.setLayout(hbox)
# Create grid section
grid_container = ttk.TTkContainer()
grid = ttk.TTkGridLayout()
grid_container.setLayout(grid)
widgets = [ttk.TTkButton(text=f'Btn{i}') for i in range(6)]
# Add to vbox using root.layout()
root.layout().addWidget(hbox_container)
root.layout().addWidget(grid_container)
# Add to hbox using hbox_container.layout()
hbox_container.layout().addWidget(widgets[0])
hbox_container.layout().addWidget(widgets[1])
# Add to grid using grid_container.layout()
grid_container.layout().addWidget(widgets[2], 0, 0)
grid_container.layout().addWidget(widgets[3], 0, 1)
grid_container.layout().addWidget(widgets[4], 1, 0)
grid_container.layout().addWidget(widgets[5], 1, 1)
# Verify all parents
assert hbox_container.parentWidget() is root
assert grid_container.parentWidget() is root
assert widgets[0].parentWidget() is hbox_container
assert widgets[1].parentWidget() is hbox_container
assert widgets[2].parentWidget() is grid_container
assert widgets[3].parentWidget() is grid_container
# Remove grid section using root.layout()
root.layout().removeWidget(grid_container)
assert grid_container.parentWidget() is None
assert widgets[2].parentWidget() is grid_container # Still parented to grid_container
# Remove widgets from hbox using hbox_container.layout()
hbox_container.layout().removeWidget(widgets[0])
assert widgets[0].parentWidget() is None
assert widgets[1].parentWidget() is hbox_container
def test_nested_layout_widgets_01():
'''
Test that widgets added to a nested layout (one layout containing another) correctly
inherit the parent from the container that owns the root layout. Widgets added to
the nested layout should be parented to the container, not to the layout itself.
'''
layout1 = ttk.TTkLayout()
layout2 = ttk.TTkLayout()
layout1.addItem(layout2)
container = ttk.TTkContainer(layout=layout1)
widget1 = ttk.TTkWidget()
widget2 = ttk.TTkWidget()
assert widget1.parentWidget() is None
assert widget2.parentWidget() is None
layout2.addWidget(widget1)
assert widget1.parentWidget() is container
layout2.addWidget(widget2)
assert widget2.parentWidget() is container
def test_nested_layout_widgets_02():
'''
Test that widgets added to a nested layout before the layout is attached to a container
get their parent set correctly when setLayout() is called. Also verifies that removing
the nested layout from the parent layout unsets the widgets' parents.
'''
layout1 = ttk.TTkLayout()
layout2 = ttk.TTkLayout()
layout1.addItem(layout2)
container = ttk.TTkContainer()
widget1 = ttk.TTkWidget()
widget2 = ttk.TTkWidget()
layout2.addWidget(widget1)
layout2.addWidget(widget2)
assert widget1.parentWidget() is None
assert widget2.parentWidget() is None
container.setLayout(layout1)
assert widget1.parentWidget() is container
assert widget2.parentWidget() is container
layout1.removeItem(layout2)
assert widget1.parentWidget() is None
assert widget2.parentWidget() is None
def test_nested_layout_widgets_03():
'''
Test that replacing a container's layout with a different layout correctly unsets
the parent relationships for widgets in the old layout's hierarchy. Widgets in
nested layouts should have their parents cleared when the root layout is replaced.
'''
layout1 = ttk.TTkLayout()
layout2 = ttk.TTkLayout()
layout3 = ttk.TTkLayout()
layout1.addItem(layout2)
container = ttk.TTkContainer()
widget1 = ttk.TTkWidget()
widget2 = ttk.TTkWidget()
layout2.addWidget(widget1)
layout2.addWidget(widget2)
assert widget1.parentWidget() is None
assert widget2.parentWidget() is None
container.setLayout(layout1)
assert widget1.parentWidget() is container
assert widget2.parentWidget() is container
container.setLayout(layout3)
assert widget1.parentWidget() is None
assert widget2.parentWidget() is None
def test_nested_layout_widgets_04():
'''
Test complex parent relationships where a container with its own child widgets
is added to a nested layout within another container. Verifies that the container
maintains its parent relationship to its widgets, while also being correctly parented
to the outer container. Removing the nested layout should only affect the intermediate
container's parent, not the leaf widgets' relationship to their immediate parent.
'''
layout1 = ttk.TTkLayout()
layout2 = ttk.TTkLayout()
layout1.addItem(layout2)
container1 = ttk.TTkContainer(layout=layout1)
container2 = ttk.TTkContainer()
widget1 = ttk.TTkWidget(parent=container2)
widget2 = ttk.TTkWidget(parent=container2)
assert container2.parentWidget() is None
assert widget1.parentWidget() is container2
assert widget2.parentWidget() is container2
layout2.addWidget(container2)
assert container2.parentWidget() is container1
assert widget1.parentWidget() is container2
assert widget2.parentWidget() is container2
layout1.removeItem(layout2)
assert container2.parentWidget() is None
assert widget1.parentWidget() is container2
assert widget2.parentWidget() is container2

202
tests/pytest/widgets/test_focus_01.py

@ -0,0 +1,202 @@
# MIT License
#
# Copyright (c) 2025 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],'../../../libs/pyTermTk'))
import TermTk as ttk
def test_focus_01_no_root():
'''
Container Widget1
Widget3
Widget3
'''
container = ttk.TTkContainer()
widget1 = ttk.TTkWidget(parent=container)
widget2 = ttk.TTkWidget(parent=container)
widget3 = ttk.TTkWidget(parent=container)
assert False is container.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
widget2.setFocus()
assert False is container.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
widget2.clearFocus()
assert False is container.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
def test_focus_01_with_root():
'''
Container Widget1
Widget3
Widget3
'''
root = ttk.TTk()
widget1 = ttk.TTkWidget(parent=root)
widget2 = ttk.TTkWidget(parent=root)
widget3 = ttk.TTkWidget(parent=root)
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
widget2.setFocus()
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
widget2.clearFocus()
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
def test_focus_02():
'''
Root Container Widget1
Widget2
Widget3
'''
root = ttk.TTk()
container = ttk.TTkContainer(parent=root)
widget1 = ttk.TTkWidget(parent=container)
widget2 = ttk.TTkWidget(parent=container)
widget3 = ttk.TTkWidget(parent=container)
assert False is container.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
widget2.setFocus()
assert False is container.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
widget2.clearFocus()
assert False is container.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
def test_focus_03_sequential():
'''Test sequential focus changes between widgets'''
root = ttk.TTk()
widget1 = ttk.TTkWidget(parent=root)
widget2 = ttk.TTkWidget(parent=root)
widget3 = ttk.TTkWidget(parent=root)
widget1.setFocus()
assert False is root.hasFocus()
assert True is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
widget2.setFocus()
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
widget3.setFocus()
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert True is widget3.hasFocus()
def test_focus_04_nested_containers():
'''
Root Container1 Widget1
Container2 Widget2
Widget3
'''
root = ttk.TTk()
container1 = ttk.TTkContainer(parent=root)
container2 = ttk.TTkContainer(parent=container1)
widget1 = ttk.TTkWidget(parent=container1)
widget2 = ttk.TTkWidget(parent=container2)
widget3 = ttk.TTkWidget(parent=container2)
widget2.setFocus()
assert False is container1.hasFocus()
assert False is container2.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
widget1.setFocus()
assert False is container1.hasFocus()
assert False is container2.hasFocus()
assert True is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
def test_focus_05_multiple_setFocus():
'''Test calling setFocus multiple times on same widget'''
root = ttk.TTk()
widget1 = ttk.TTkWidget(parent=root)
widget2 = ttk.TTkWidget(parent=root)
widget1.setFocus()
widget1.setFocus()
assert (widget1.hasFocus(), widget2.hasFocus()) == (True, False)
def test_focus_06_clearFocus_without_focus():
'''Test clearFocus on widget that doesn't have focus'''
root = ttk.TTk()
widget1 = ttk.TTkWidget(parent=root)
widget2 = ttk.TTkWidget(parent=root)
widget1.setFocus()
widget2.clearFocus()
assert (widget1.hasFocus(), widget2.hasFocus()) == (True, False)
def test_focus_07_single_widget():
'''Test focus behavior with single widget'''
root = ttk.TTk()
widget = ttk.TTkWidget(parent=root)
assert (root.hasFocus(), widget.hasFocus()) == (False, False)
widget.setFocus()
assert (root.hasFocus(), widget.hasFocus()) == (False, True)
widget.clearFocus()
assert (root.hasFocus(), widget.hasFocus()) == (False, False)

113
tests/pytest/widgets/test_focus_02_first_focus.py

@ -0,0 +1,113 @@
# MIT License
#
# Copyright (c) 2025 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],'../../../libs/pyTermTk'))
import TermTk as ttk
def test_next_prev_widget_01():
'''
Container Widget1
Widget2
Widget3
'''
container = ttk.TTkContainer()
widget1 = ttk.TTkWidget(parent=container)
widget2 = ttk.TTkWidget(parent=container)
widget3 = ttk.TTkWidget(parent=container)
widget1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
# Forward
ff = container._getFirstFocus(widget=None, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget1
ff = container._getFirstFocus(widget=widget1, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget2
ff = container._getFirstFocus(widget=widget2, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget3
ff = container._getFirstFocus(widget=widget3, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is None
# Reverse
ff = container._getLastFocus(widget=None, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget3
ff = container._getLastFocus(widget=widget3, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget2
ff = container._getLastFocus(widget=widget2, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget1
ff = container._getLastFocus(widget=widget1, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is None
def test_next_prev_widget_02_nested():
'''
Root Container1 Widget1
Container2 Widget2
Widget3
Widget4
'''
root = ttk.TTk()
container1 = ttk.TTkContainer(parent=root)
widget1 = ttk.TTkWidget(parent=container1)
container2 = ttk.TTkContainer(parent=container1)
widget2 = ttk.TTkWidget(parent=container2)
widget3 = ttk.TTkWidget(parent=container2)
widget4 = ttk.TTkWidget(parent=container2)
widget1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget4.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
# Forward
ff = container1._getFirstFocus(widget=None, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget1
ff = container2._getFirstFocus(widget=None, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget2
ff = container1._getFirstFocus(widget=widget1, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget2
ff = container2._getFirstFocus(widget=widget2, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget3
ff = container2._getFirstFocus(widget=widget3, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget4
ff = container2._getFirstFocus(widget=widget4, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is None
# Reverse
ff = container1._getLastFocus(widget=None, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget4
ff = container2._getLastFocus(widget=None, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget4
ff = container1._getLastFocus(widget=widget1, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is None
ff = container2._getLastFocus(widget=widget4, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget3
ff = container2._getLastFocus(widget=widget3, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is widget2
ff = container2._getLastFocus(widget=widget2, focusPolicy=ttk.TTkK.FocusPolicy.TabFocus)
assert ff is None

556
tests/pytest/widgets/test_focus_02_tab.py

@ -0,0 +1,556 @@
# MIT License
#
# Copyright (c) 2025 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],'../../../libs/pyTermTk'))
import TermTk as ttk
def test_focus_01_tab():
'''
Container Widget1
Widget2
Widget3
'''
root = ttk.TTk()
widget1 = ttk.TTkWidget(parent=root)
widget2 = ttk.TTkWidget(parent=root)
widget3 = ttk.TTkWidget(parent=root)
widget1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocus()
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
tab_key = ttk.TTkKeyEvent(
type=ttk.TTkK.KeyType.SpecialKey,
key=ttk.TTkK.Key_Tab,
mod=ttk.TTkK.NoModifier,
code='',)
root.keyEvent(evt=tab_key)
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert True is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is root.hasFocus()
assert True is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
def test_focus_01_tab_reverse():
'''
Container Widget1
Widget2
Widget3
'''
root = ttk.TTk()
widget1 = ttk.TTkWidget(parent=root)
widget2 = ttk.TTkWidget(parent=root)
widget3 = ttk.TTkWidget(parent=root)
widget1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocus()
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
tab_key = ttk.TTkKeyEvent(
type=ttk.TTkK.KeyType.SpecialKey,
key=ttk.TTkK.Key_Tab,
mod=ttk.TTkK.ShiftModifier,
code='',)
root.keyEvent(evt=tab_key)
assert False is root.hasFocus()
assert True is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert True is widget3.hasFocus()
def test_focus_04_nested_containers():
'''
Root Container1 Widget1
Container2 Widget2
Widget3
'''
root = ttk.TTk()
container1 = ttk.TTkContainer(parent=root)
widget1 = ttk.TTkWidget(parent=container1)
container2 = ttk.TTkContainer(parent=container1)
widget2 = ttk.TTkWidget(parent=container2)
widget3 = ttk.TTkWidget(parent=container2)
widget1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocus()
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
tab_key = ttk.TTkKeyEvent(
type=ttk.TTkK.KeyType.SpecialKey,
key=ttk.TTkK.Key_Tab,
mod=ttk.TTkK.NoModifier,
code='',)
root.keyEvent(evt=tab_key)
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert True is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is root.hasFocus()
assert True is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
def test_focus_04_nested_containers_reversed():
'''
Root Container1 Widget1
Container2 Widget2
Widget3
Widget4
'''
root = ttk.TTk()
container1 = ttk.TTkContainer(parent=root)
widget1 = ttk.TTkWidget(parent=container1)
container2 = ttk.TTkContainer(parent=container1)
widget2 = ttk.TTkWidget(parent=container2)
widget3 = ttk.TTkWidget(parent=container2)
widget4 = ttk.TTkWidget(parent=container2)
widget1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget4.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocus()
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert True is widget3.hasFocus()
assert False is widget4.hasFocus()
tab_key = ttk.TTkKeyEvent(
type=ttk.TTkK.KeyType.SpecialKey,
key=ttk.TTkK.Key_Tab,
mod=ttk.TTkK.ShiftModifier,
code='',)
root.keyEvent(evt=tab_key)
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
assert False is widget4.hasFocus()
root.keyEvent(evt=tab_key)
assert False is root.hasFocus()
assert True is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
assert False is widget4.hasFocus()
root.keyEvent(evt=tab_key)
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
assert True is widget4.hasFocus()
root.keyEvent(evt=tab_key)
assert False is root.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert True is widget3.hasFocus()
assert False is widget4.hasFocus()
def test_focus_container_with_tab_focus():
'''
Container (TabFocus) Widget1
Widget2
Widget3
'''
root = ttk.TTk()
container = ttk.TTkContainer(parent=root)
container.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget1 = ttk.TTkWidget(parent=container)
widget2 = ttk.TTkWidget(parent=container)
widget3 = ttk.TTkWidget(parent=container)
widget1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
container.setFocus()
assert True is container.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
tab_key = ttk.TTkKeyEvent(
type=ttk.TTkK.KeyType.SpecialKey,
key=ttk.TTkK.Key_Tab,
mod=ttk.TTkK.NoModifier,
code='',)
root.keyEvent(evt=tab_key)
assert False is container.hasFocus()
assert True is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is container.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is container.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert True is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert True is container.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
def test_focus_nested_containers_with_tab_focus():
'''
Root Container1 (TabFocus) Widget1
Container2 (TabFocus) Widget2
Widget3
'''
root = ttk.TTk()
container1 = ttk.TTkContainer(parent=root)
container1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget1 = ttk.TTkWidget(parent=container1)
container2 = ttk.TTkContainer(parent=container1)
container2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2 = ttk.TTkWidget(parent=container2)
widget3 = ttk.TTkWidget(parent=container2)
widget1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
container1.setFocus()
assert True is container1.hasFocus()
assert False is widget1.hasFocus()
assert False is container2.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
tab_key = ttk.TTkKeyEvent(
type=ttk.TTkK.KeyType.SpecialKey,
key=ttk.TTkK.Key_Tab,
mod=ttk.TTkK.NoModifier,
code='',)
root.keyEvent(evt=tab_key)
assert False is container1.hasFocus()
assert True is widget1.hasFocus()
assert False is container2.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is container1.hasFocus()
assert False is widget1.hasFocus()
assert True is container2.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is container1.hasFocus()
assert False is widget1.hasFocus()
assert False is container2.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is container1.hasFocus()
assert False is widget1.hasFocus()
assert False is container2.hasFocus()
assert False is widget2.hasFocus()
assert True is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert True is container1.hasFocus()
assert False is widget1.hasFocus()
assert False is container2.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
def test_focus_nested_containers_with_tab_focus_reversed():
'''
Root Container1 (TabFocus) Widget1
Container2 (TabFocus) Widget2
Widget3
'''
root = ttk.TTk()
container1 = ttk.TTkContainer(name='container1', parent=root)
widget1 = ttk.TTkWidget(name='widget1', parent=container1)
container2 = ttk.TTkContainer(name='container2', parent=container1)
widget2 = ttk.TTkWidget(name='widget2', parent=container2)
widget3 = ttk.TTkWidget(name='widget3', parent=container2)
container1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
container2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocus()
assert False is container1.hasFocus()
assert False is widget1.hasFocus()
assert False is container2.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
tab_key = ttk.TTkKeyEvent(
type=ttk.TTkK.KeyType.SpecialKey,
key=ttk.TTkK.Key_Tab,
mod=ttk.TTkK.ShiftModifier,
code='',)
root.keyEvent(evt=tab_key)
assert False is container1.hasFocus()
assert False is widget1.hasFocus()
assert True is container2.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is container1.hasFocus()
assert True is widget1.hasFocus()
assert False is container2.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert True is container1.hasFocus()
assert False is widget1.hasFocus()
assert False is container2.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is container1.hasFocus()
assert False is widget1.hasFocus()
assert False is container2.hasFocus()
assert False is widget2.hasFocus()
assert True is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is container1.hasFocus()
assert False is widget1.hasFocus()
assert False is container2.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
def test_focus_mixed_containers_tab_focus():
'''
Root Container1 (No TabFocus) Widget1
Container2 (TabFocus) Widget2
Widget3
Widget4
'''
root = ttk.TTk()
container1 = ttk.TTkContainer(parent=root)
widget1 = ttk.TTkWidget(parent=container1)
container2 = ttk.TTkContainer(parent=container1)
container2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2 = ttk.TTkWidget(parent=container2)
widget3 = ttk.TTkWidget(parent=container2)
widget4 = ttk.TTkWidget(parent=container1)
widget1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget4.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget1.setFocus()
assert True is widget1.hasFocus()
assert False is container2.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
assert False is widget4.hasFocus()
tab_key = ttk.TTkKeyEvent(
type=ttk.TTkK.KeyType.SpecialKey,
key=ttk.TTkK.Key_Tab,
mod=ttk.TTkK.NoModifier,
code='',)
root.keyEvent(evt=tab_key)
assert False is widget1.hasFocus()
assert True is container2.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
assert False is widget4.hasFocus()
root.keyEvent(evt=tab_key)
assert False is widget1.hasFocus()
assert False is container2.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
assert False is widget4.hasFocus()
root.keyEvent(evt=tab_key)
assert False is widget1.hasFocus()
assert False is container2.hasFocus()
assert False is widget2.hasFocus()
assert True is widget3.hasFocus()
assert False is widget4.hasFocus()
root.keyEvent(evt=tab_key)
assert False is widget1.hasFocus()
assert False is container2.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
assert True is widget4.hasFocus()
root.keyEvent(evt=tab_key)
assert True is widget1.hasFocus()
assert False is container2.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
assert False is widget4.hasFocus()
def test_focus_container_tab_focus_reversed():
'''
Container (TabFocus) Widget1
Widget2
Widget3
'''
root = ttk.TTk()
container = ttk.TTkContainer(parent=root)
container.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget1 = ttk.TTkWidget(parent=container)
widget2 = ttk.TTkWidget(parent=container)
widget3 = ttk.TTkWidget(parent=container)
widget1.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget3.setFocusPolicy(ttk.TTkK.FocusPolicy.TabFocus)
widget2.setFocus()
assert False is container.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()
tab_key = ttk.TTkKeyEvent(
type=ttk.TTkK.KeyType.SpecialKey,
key=ttk.TTkK.Key_Tab,
mod=ttk.TTkK.ShiftModifier,
code='',)
root.keyEvent(evt=tab_key)
assert False is container.hasFocus()
assert True is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert True is container.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert False is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is container.hasFocus()
assert False is widget1.hasFocus()
assert False is widget2.hasFocus()
assert True is widget3.hasFocus()
root.keyEvent(evt=tab_key)
assert False is container.hasFocus()
assert False is widget1.hasFocus()
assert True is widget2.hasFocus()
assert False is widget3.hasFocus()

2
tests/t.ui/test.ui.001.window.01.py

@ -30,5 +30,5 @@ import TermTk as ttk
ttk.TTkLog.use_default_file_logging() ttk.TTkLog.use_default_file_logging()
root = ttk.TTk() root = ttk.TTk()
ttk.TTkWindow(parent=root, pops=(0,0), size=(30,5), border=True) ttk.TTkWindow(parent=root, pos=(0,0), size=(30,5), border=True)
root.mainloop() root.mainloop()

62
tests/t.ui/test.ui.037.prototype.01.rootContainer.py

@ -0,0 +1,62 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 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],'../../libs/pyTermTk'))
import TermTk as ttk
class MovementContainer(ttk.TTkContainer):
def __init__(self, *, widget=ttk.TTkWidget, **kwargs):
super().__init__(**kwargs)
self.layout().addWidget(widget=widget)
widget.sizeChanged.connect(self._sizeChanged)
# widget.positionChanged.connect(self._positionChanged)
@ttk.pyTTkSlot(int,int)
def _sizeChanged(self,w,h):
self.resize(w,h)
@ttk.pyTTkSlot(int,int)
def _positionChanged(self,x,y):
ox,oy = self.layout().offset()
self.layout().setOffset(-x,-y)
self.move(x,y)
def paintEvent(self, canvas):
canvas.fill(color=ttk.TTkColor.BG_RED)
root = ttk.TTk()
win = ttk.TTkWindow(size=(40,15))
frame = ttk.TTkResizableFrame(size=(40,15), border=True)
mcWin = MovementContainer(widget=win, parent=root, pos=(10,5), size=(40,15))
mcFrame = MovementContainer(widget=frame, parent=root, pos=(5,2), size=(40,15))
root.mainloop()

69
tests/t.ui/test.ui.037.prototype.02.modal.py

@ -0,0 +1,69 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 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],'../../libs/pyTermTk'))
import TermTk as ttk
class TryModal(ttk.TTkWindow):
def __init__(self, **kwargs):
super().__init__(**kwargs)
overlay_btn = ttk.TTkButton(parent=self, pos=(0,0), size=(15,3), border=True, text='Overlay')
modal_btn = ttk.TTkButton(parent=self, pos=(0,3), size=(15,3), border=True, text='Modal')
toolbox_btn = ttk.TTkButton(parent=self, pos=(0,6), size=(15,3), border=True, text='Toolbox')
def _overlay():
win = TryModal(size=(30,13), title="Overlay")
ttk.TTkHelper.overlay(
caller=overlay_btn,
widget=win,
x=0,y=0)
overlay_btn.clicked.connect(_overlay)
def _modal():
win = TryModal(size=(30,13), title="Modal")
ttk.TTkHelper.overlay(
caller=modal_btn,
widget=win,
modal=True,
x=0,y=0)
modal_btn.clicked.connect(_modal)
def _toolbox():
win = TryModal(size=(30,13), title="Toolbox")
ttk.TTkHelper.overlay(
caller=toolbox_btn,
widget=win,
toolWindow=True,
x=0,y=0)
toolbox_btn.clicked.connect(_toolbox)
root = ttk.TTk()
TryModal(parent=root, pos=(5,2), size=(30,13), title="Root")
root.mainloop()

46
tests/t.ui/test.ui.038.tab_focus.py

@ -0,0 +1,46 @@
#!/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],'../../libs/pyTermTk'))
import TermTk as ttk
ttk.TTkLog.use_default_file_logging()
root = ttk.TTk()
win1 = ttk.TTkWindow(parent=root, title='1', pos=( 0,0), size=(40,10), border=True)
win2 = ttk.TTkWindow(parent=root, title='2', pos=(20,3), size=(40,15), border=True)
win3 = ttk.TTkWindow(parent=win2, title='3', pos=( 0,0), size=(30,10), border=True)
win4 = ttk.TTkWindow(parent=win2, title='4', pos=( 5,4), size=(30,5), border=True)
win5 = ttk.TTkWindow(parent=win3, title='5', pos=( 0,0), size=(20,5), border=True)
win1.setFocusPolicy(ttk.TTkK.TabFocus | ttk.TTkK.ClickFocus)
win2.setFocusPolicy(ttk.TTkK.TabFocus | ttk.TTkK.ClickFocus)
win3.setFocusPolicy(ttk.TTkK.TabFocus | ttk.TTkK.ClickFocus)
win4.setFocusPolicy(ttk.TTkK.TabFocus | ttk.TTkK.ClickFocus)
win5.setFocusPolicy(ttk.TTkK.TabFocus | ttk.TTkK.ClickFocus)
root.mainloop()
Loading…
Cancel
Save