diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 3348fdad..a7ddebfd 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -55,10 +55,10 @@ jobs: tools/check.import.sh - name: Install dependencies + shell: bash run: | python -m pip install --upgrade pip - python -m pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + python -m pip install -e 'libs/pyTermTk[test]' - name: Lint with flake8 run: | diff --git a/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollview.py b/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollview.py index a63d1901..60caa83e 100644 --- a/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollview.py +++ b/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollview.py @@ -503,7 +503,8 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf return self._excludeEvent = True for widget in self.iterWidgets(): - widget.viewMoveTo(x,y) + if isinstance(widget, TTkAbstractScrollViewInterface): + widget.viewMoveTo(x,y) self._excludeEvent = False self._viewOffsetX = x self._viewOffsetY = y @@ -595,9 +596,10 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf ''' w,h=0,0 for widget in self.iterWidgets(): - ww,wh = widget.viewFullAreaSize() - w = max(w,ww) - h = max(h,wh) + if isinstance(widget, TTkAbstractScrollViewInterface): + ww,wh = widget.viewFullAreaSize() + w = max(w,ww) + h = max(h,wh) return w,h # Override this function @@ -609,9 +611,10 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf ''' w,h=0,0 for widget in self.iterWidgets(): - ww,wh = widget.viewDisplayedSize() - w = max(w,ww) - h = max(h,wh) + if isinstance(widget, TTkAbstractScrollViewInterface): + ww,wh = widget.viewDisplayedSize() + w = max(w,ww) + h = max(h,wh) return w,h def getViewOffsets(self) -> Tuple[int,int]: diff --git a/libs/pyTermTk/TermTk/TTkCore/timer.py b/libs/pyTermTk/TermTk/TTkCore/timer.py index 9aad2a88..2c22040c 100644 --- a/libs/pyTermTk/TermTk/TTkCore/timer.py +++ b/libs/pyTermTk/TermTk/TTkCore/timer.py @@ -23,9 +23,12 @@ __all__ = ['TTkTimer'] import importlib.util +from typing import TYPE_CHECKING + +from .timer_interface import _TTkTimer_Interface as TTkTimer if importlib.util.find_spec('pyodideProxy'): - from .timer_pyodide import TTkTimer + from .timer_pyodide import _TTkTimer_Pyodide as TTkTimer else: - from .timer_unix import TTkTimer + from .timer_unix import _TTkTimer_Unix as TTkTimer diff --git a/libs/pyTermTk/TermTk/TTkCore/timer_interface.py b/libs/pyTermTk/TermTk/TTkCore/timer_interface.py new file mode 100644 index 00000000..c8e1456b --- /dev/null +++ b/libs/pyTermTk/TermTk/TTkCore/timer_interface.py @@ -0,0 +1,65 @@ +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# 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 Callable,Optional,Protocol + +from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot + +class _TTkTimer_Interface(Protocol): + '''Protocol defining the interface for timer implementations.''' + + timeout: pyTTkSignal + + def __init__( + self, + name: Optional[str] = None, + excepthook: Optional[Callable[[Exception], None]] = None) -> None: + '''Initialize timer with optional name and exception handler. + + :param name: Optional name for the timer + :type name: str, optional + :param excepthook: Optional callback for exception handling + :type excepthook: Callable[[Exception], None], optional + ''' + ... + + def quit(self) -> None: + '''Stop the timer and cleanup resources.''' + ... + + def run(self) -> None: + '''Main timer loop (typically runs in a thread).''' + ... + + def start(self, sec: float = 0.0) -> None: + '''Start the timer with specified interval. + + :param sec: Interval in seconds + :type sec: float + ''' + ... + + def stop(self) -> None: + '''Stop the timer without cleanup.''' + ... \ No newline at end of file diff --git a/libs/pyTermTk/TermTk/TTkCore/timer_pyodide.py b/libs/pyTermTk/TermTk/TTkCore/timer_pyodide.py index 71830cb2..c9319f5c 100644 --- a/libs/pyTermTk/TermTk/TTkCore/timer_pyodide.py +++ b/libs/pyTermTk/TermTk/TTkCore/timer_pyodide.py @@ -22,17 +22,18 @@ from __future__ import annotations -__all__ = ['TTkTimer'] +__all__ = [] from typing import Optional,Callable,Dict from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal +from TermTk.TTkCore.timer_interface import _TTkTimer_Interface -import pyodideProxy +import pyodideProxy # type: ignore[import-not-found] -class TTkTimer(): - _timers:Dict[int,TTkTimer] = {} +class _TTkTimer_Pyodide(_TTkTimer_Interface): + _timers:Dict[int,_TTkTimer_Pyodide] = {} _uid = 0 __slots__ = ( @@ -50,38 +51,38 @@ class TTkTimer(): self._running = True self._timer = None - self._id = TTkTimer._uid - TTkTimer._uid +=1 - TTkTimer._timers[self._id] = self + self._id = _TTkTimer_Pyodide._uid + _TTkTimer_Pyodide._uid +=1 + _TTkTimer_Pyodide._timers[self._id] = self @staticmethod - def triggerTimerId(tid): - if tid in TTkTimer._timers: + def triggerTimerId(tid) -> None: + if tid in _TTkTimer_Pyodide._timers: # Little hack to avoid deadloop in pyodide if rw := TTkHelper._rootWidget: rw._paintEvent.set() - TTkTimer._timers[tid].timeout.emit() + _TTkTimer_Pyodide._timers[tid].timeout.emit() @staticmethod - def pyodideQuit(): - for timer in TTkTimer._timers: - TTkTimer._timers[timer].timeout.clearAll() - TTkTimer._timers[timer]._running = False - TTkTimer._timers[timer].quit() - TTkTimer._timers = {} + def pyodideQuit() -> None: + for timer in _TTkTimer_Pyodide._timers: + _TTkTimer_Pyodide._timers[timer].timeout.clearAll() + _TTkTimer_Pyodide._timers[timer]._running = False + _TTkTimer_Pyodide._timers[timer].quit() + _TTkTimer_Pyodide._timers = {} - def quit(self): + def quit(self) -> None: pass @pyTTkSlot(float) - def start(self, sec=0.0): + def start(self, sec=0.0) -> None: self.stop() if self._running: self._timer = pyodideProxy.setTimeout(int(sec*1000), self._id) # pyodideProxy.consoleLog(f"Timer {self._timer}") @pyTTkSlot() - def stop(self): + def stop(self) -> None: # pyodideProxy.consoleLog(f"Timer {self._timer}") if self._timer: pyodideProxy.stopTimeout(self._timer) diff --git a/libs/pyTermTk/TermTk/TTkCore/timer_unix.py b/libs/pyTermTk/TermTk/TTkCore/timer_unix.py index a1ff52b4..ab622faa 100644 --- a/libs/pyTermTk/TermTk/TTkCore/timer_unix.py +++ b/libs/pyTermTk/TermTk/TTkCore/timer_unix.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -__all__ = ['TTkTimer'] +__all__ = [] from typing import Optional,Callable @@ -28,8 +28,9 @@ import threading from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.helper import TTkHelper +from TermTk.TTkCore.timer_interface import _TTkTimer_Interface -class TTkTimer(threading.Thread): +class _TTkTimer_Unix(threading.Thread, _TTkTimer_Interface): __slots__ = ( 'timeout', '_delay', '_timer', '_quit', '_start', @@ -51,14 +52,14 @@ class TTkTimer(threading.Thread): super().__init__(name=name) TTkHelper.quitEvent.connect(self.quit) - def quit(self): + def quit(self) -> None: TTkHelper.quitEvent.disconnect(self.quit) self.timeout.clear() self._quit.set() self._timer.set() self._start.set() - def run(self): + def run(self) -> None: try: while not self._quit.is_set(): self._start.wait() @@ -73,7 +74,7 @@ class TTkTimer(threading.Thread): raise e @pyTTkSlot(float) - def start(self, sec:float=0.0): + def start(self, sec:float=0.0) -> None: self._delay = sec self._timer.set() self._timer.clear() @@ -82,5 +83,5 @@ class TTkTimer(threading.Thread): super().start() @pyTTkSlot() - def stop(self): + def stop(self) -> None: self._timer.set() diff --git a/libs/pyTermTk/TermTk/TTkTestWidgets/tominspector.py b/libs/pyTermTk/TermTk/TTkTestWidgets/tominspector.py index 9bad9f70..d32724c4 100644 --- a/libs/pyTermTk/TermTk/TTkTestWidgets/tominspector.py +++ b/libs/pyTermTk/TermTk/TTkTestWidgets/tominspector.py @@ -151,7 +151,12 @@ class TTkTomInspector(TTkContainer): self.setLayout(layout) self._domTree = TTkTree() - self._domTree.setHeaderLabels(["Object", "Class", "Visibility", "Layout"]) + self._domTree.setHeaderLabels([ + TTkString("Object"), + TTkString("Class"), + TTkString("Visibility"), + TTkString("Layout") + ]) if TTkHelper._rootWidget: self._domTree.addTopLevelItem(TTkTomInspector._getTomTreeItem(TTkHelper._rootWidget._widgetItem)) diff --git a/libs/pyTermTk/pyproject.toml b/libs/pyTermTk/pyproject.toml index 10073c9a..c960569a 100644 --- a/libs/pyTermTk/pyproject.toml +++ b/libs/pyTermTk/pyproject.toml @@ -39,6 +39,13 @@ [tool.setuptools.packages.find] where = ["."] +[project.optional-dependencies] + test = [ + "pytest>=8.3.4", + "flake8>=7.2.0", + "mypy>=1.15.0" + ] + [tool.setuptools.dynamic] version = {attr = "TermTk.__version__"}