From b066ee0ef3c7831aa59dfcf492ac9f2595b0bdca Mon Sep 17 00:00:00 2001 From: Pier CeccoPierangioliEugenio Date: Mon, 22 Dec 2025 19:50:10 +0000 Subject: [PATCH] chore: improve typing (#562) --- .../TermTk/TTkAbstract/abstractscrollarea.py | 99 +++++- .../TermTk/TTkAbstract/abstractscrollview.py | 291 +++++++++++++++--- .../TermTk/TTkAbstract/abstracttablemodel.py | 13 +- libs/pyTermTk/TermTk/TTkCore/constant.py | 4 +- .../TermTk/TTkTestWidgets/tominspector.py | 6 +- .../TermTk/TTkWidgets/TTkModelView/tree.py | 2 +- .../TTkWidgets/TTkTerminal/debugterminal.py | 26 +- tests/pytest/modelView/test_tablemodelcsv.py | 2 +- tests/pytest/modelView/test_tablemodellist.py | 2 +- 9 files changed, 372 insertions(+), 73 deletions(-) diff --git a/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollarea.py b/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollarea.py index 03f00b13..f5e55b70 100644 --- a/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollarea.py +++ b/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollarea.py @@ -23,7 +23,7 @@ __all__ = ['TTkAbstractScrollArea'] from dataclasses import dataclass -from typing import List,Any,Type +from typing import List,Any,Type,Optional from TermTk.TTkCore.constant import TTkK # from TermTk.TTkCore.log import TTkLog @@ -31,6 +31,7 @@ from TermTk.TTkCore.signal import pyTTkSlot from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.scrollbar import TTkScrollBar +from TermTk.TTkLayouts.layout import TTkLayout from TermTk.TTkLayouts.gridlayout import TTkGridLayout from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollViewInterface @@ -42,12 +43,25 @@ class _ForwardData(): methods: List[str] class TTkAbstractScrollArea(TTkContainer): + ''' + The :py:class:`TTkAbstractScrollArea` provides a scrolling area with on-demand scroll bars. + + This abstract class manages vertical and horizontal scroll bars that appear automatically + based on the viewport size and content. + ''' __slots__ = ( '_processing', # this flag is required to avoid unnecessary loop on edge cases '_viewport', '_verticalScrollBar', '_verticalScrollBarPolicy', '_horizontalScrollBar', '_horizontalScrollBarPolicy',) + _processing:bool + _viewport:Optional[TTkAbstractScrollViewInterface] + _verticalScrollBar:TTkScrollBar + _horizontalScrollBar:TTkScrollBar + _verticalScrollBarPolicy:TTkK.ScrollBarPolicy + _horizontalScrollBarPolicy:TTkK.ScrollBarPolicy + def __init__(self, *, verticalScrollBarPolicy:TTkK.ScrollBarPolicy=TTkK.ScrollBarPolicy.ScrollBarAsNeeded, horizontalScrollBarPolicy:TTkK.ScrollBarPolicy=TTkK.ScrollBarPolicy.ScrollBarAsNeeded, @@ -63,8 +77,10 @@ class TTkAbstractScrollArea(TTkContainer): self.layout().addWidget(self._verticalScrollBar) self.layout().addWidget(self._horizontalScrollBar) - def _resizeEvent(self): - if self._processing: return + def _resizeEvent(self) -> None: + ''' Internal method to handle resize events and reposition scroll bars ''' + if self._processing: + return self._processing = True w,h = self.size() vert = 1 if self._verticalScrollBar.isVisible() else 0 @@ -73,16 +89,24 @@ class TTkAbstractScrollArea(TTkContainer): self._verticalScrollBar.setGeometry(w-1,0,1,h-hori) if hori: self._horizontalScrollBar.setGeometry(0,h-1,w-vert,1) - if self._viewport: + if isinstance(self._viewport, (TTkWidget, TTkLayout)): self._viewport.setGeometry(0,0,w-vert,h-hori) self._processing = False - def resizeEvent(self, w: int, h: int): + def resizeEvent(self, w: int, h: int) -> None: + ''' Handle resize events + + :param w: the new width + :type w: int + :param h: the new height + :type h: int + ''' self._resizeEvent() @pyTTkSlot() - def _viewportChanged(self): - if not self.isVisible(): return + def _viewportChanged(self) -> None: + ''' Internal slot to handle viewport changes and update scroll bar ranges and visibility ''' + if not self._viewport or not self.isVisible(): return w,h = self.size() fw, fh = self._viewport.viewFullAreaSize() dw, dh = self._viewport.viewDisplayedSize() @@ -125,16 +149,37 @@ class TTkAbstractScrollArea(TTkContainer): self._resizeEvent() @pyTTkSlot(int) - def _vscrollMoved(self, val): + def _vscrollMoved(self, val: int) -> None: + ''' Internal slot to handle vertical scroll bar movement + + :param val: the new vertical scroll position + :type val: int + ''' + if not self._viewport: + return ox, _ = self._viewport.getViewOffsets() self._viewport.viewMoveTo(ox, val) @pyTTkSlot(int) - def _hscrollMoved(self, val): + def _hscrollMoved(self, val: int) -> None: + ''' Internal slot to handle horizontal scroll bar movement + + :param val: the new horizontal scroll position + :type val: int + ''' + if not self._viewport: + return _, oy = self._viewport.getViewOffsets() self._viewport.viewMoveTo(val, oy) - def setViewport(self, viewport): + def setViewport(self, viewport: TTkAbstractScrollViewInterface) -> None: + ''' Set the viewport widget + + :param viewport: the viewport widget implementing TTkAbstractScrollViewInterface + :type viewport: :py:class:`TTkAbstractScrollViewInterface` + + :raises TypeError: if viewport does not implement TTkAbstractScrollViewInterface + ''' if not isinstance(viewport, TTkAbstractScrollViewInterface): raise TypeError("TTkAbstractScrollViewInterface is required in TTkAbstractScrollArea.setVewport(viewport)") if self._viewport: @@ -157,20 +202,44 @@ class TTkAbstractScrollArea(TTkContainer): self.layout().addItem(viewport) self._resizeEvent() - def setVerticalScrollBarPolicy(self, policy): + def setVerticalScrollBarPolicy(self, policy: TTkK.ScrollBarPolicy) -> None: + ''' Set the vertical scroll bar policy + + :param policy: the scroll bar policy (ScrollBarAsNeeded, ScrollBarAlwaysOn, ScrollBarAlwaysOff) + :type policy: :py:class:`TTkK.ScrollBarPolicy` + ''' if policy != self._verticalScrollBarPolicy: self._verticalScrollBarPolicy = policy self._viewportChanged() - def setHorizontalScrollBarPolicy(self, policy): + def setHorizontalScrollBarPolicy(self, policy: TTkK.ScrollBarPolicy) -> None: + ''' Set the horizontal scroll bar policy + + :param policy: the scroll bar policy (ScrollBarAsNeeded, ScrollBarAlwaysOn, ScrollBarAlwaysOff) + :type policy: :py:class:`TTkK.ScrollBarPolicy` + ''' if policy != self._horizontalScrollBarPolicy: self._horizontalScrollBarPolicy = policy self._viewportChanged() - def viewport(self) -> TTkAbstractScrollViewInterface: + def viewport(self) -> Optional[TTkAbstractScrollViewInterface]: + ''' Return the current viewport + + :return: the viewport widget or None + :rtype: :py:class:`TTkAbstractScrollViewInterface`, optional + ''' return self._viewport - def update(self, repaint=True, updateLayout=False, updateParent=False): - if self._viewport: + def update(self, repaint: bool = True, updateLayout: bool = False, updateParent: bool = False) -> None: + ''' Update the scroll area and viewport + + :param repaint: trigger a repaint, defaults to True + :type repaint: bool, optional + :param updateLayout: trigger a layout update, defaults to False + :type updateLayout: bool, optional + :param updateParent: trigger a parent update, defaults to False + :type updateParent: bool, optional + ''' + if isinstance(self._viewport, (TTkWidget, TTkLayout)): self._viewport.update(repaint, updateLayout, updateParent=False) return super().update(repaint, updateLayout, updateParent) diff --git a/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollview.py b/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollview.py index a28ba735..a63d1901 100644 --- a/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollview.py +++ b/libs/pyTermTk/TermTk/TTkAbstract/abstractscrollview.py @@ -20,7 +20,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -__all__ = ['TTkAbstractScrollViewInterface', 'TTkAbstractScrollView', 'TTkAbstractScrollViewGridLayout'] +__all__ = ['TTkAbstractScrollViewInterface', 'TTkAbstractScrollView', 'TTkAbstractScrollViewLayout', 'TTkAbstractScrollViewGridLayout'] + +from typing import Tuple from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.cfg import TTkCfg @@ -46,10 +48,37 @@ class TTkAbstractScrollViewInterface(): * :py:class:`TTkAbstractScrollViewGridLayout` ''' - def __init__(self) -> None: pass + viewMovedTo:pyTTkSignal + ''' + This signal is emitted when the view content move to a new position (x,y), + + :param x: the new horizontal offset + :type x: int + :param y: the new vertical offset + :type y: int + ''' + viewSizeChanged:pyTTkSignal + ''' + This signal is emitted when the view content size changed + + :param width: the new width + :type width: int + :param height: the new height + :type height: int + ''' + viewChanged:pyTTkSignal + ''' + This signal is emitted whenever there is a change in the view content topology (size,pos) + + .. note:: This signal must be implemented in any implementation + of :py:class:`TTkAbstractScrollView` to notify that the view content boudaries changed + ''' + + def __init__(self) -> None: + pass # Override this function - def viewFullAreaSize(self) -> tuple[int,int]: + def viewFullAreaSize(self) -> Tuple[int,int]: ''' This method returns the full widget area size of the :py:class:`TTkAbstractScrollViewInterface` implementation. @@ -63,7 +92,7 @@ class TTkAbstractScrollViewInterface(): raise NotImplementedError() # Override this function - def viewDisplayedSize(self) -> tuple[int,int]: + def viewDisplayedSize(self) -> Tuple[int,int]: ''' This method returns the displayed size of the :py:class:`TTkAbstractScrollViewInterface` implementation. @@ -85,7 +114,7 @@ class TTkAbstractScrollViewInterface(): raise NotImplementedError() @pyTTkSlot(int, int) - def viewMoveTo(self, x: int, y: int): + def viewMoveTo(self, x: int, y: int) -> None: ''' This method is used to set the vertical and horizontal offsets of the :py:class:`TTkAbstractScrollViewInterface` @@ -105,7 +134,7 @@ class TTkAbstractScrollViewInterface(): ''' raise NotImplementedError() - def getViewOffsets(self) -> tuple: + def getViewOffsets(self) -> Tuple[int,int]: ''' Retrieve the vertical and horizontal offsets of the :py:class:`TTkAbstractScrollViewInterface` @@ -121,7 +150,7 @@ class TTkAbstractScrollViewInterface(): :return: the (x,y) offset :rtype: tuple[int,int] ''' - return self._viewOffsetX, self._viewOffsetY + raise NotImplementedError() class TTkAbstractScrollView(TTkContainer, TTkAbstractScrollViewInterface): ''' @@ -147,7 +176,7 @@ class TTkAbstractScrollView(TTkContainer, TTkAbstractScrollViewInterface): :param width: the new width :type width: int - :param height: the new heighht + :param height: the new height :type height: int ''' viewChanged:pyTTkSignal @@ -173,16 +202,33 @@ class TTkAbstractScrollView(TTkContainer, TTkAbstractScrollViewInterface): # Do NOT use super() TTkContainer.__init__(self, **kwargs) - def viewDisplayedSize(self) -> tuple[int,int]: + def viewDisplayedSize(self) -> Tuple[int,int]: + ''' Return the displayed size of this view + + :return: the (width, height) of the view + :rtype: tuple[int,int] + ''' return self.size() - def viewFullAreaSize(self) -> tuple[int,int]: + def viewFullAreaSize(self) -> Tuple[int,int]: + ''' Return the full area size including padding + + :return: the (width, height) of the full area + :rtype: tuple[int,int] + ''' t,b,l,r = self.getPadding() _,_,w,h = self.layout().fullWidgetAreaGeometry() return w+l+r, h+t+b @pyTTkSlot(int, int) - def viewMoveTo(self, x: int, y: int): + def viewMoveTo(self, x: int, y: int) -> None: + ''' Move the view to the specified offset position + + :param x: the horizontal offset + :type x: int + :param y: the vertical offset + :type y: int + ''' fw, fh = self.viewFullAreaSize() dw, dh = self.viewDisplayedSize() rangex = fw - dw @@ -191,8 +237,8 @@ class TTkAbstractScrollView(TTkContainer, TTkAbstractScrollViewInterface): x = max(0,min(rangex,x)) y = max(0,min(rangey,y)) # TTkLog.debug(f"x:{x},y:{y}, wo:{self._viewOffsetX,self._viewOffsetY}") - if self._viewOffsetX == x and \ - self._viewOffsetY == y: # Nothong to do + if ( self._viewOffsetX == x and + self._viewOffsetY == y ): # Nothing to do return self._viewOffsetX = x self._viewOffsetY = y @@ -200,10 +246,15 @@ class TTkAbstractScrollView(TTkContainer, TTkAbstractScrollViewInterface): self.viewChanged.emit() self.update() - def getViewOffsets(self) -> tuple: - return self._viewOffsetX, self._viewOffsetY + def wheelEvent(self, evt: TTkMouseEvent) -> bool: + ''' Handle mouse wheel events for scrolling + + :param evt: the mouse event + :type evt: :py:class:`TTkMouseEvent` - def wheelEvent(self, evt:TTkMouseEvent) -> bool: + :return: True if the event was handled + :rtype: bool + ''' delta = TTkCfg.scrollDelta offx, offy = self.getViewOffsets() if evt.evt == TTkK.WHEEL_Up: @@ -216,20 +267,65 @@ class TTkAbstractScrollView(TTkContainer, TTkAbstractScrollViewInterface): self.viewMoveTo(offx + delta, offy) return True - def resizeEvent(self, w, h): + def resizeEvent(self, w: int, h: int) -> None: + ''' Handle resize events + + :param w: the new width + :type w: int + :param h: the new height + :type h: int + ''' self.viewMoveTo(self._viewOffsetX, self._viewOffsetY) self.viewSizeChanged.emit(w,h) self.viewChanged.emit() - def update(self, repaint=True, updateLayout=False, updateParent=False): + def update(self, repaint: bool = True, updateLayout: bool = False, updateParent: bool = False) -> None: + ''' Update the widget and emit viewChanged signal if layout is updated + + :param repaint: trigger a repaint, defaults to True + :type repaint: bool, optional + :param updateLayout: trigger a layout update, defaults to False + :type updateLayout: bool, optional + :param updateParent: trigger a parent update, defaults to False + :type updateParent: bool, optional + ''' if updateLayout: self.viewChanged.emit() return super().update(repaint, updateLayout, updateParent) - def setPadding(self, top, bottom, left, right) -> None: + def setPadding(self, top: int, bottom: int, left: int, right: int) -> None: + ''' Set the padding and emit viewChanged signal + + :param top: the top padding + :type top: int + :param bottom: the bottom padding + :type bottom: int + :param left: the left padding + :type left: int + :param right: the right padding + :type right: int + ''' super().setPadding(top, bottom, left, right) self.viewChanged.emit() + def getViewOffsets(self) -> Tuple[int,int]: + ''' + Retrieve the vertical and horizontal offsets of the :py:class:`TTkAbstractScrollViewInterface` + + .. note:: + + Reimplement this function to handle this event + + This method is already implemented in the following classes: + * :py:class:`TTkAbstractScrollView` + * :py:class:`TTkAbstractScrollViewLayout` + * :py:class:`TTkAbstractScrollViewGridLayout` + + :return: the (x,y) offset + :rtype: tuple[int,int] + ''' + return self._viewOffsetX, self._viewOffsetY + class TTkAbstractScrollViewLayout(TTkLayout, TTkAbstractScrollViewInterface): ''' :py:class:`TTkAbstractScrollViewLayout` @@ -250,7 +346,7 @@ class TTkAbstractScrollViewLayout(TTkLayout, TTkAbstractScrollViewInterface): :param width: the new width :type width: int - :param height: the new heighht + :param height: the new height :type height: int ''' viewChanged:pyTTkSignal @@ -276,25 +372,68 @@ class TTkAbstractScrollViewLayout(TTkLayout, TTkAbstractScrollViewInterface): self._excludeEvent = False TTkLayout.__init__(self, *args, **kwargs) - def viewFullAreaSize(self) -> tuple[int,int]: + def viewFullAreaSize(self) -> Tuple[int,int]: + ''' Return the full area size of the layout + + :return: the (width, height) of the full area + :rtype: tuple[int,int] + ''' _,_,w,h = self.fullWidgetAreaGeometry() return w,h - def viewDisplayedSize(self) -> tuple[int,int]: + def viewDisplayedSize(self) -> Tuple[int,int]: + ''' Return the displayed size of the layout + + :return: the (width, height) of the displayed area + :rtype: tuple[int,int] + ''' _,_,w,h = self.geometry() return w,h @pyTTkSlot(int, int) - def viewMoveTo(self, x: int, y: int): + def viewMoveTo(self, x: int, y: int) -> None: + ''' Move the view to the specified offset position + + :param x: the horizontal offset + :type x: int + :param y: the vertical offset + :type y: int + ''' self.setOffset(-x,-y) - def getViewOffsets(self) -> tuple: - return self._viewOffsetX, self._viewOffsetY + def setGeometry(self, x: int, y: int, w: int, h: int) -> None: + ''' Set the geometry and emit viewChanged signal - def setGeometry(self, x, y, w, h): + :param x: the horizontal position + :type x: int + :param y: the vertical position + :type y: int + :param w: the width + :type w: int + :param h: the height + :type h: int + ''' TTkLayout.setGeometry(self, x, y, w, h) self.viewChanged.emit() + def getViewOffsets(self) -> Tuple[int,int]: + ''' + Retrieve the vertical and horizontal offsets of the :py:class:`TTkAbstractScrollViewInterface` + + .. note:: + + Reimplement this function to handle this event + + This method is already implemented in the following classes: + * :py:class:`TTkAbstractScrollView` + * :py:class:`TTkAbstractScrollViewLayout` + * :py:class:`TTkAbstractScrollViewGridLayout` + + :return: the (x,y) offset + :rtype: tuple[int,int] + ''' + return self._viewOffsetX, self._viewOffsetY + class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterface): ''' :py:class:`TTkAbstractScrollViewGridLayout` @@ -315,7 +454,7 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf :param width: the new width :type width: int - :param height: the new heighht + :param height: the new height :type height: int ''' viewChanged:pyTTkSignal @@ -343,7 +482,14 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf TTkGridLayout.__init__(self, *args, **kwargs) @pyTTkSlot(int, int) - def viewMoveTo(self, x: int, y: int): + def viewMoveTo(self, x: int, y: int) -> None: + ''' Move the view to the specified offset position and propagate to child widgets + + :param x: the horizontal offset + :type x: int + :param y: the vertical offset + :type y: int + ''' fw, fh = self.viewFullAreaSize() dw, dh = self.viewDisplayedSize() rangex = fw - dw @@ -353,7 +499,7 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf y = max(0,min(rangey,y)) # TTkLog.debug(f"x:{x},y:{y}, wo:{self._viewOffsetX,self._viewOffsetY}") if self._viewOffsetX == x and \ - self._viewOffsetY == y: # Nothong to do + self._viewOffsetY == y: # Nothing to do return self._excludeEvent = True for widget in self.iterWidgets(): @@ -365,24 +511,55 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf self.viewChanged.emit() self.update() - def getViewOffsets(self) -> tuple: - return self._viewOffsetX, self._viewOffsetY + def setGeometry(self, x: int, y: int, w: int, h: int) -> None: + ''' Set the geometry and emit viewChanged signal - def setGeometry(self, x, y, w, h): + :param x: the horizontal position + :type x: int + :param y: the vertical position + :type y: int + :param w: the width + :type w: int + :param h: the height + :type h: int + ''' TTkGridLayout.setGeometry(self, x, y, w, h) self.viewChanged.emit() @pyTTkSlot() - def _viewChanged(self): + def _viewChanged(self) -> None: + ''' Internal slot to handle viewChanged signal from child widgets ''' if self._excludeEvent: return self.viewChanged.emit() @pyTTkSlot(int,int) - def _viewMovedTo(self, x, y): + def _viewMovedTo(self, x: int, y: int) -> None: + ''' Internal slot to handle viewMovedTo signal from child widgets + + :param x: the horizontal offset + :type x: int + :param y: the vertical offset + :type y: int + ''' if self._excludeEvent: return self.viewMoveTo(x, y) def addWidget(self, widget, row=None, col=None, rowspan=1, colspan=1): + ''' Add a widget that implements :py:class:`TTkAbstractScrollViewInterface` to the grid layout + + :param widget: the widget to be added (must implement TTkAbstractScrollViewInterface) + :type widget: :py:class:`TTkWidget` + :param row: the row position, optional, defaults to None + :type row: int, optional + :param col: the column position, optional, defaults to None + :type col: int, optional + :param rowspan: number of rows to span, defaults to 1 + :type rowspan: int, optional + :param colspan: number of columns to span, defaults to 1 + :type colspan: int, optional + + :raises TypeError: if widget does not implement TTkAbstractScrollViewInterface + ''' if not issubclass(type(widget),TTkAbstractScrollViewInterface): raise TypeError("TTkAbstractScrollViewInterface is required in TTkAbstractScrollViewGridLayout.addWidget(...)") widget.viewChanged.connect(self._viewChanged) @@ -390,12 +567,32 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf return TTkGridLayout.addWidget(self, widget, row, col, rowspan, colspan) def addItem(self, item, row=None, col=None, rowspan=1, colspan=1): + ''' Add a layout item that implements :py:class:`TTkAbstractScrollViewInterface` to the grid layout + + :param item: the layout item to be added (must implement TTkAbstractScrollViewInterface) + :type item: :py:class:`TTkLayoutItem` + :param row: the row position, optional, defaults to None + :type row: int, optional + :param col: the column position, optional, defaults to None + :type col: int, optional + :param rowspan: number of rows to span, defaults to 1 + :type rowspan: int, optional + :param colspan: number of columns to span, defaults to 1 + :type colspan: int, optional + + :raises TypeError: if item does not implement TTkAbstractScrollViewInterface + ''' if not issubclass(type(item),TTkAbstractScrollViewInterface): raise TypeError("TTkAbstractScrollViewInterface is required in TTkAbstractScrollViewGridLayout.addItem(...)") return TTkGridLayout.addItem(self, item, row, col, rowspan, colspan) # Override this function - def viewFullAreaSize(self) -> tuple[int,int]: + def viewFullAreaSize(self) -> Tuple[int,int]: + ''' Return the full area size by computing the maximum size from all child widgets + + :return: the (width, height) of the full area + :rtype: tuple[int,int] + ''' w,h=0,0 for widget in self.iterWidgets(): ww,wh = widget.viewFullAreaSize() @@ -404,7 +601,12 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf return w,h # Override this function - def viewDisplayedSize(self) -> tuple[int,int]: + def viewDisplayedSize(self) -> Tuple[int,int]: + ''' Return the displayed size by computing the maximum displayed size from all child widgets + + :return: the (width, height) of the displayed area + :rtype: tuple[int,int] + ''' w,h=0,0 for widget in self.iterWidgets(): ww,wh = widget.viewDisplayedSize() @@ -412,3 +614,20 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf h = max(h,wh) return w,h + def getViewOffsets(self) -> Tuple[int,int]: + ''' + Retrieve the vertical and horizontal offsets of the :py:class:`TTkAbstractScrollViewInterface` + + .. note:: + + Reimplement this function to handle this event + + This method is already implemented in the following classes: + * :py:class:`TTkAbstractScrollView` + * :py:class:`TTkAbstractScrollViewLayout` + * :py:class:`TTkAbstractScrollViewGridLayout` + + :return: the (x,y) offset + :rtype: tuple[int,int] + ''' + return self._viewOffsetX, self._viewOffsetY \ No newline at end of file diff --git a/libs/pyTermTk/TermTk/TTkAbstract/abstracttablemodel.py b/libs/pyTermTk/TermTk/TTkAbstract/abstracttablemodel.py index e4610d07..6c68be76 100644 --- a/libs/pyTermTk/TermTk/TTkAbstract/abstracttablemodel.py +++ b/libs/pyTermTk/TermTk/TTkAbstract/abstracttablemodel.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from __future__ import annotations + __all__ = ['TTkAbstractTableModel','TTkModelIndex'] from typing import Tuple,Any @@ -52,7 +54,7 @@ class TTkModelIndex(): :return: int ''' - pass + raise NotImplementedError() def col(self) -> int: ''' @@ -60,7 +62,8 @@ class TTkModelIndex(): :return: int ''' - pass + raise NotImplementedError() + def data(self) -> object: ''' @@ -68,7 +71,7 @@ class TTkModelIndex(): :return: object ''' - pass + raise NotImplementedError() def setData(self, data:object) -> None: ''' @@ -77,11 +80,11 @@ class TTkModelIndex(): :param data: the data to be set in the (row,col) position of the table :type data: object ''' - pass + raise NotImplementedError() class _TTkModelIndexList(TTkModelIndex): __slots__ = ('_col','_row','_model') - def __init__(self, row:int, col:list, model) -> None: + def __init__(self, row:int, col:int, model:TTkAbstractTableModel) -> None: self._col = col self._row = row self._model = model diff --git a/libs/pyTermTk/TermTk/TTkCore/constant.py b/libs/pyTermTk/TermTk/TTkCore/constant.py index 2cc67c5d..956b3816 100644 --- a/libs/pyTermTk/TermTk/TTkCore/constant.py +++ b/libs/pyTermTk/TermTk/TTkCore/constant.py @@ -169,7 +169,7 @@ class TTkConstant: HORIZONTAL = Direction.HORIZONTAL VERTICAL = Direction.VERTICAL - class ScrollBarPolicy(int): + class ScrollBarPolicy(IntEnum): ScrollBarAsNeeded = 0x00 ScrollBarAlwaysOff = 0x01 ScrollBarAlwaysOn = 0x02 @@ -543,7 +543,7 @@ class TTkConstant: ClearAndSelect = Clear | Select '''A combination of Clear and Select, provided for convenience.''' - class ItemFlag(int): + class ItemFlag(Flag): ''':py:class:`ItemFlag` describes the properties of an item .. autosummary:: diff --git a/libs/pyTermTk/TermTk/TTkTestWidgets/tominspector.py b/libs/pyTermTk/TermTk/TTkTestWidgets/tominspector.py index 03c33933..9bad9f70 100644 --- a/libs/pyTermTk/TermTk/TTkTestWidgets/tominspector.py +++ b/libs/pyTermTk/TermTk/TTkTestWidgets/tominspector.py @@ -22,6 +22,8 @@ __all__ = ['TTkTomInspector'] +from typing import List + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.helper import TTkHelper @@ -75,6 +77,7 @@ class _DetailGridView(TTkAbstractScrollView): class _DetailLazyFormView(TTkAbstractScrollView): __slots__ = ('_gridLayout', '_lazyRows', '_lastRow') + _lazyRows:List[List] def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.setPadding(1,0,0,0) @@ -149,7 +152,8 @@ class TTkTomInspector(TTkContainer): self._domTree = TTkTree() self._domTree.setHeaderLabels(["Object", "Class", "Visibility", "Layout"]) - self._domTree.addTopLevelItem(TTkTomInspector._getTomTreeItem(TTkHelper._rootWidget._widgetItem)) + if TTkHelper._rootWidget: + self._domTree.addTopLevelItem(TTkTomInspector._getTomTreeItem(TTkHelper._rootWidget._widgetItem)) self._detail = TTkFrame() diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tree.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tree.py index 205382e7..ef76fdb6 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tree.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tree.py @@ -25,7 +25,7 @@ __all__ = ['TTkTree'] from typing import List,Optional from TermTk.TTkCore.constant import TTkK -from TermTk.TTkCore.string import TTkString +from TermTk.TTkCore.string import TTkString, TTkStringType from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkWidgets.TTkModelView.treewidget import TTkTreeWidget from TermTk.TTkWidgets.TTkModelView.treewidgetitem import TTkTreeWidgetItem diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/debugterminal.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/debugterminal.py index 00a14591..5677e83e 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/debugterminal.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkTerminal/debugterminal.py @@ -20,7 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -__all__ = ['DebugTTkTerminal'] +# __all__ = ['DebugTTkTerminal'] +__all__ = [] import os, pty, threading import struct, fcntl, termios @@ -48,6 +49,7 @@ from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView, TTkAbstractScrollViewGridLayout from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.TTkTerminal import TTkTerminal from TermTk.TTkWidgets.TTkTerminal.mode import TTkTerminalModes from TermTk.TTkWidgets.TTkTerminal.vt102 import TTkVT102 @@ -55,14 +57,16 @@ from TermTk.TTkWidgets.TTkTerminal.vt102 import TTkVT102 from TermTk.TTkCore.TTkTerm.colors import TTkTermColor from TermTk.TTkCore.TTkTerm.colors_ansi_map import ansiMap16, ansiMap256 -class DebugTTkTerminal(TTkWidget): - __slots__ = ('_terminal') - def __init__(self, terminal, **kwargs) -> None: - super().__init__(**kwargs) +# class DebugTTkTerminal(TTkWidget): +# __slots__ = ('_terminal') +# _terminal:TTkTerminal +# def __init__(self, terminal, **kwargs) -> None: +# self._terminal = terminal +# super().__init__(**kwargs) - def paintEvent(self, canvas: TTkCanvas): - t = self._terminal - canvas.drawText(pos=(0,0), text=f"{t=}") - canvas.drawText(pos=(0,1), text=f"{t._terminalCursor=}") - canvas.drawText(pos=(0,2), text=f"{t._scrollingRegion=}") - canvas.drawText(pos=(0,3), text=f"{len(t._bufferedLines)=}") +# def paintEvent(self, canvas: TTkCanvas): +# t = self._terminal +# canvas.drawText(pos=(0,0), text=f"{t=}") +# canvas.drawText(pos=(0,1), text=f"{t._terminalCursor=}") +# canvas.drawText(pos=(0,2), text=f"{t._scrollingRegion=}") +# canvas.drawText(pos=(0,3), text=f"{len(t._bufferedLines)=}") diff --git a/tests/pytest/modelView/test_tablemodelcsv.py b/tests/pytest/modelView/test_tablemodelcsv.py index a82a32b6..da7e96a1 100644 --- a/tests/pytest/modelView/test_tablemodelcsv.py +++ b/tests/pytest/modelView/test_tablemodelcsv.py @@ -306,7 +306,7 @@ class TestTTkTableModelCSV: model = ttk.TTkTableModelCSV(fd=csv_fd) # Test inherited methods - assert isinstance(model.flags(0, 0), int) + assert isinstance(model.flags(0, 0), ttk.TTkK.ItemFlag) # Test setData (inherited) original_value = model.data(0, 0) diff --git a/tests/pytest/modelView/test_tablemodellist.py b/tests/pytest/modelView/test_tablemodellist.py index 77ad9c3b..5e6dc27d 100644 --- a/tests/pytest/modelView/test_tablemodellist.py +++ b/tests/pytest/modelView/test_tablemodellist.py @@ -514,7 +514,7 @@ def test_integration_with_table_widget(): assert isinstance(model.rowCount(), int) assert isinstance(model.columnCount(), int) assert isinstance(model.data(0, 0), (str, int, float, type(None))) - assert isinstance(model.flags(0, 0), int) # Flags are integer bitmasks + assert isinstance(model.flags(0, 0), ttk.TTkK.ItemFlag) # Flags are integer bitmasks if __name__ == '__main__':