25 changed files with 2192 additions and 324 deletions
@ -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 |
||||
@ -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) |
||||
@ -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 |
||||
|
||||
@ -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) |
||||
@ -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 |
||||
|
||||
@ -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() |
||||
@ -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() |
||||
@ -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() |
||||
|
||||
@ -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…
Reference in new issue