diff --git a/TermTk/TTkCore/constant.py b/TermTk/TTkCore/constant.py index b02502ce..7a88bec1 100644 --- a/TermTk/TTkCore/constant.py +++ b/TermTk/TTkCore/constant.py @@ -42,12 +42,37 @@ class TTkConstant: Background = ColorType.Background Modifier = ColorType.Modifier - # Focus Policies - NoFocus = 0x0000 - ClickFocus = 0x0001 - WheelFocus = 0x0002 - TabFocus = 0x0004 - ParentFocus = 0x0101 + class FocusPolicy(int): + ''' + This Class type defines the various policies a widget + can have with respect to acquiring keyboard focus. + + .. autosummary:: + NoFocus + ClickFocus + WheelFocus + TabFocus + ParentFocus + ''' + NoFocus = 0x0000 + '''The widget does not accept focus.''' + ClickFocus = 0x0001 + '''The widget accepts focus by clicking.''' + WheelFocus = 0x0002 + '''The widget accepts focus by using the mouse wheel.''' + TabFocus = 0x0004 + '''The widget accepts focus by tabbing.''' + ParentFocus = 0x0101 + '''The parent widget forward the focus to this widget''' + StrongFocus = TabFocus | ClickFocus | 0x8 + '''the widget accepts focus by both tabbing and clicking.''' + + NoFocus = FocusPolicy.NoFocus + ClickFocus = FocusPolicy.ClickFocus + WheelFocus = FocusPolicy.WheelFocus + TabFocus = FocusPolicy.TabFocus + ParentFocus = FocusPolicy.ParentFocus + StrongFocus = FocusPolicy.StrongFocus # positions NONE = 0x0000 diff --git a/TermTk/TTkCore/ttk.py b/TermTk/TTkCore/ttk.py index ae86f46e..b033732b 100644 --- a/TermTk/TTkCore/ttk.py +++ b/TermTk/TTkCore/ttk.py @@ -42,6 +42,7 @@ from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.timer import TTkTimer from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.shortcut import TTkShortcut +from TermTk.TTkWidgets.about import TTkAbout from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkWidgets.container import TTkContainer @@ -369,3 +370,13 @@ class TTk(TTkContainer): def isVisibleAndParent(self): return self.isVisible() + + @pyTTkSlot() + def aboutTermTk(self): + ''' + Displays a simple message box about `pyTermTk `__. + The message includes the version number of TermTk being used by the application. + + This is useful for inclusion in the Help menu of an application, as shown in the Menus example. + ''' + TTkHelper.overlay(None, TTkAbout(), 30,10) \ No newline at end of file diff --git a/TermTk/TTkTemplates/keyevents.py b/TermTk/TTkTemplates/keyevents.py index a7c24086..82cb4c0b 100644 --- a/TermTk/TTkTemplates/keyevents.py +++ b/TermTk/TTkTemplates/keyevents.py @@ -20,35 +20,37 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -class TKeyEvents(): - def keyPressEvent(self, evt) -> bool : - ''' - This event handler, can be reimplemented in a subclass to receive key press events for the widget. - - .. note:: Reimplement this function to handle this event - - :param evt: The keyboard event - :type evt: :py:class:`TTkKeyEvent` - - :return: **True** if the event has been handled - :rtype: bool - ''' - return False - def keyReleaseEvent(self, evt) -> bool : - ''' - This event handler, can be reimplemented in a subclass to receive key release events for the widget. - - .. note:: Reimplement this function to handle this event +from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent - :param evt: The keyboard event - :type evt: :py:class:`TTkKeyEvent` - - :return: **True** if the event has been handled - :rtype: bool - ''' - return False +class TKeyEvents(): +# def keyPressEvent(self, evt:TTkKeyEvent) -> bool : +# ''' +# This event handler, can be reimplemented in a subclass to receive key press events for the widget. +# +# .. note:: Reimplement this function to handle this event +# +# :param evt: The keyboard event +# :type evt: :py:class:`TTkKeyEvent` +# +# :return: **True** if the event has been handled +# :rtype: bool +# ''' +# return False +# def keyReleaseEvent(self, evt:TTkKeyEvent) -> bool : +# ''' +# This event handler, can be reimplemented in a subclass to receive key release events for the widget. +# +# .. note:: Reimplement this function to handle this event +# +# :param evt: The keyboard event +# :type evt: :py:class:`TTkKeyEvent` +# +# :return: **True** if the event has been handled +# :rtype: bool +# ''' +# return False - def keyEvent(self, evt) -> bool : + def keyEvent(self, evt:TTkKeyEvent) -> bool : ''' This event handler, can be reimplemented in a subclass to receive key events for the widget. diff --git a/TermTk/TTkTemplates/mouseevents.py b/TermTk/TTkTemplates/mouseevents.py index 5dfbc8e7..f6ad372d 100644 --- a/TermTk/TTkTemplates/mouseevents.py +++ b/TermTk/TTkTemplates/mouseevents.py @@ -20,8 +20,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent + class TMouseEvents(): - def mouseTapEvent(self, evt) -> bool : + def mouseTapEvent(self, evt:TTkMouseEvent) -> bool : ''' This event handler, can be reimplemented in a subclass to receive mouse click events for the widget. @@ -35,7 +37,7 @@ class TMouseEvents(): ''' return True - def mouseDoubleClickEvent(self, evt) -> bool : + def mouseDoubleClickEvent(self, evt:TTkMouseEvent) -> bool : ''' This event handler, can be reimplemented in a subclass to receive mouse click events for the widget. @@ -49,7 +51,7 @@ class TMouseEvents(): ''' return False - def mouseMoveEvent(self, evt) -> bool : + def mouseMoveEvent(self, evt:TTkMouseEvent) -> bool : ''' This event handler, can be reimplemented in a subclass to receive mouse move events for the widget. @@ -63,7 +65,7 @@ class TMouseEvents(): ''' return False - def mouseDragEvent(self, evt) -> bool : + def mouseDragEvent(self, evt:TTkMouseEvent) -> bool : ''' This event handler, can be reimplemented in a subclass to receive mouse drag events for the widget. @@ -77,7 +79,7 @@ class TMouseEvents(): ''' return False - def mousePressEvent(self, evt) -> bool : + def mousePressEvent(self, evt:TTkMouseEvent) -> bool : ''' This event handler, can be reimplemented in a subclass to receive mouse press events for the widget. @@ -91,7 +93,7 @@ class TMouseEvents(): ''' return False - def mouseReleaseEvent(self, evt) -> bool : + def mouseReleaseEvent(self, evt:TTkMouseEvent) -> bool : ''' This event handler, can be reimplemented in a subclass to receive mouse release events for the widget. @@ -105,7 +107,7 @@ class TMouseEvents(): ''' return False - def wheelEvent(self, evt) -> bool : + def wheelEvent(self, evt:TTkMouseEvent) -> bool : ''' This event handler, can be reimplemented in a subclass to receive mouse wheel events for the widget. @@ -119,7 +121,7 @@ class TMouseEvents(): ''' return False - def enterEvent(self, evt) -> bool : + def enterEvent(self, evt:TTkMouseEvent) -> bool : ''' This event handler, can be reimplemented in a subclass to receive mouse enter events for the widget. @@ -133,7 +135,7 @@ class TMouseEvents(): ''' return self._processStyleEvent(self._S_HOVER) - def leaveEvent(self, evt) -> bool : + def leaveEvent(self, evt:TTkMouseEvent) -> bool : ''' This event handler, can be reimplemented in a subclass to receive mouse leave events for the widget. diff --git a/TermTk/TTkWidgets/about.py b/TermTk/TTkWidgets/about.py index 9a5d3236..933fbadb 100644 --- a/TermTk/TTkWidgets/about.py +++ b/TermTk/TTkWidgets/about.py @@ -41,6 +41,37 @@ _peppered_image = TTkUtil.base64_deflate_2_obj( "4Zf9GYaCzmMK1e6+dLH7AkxADRtZ3ojHUrmNqNtco9mqF7pzbppx0GQSQ3Op8FcJ2G5ltCNVLuI74ZeG5vr8rDHAaP/o5bQnnOawWcPyRePnp/8KRQkp") class TTkAbout(TTkWindow): + ''' + This is a basic window widget that displays a simple message box about TermTk. + The message includes the version number of TermTk being used by the application. + + This is useful for inclusion in the Help menu of an application, as shown in the Menus example. + + :py:class:`TTk` provides this functionality as a slot (:py:meth:`TTk.aboutTermTk`). + + .. code-block:: python + + import TermTk + + root = TermTk.TTk() + + TermTk.TTkAbout(parent=root) + + root.mainloop() + + + .. code-block:: python + + from TermTk import TTk, TTkButton + + root = TTk() + + btn = TTkButton(parent=root, size=(15,3), border=True, text="About!!!") + btn.clicked.connect(root.aboutTermTk) + + root.mainloop() + + ''' peppered=[ [(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x01,0x00),(0x39,0x61,0x00),(0x76,0x9e,0x17),(0x87,0x9f,0x3a),(0x3d,0x4c,0x14),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00)], [(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x02,0x11,0x00),(0x21,0x44,0x01),(0x99,0xc1,0x33),(0x4e,0x71,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00),(0x00,0x00,0x00)], diff --git a/TermTk/TTkWidgets/apptemplate.py b/TermTk/TTkWidgets/apptemplate.py index b26bd074..50f8243d 100644 --- a/TermTk/TTkWidgets/apptemplate.py +++ b/TermTk/TTkWidgets/apptemplate.py @@ -23,11 +23,12 @@ __all__ = ['TTkAppTemplate'] from dataclasses import dataclass -from TermTk.TTkCore.canvas import TTkCanvas from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.canvas import TTkCanvas from TermTk.TTkCore.string import TTkString +from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent from TermTk.TTkLayouts import TTkLayout, TTkGridLayout from TermTk.TTkWidgets.container import TTkWidget, TTkContainer from TermTk.TTkWidgets.menubar import TTkMenuBarLayout @@ -55,14 +56,52 @@ class TTkAppTemplate(TTkContainer): R L ''' - MAIN = TTkK.CENTER - TOP = TTkK.TOP - BOTTOM = TTkK.BOTTOM - LEFT = TTkK.LEFT - RIGHT = TTkK.RIGHT - CENTER = TTkK.CENTER - HEADER = TTkK.HEADER - FOOTER = TTkK.FOOTER + class Position(int): + ''' This Class enumerate the different panels available in the layout of :py:class:`TTkAppTemplate` + + .. autosummary:: + MAIN + TOP + BOTTOM + LEFT + RIGHT + CENTER + HEADER + FOOTER + ''' + MAIN = TTkK.CENTER + '''Main''' + TOP = TTkK.TOP + '''Top''' + BOTTOM = TTkK.BOTTOM + '''Bottom''' + LEFT = TTkK.LEFT + '''Left''' + RIGHT = TTkK.RIGHT + '''Right''' + CENTER = TTkK.CENTER + '''Center''' + HEADER = TTkK.HEADER + '''Header''' + FOOTER = TTkK.FOOTER + '''Footer''' + + MAIN = Position.MAIN + ''':py:class:`Position.MAIN`''' + TOP = Position.TOP + ''':py:class:`Position.TOP`''' + BOTTOM = Position.BOTTOM + ''':py:class:`Position.BOTTOM`''' + LEFT = Position.LEFT + ''':py:class:`Position.LEFT`''' + RIGHT = Position.RIGHT + ''':py:class:`Position.RIGHT`''' + CENTER = Position.CENTER + ''':py:class:`Position.CENTER`''' + HEADER = Position.HEADER + ''':py:class:`Position.HEADER`''' + FOOTER = Position.FOOTER + ''':py:class:`Position.FOOTER`''' @dataclass(frozen=False) class _Panel: @@ -162,7 +201,26 @@ class TTkAppTemplate(TTkContainer): self._updateGeometries(force=True) self.setFocusPolicy(TTkK.ClickFocus) - def setWidget(self, widget, position=MAIN, size=None, title="", border=None, fixed=None): + def setWidget(self, + widget:TTkWidget, position:Position=Position.MAIN, + size:int=None, title:TTkString="", + border:bool=None, fixed:bool=None) -> None: + ''' + Place the :py:class:`TTkWidget` in the :py:class:`TTkAppTemplate`'s panel identified by its :py:class:`Position` + + :param widget: + :type widget: :py:class:`TTkWidget` + :param position: defaults to :py:class:`Position.MAIN` + :type position: :py:class:`Position`, optional + :param size: defaults to None + :type size: int, optional + :param title: defaults to "" + :type title: :py:class:`TTkString`, optional + :param border: defaults to True + :type border: bool, optional + :param fixed: defaults to False + :type fixed: bool, optional + ''' if not self._panels[position]: self._panels[position] = TTkAppTemplate._Panel() if wid:=self._panels[position].widget: @@ -184,7 +242,26 @@ class TTkAppTemplate(TTkContainer): widget.minimumHeight() ) self._updateGeometries(force=True) - def setItem(self, item, position=MAIN, size=None, title="", border=None, fixed=None): + def setItem(self, + item:TTkLayout, position:Position=Position.MAIN, + size:int=None, title:TTkString="", + border:bool=None, fixed:bool=None) -> None: + ''' + Place the :py:class:`TTkLayout` in the :py:class:`TTkAppTemplate`'s panel identified by its :py:class:`Position` + + :param item: + :type item: :py:class:`TTkLayout` + :param position: defaults to :py:class:`Position.MAIN` + :type position: :py:class:`Position`, optional + :param size: defaults to None + :type size: int, optional + :param title: defaults to "" + :type title: :py:class:`TTkString`, optional + :param border: defaults to True + :type border: bool, optional + :param fixed: defaults to False + :type fixed: bool, optional + ''' if not self._panels[position]: self._panels[position] = TTkAppTemplate._Panel() if wid:=self._panels[position].widget: @@ -206,15 +283,28 @@ class TTkAppTemplate(TTkContainer): item.minimumHeight() ) self._updateGeometries(force=True) - def setTitle(self, position=MAIN, title=""): + def setTitle(self, position:Position=Position.MAIN, title:str=""): + '''Set the title of the panel identified by the "position" + + :param position: defaults to :py:class:`Position.MAIN` + :type position: :py:class:`Position`, optional + :param title: defaults to "" + :type title: :py:class:`TTkString`, optional + ''' if not self._panels[position]: return self._panels[position].title = TTkString(title) if title else "" self._updateGeometries(force=True) - def menuBar(self, position=MAIN): + def menuBar(self, position:Position=MAIN) -> TTkMenuBarLayout: + ''' + Retrieve the :py:class:`TTkMenuBarLayout` in the panel identified by the position. + + :param position: defaults to :py:class:`Position.MAIN` + :type position: :py:class:`Position`, optional + ''' return None if not self._panels[position] else self._panels[position].menubar - def setMenuBar(self, menuBar, position=MAIN): + def setMenuBar(self, menuBar:TTkMenuBarLayout, position:Position=MAIN) -> None: if not self._panels[position]: self._panels[position] = TTkAppTemplate._Panel() p = self._panels[position] @@ -226,31 +316,31 @@ class TTkAppTemplate(TTkContainer): self.rootLayout().addItem(p.menubar) self._updateGeometries(force=True) - def setBorder(self, border=True, position=MAIN): + def setBorder(self, border=True, position=MAIN) -> None: if not self._panels[position]: self._panels[position] = TTkAppTemplate._Panel() self._panels[position].border = border self._updateGeometries(force=True) - def setFixed(self, fixed=False, position=MAIN): + def setFixed(self, fixed=False, position=MAIN) -> None: if not self._panels[position]: self._panels[position] = TTkAppTemplate._Panel() self._panels[position].fixed = fixed self._updateGeometries(force=True) - def resizeEvent(self, w, h): + def resizeEvent(self, width: int, height: int) -> None: self._updateGeometries() - def focusOutEvent(self): + def focusOutEvent(self) -> None: self._selected = None self.update() - def mouseReleaseEvent(self, evt): + def mouseReleaseEvent(self, evt: TTkMouseEvent) -> bool: self._selected = None self.update() return True - def mousePressEvent(self, evt): + def mousePressEvent(self, evt: TTkMouseEvent) -> bool: self._selected = [] self._updateGeometries() spl = self._splitters @@ -263,7 +353,7 @@ class TTkAppTemplate(TTkContainer): self._selected.append(loc) return True - def mouseDragEvent(self, evt): + def mouseDragEvent(self, evt: TTkMouseEvent) -> bool: if not self._selected: return False pns = self._panels for loc in self._selected: @@ -279,7 +369,7 @@ class TTkAppTemplate(TTkContainer): self._updateGeometries() return True - def minimumWidth(self): + def minimumWidth(self) -> int: pns = self._panels # Header and Footer sizes @@ -301,7 +391,7 @@ class TTkAppTemplate(TTkContainer): return max(mh, mf, mcr+mcl+max(mct, mcb, mcm)) + (2 if p.border else 0) - def maximumWidth(self): + def maximumWidth(self) -> int: pns = self._panels # Header and Footer sizes @@ -323,7 +413,7 @@ class TTkAppTemplate(TTkContainer): return min(mh, mf, mcr+mcl+min(mct, mcb, mcm)) + (2 if p.border else 0) - def minimumHeight(self): + def minimumHeight(self) -> int: pns = self._panels # Retrieve all the panels parameters and hide the menubar if required @@ -352,7 +442,7 @@ class TTkAppTemplate(TTkContainer): return mh+mf+max(mr ,ml, mm+mt+mb ) + ( 2 if bm else 0 ) - def maximumHeight(self): + def maximumHeight(self) -> int: pns = self._panels # Retrieve all the panels parameters and hide the menubar if required @@ -386,7 +476,7 @@ class TTkAppTemplate(TTkContainer): return mh+mf+min(mr ,ml, mm+mt+mb ) + ( 2 if bm else 0 ) - def _updateGeometries(self, force=False): + def _updateGeometries(self, force:bool=False) -> None: w,h = self.size() if w<=0 or h<=0 or (not force and not self.isVisibleAndParent()): return pns = self._panels @@ -513,7 +603,7 @@ class TTkAppTemplate(TTkContainer): self.update() - def update(self, repaint: bool =True, updateLayout: bool =False, updateParent: bool =False): + def update(self, repaint: bool =True, updateLayout: bool =False, updateParent: bool =False) -> None: if updateLayout: self._updateGeometries() super().update(repaint=repaint,updateLayout=updateLayout,updateParent=updateParent) @@ -524,7 +614,7 @@ class TTkAppTemplate(TTkContainer): #def setLayout(self, layout): # self._panels[TTkAppTemplate.MAIN].item = layout - def paintEvent(self, canvas: TTkCanvas): + def paintEvent(self, canvas: TTkCanvas) -> None: w,h = self.size() pns = self._panels spl = self._splitters diff --git a/TermTk/TTkWidgets/button.py b/TermTk/TTkWidgets/button.py index 9853b8d7..dbc0495a 100644 --- a/TermTk/TTkWidgets/button.py +++ b/TermTk/TTkWidgets/button.py @@ -27,6 +27,9 @@ from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSignal from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.canvas import TTkCanvas +from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent +from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent from TermTk.TTkWidgets.widget import TTkWidget class TTkButton(TTkWidget): @@ -144,17 +147,21 @@ class TTkButton(TTkWidget): self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) - def border(self): + def border(self) -> bool: + ''' This property holds whether the button has a border + + :return: bool + ''' return self._border - def isCheckable(self): + def isCheckable(self) -> bool: ''' This property holds whether the button is checkable :return: bool ''' return self._checkable - def setCheckable(self, ch): + def setCheckable(self, ch:bool) -> None: ''' Enable/Disable the checkable property :param ch: Checkable @@ -163,7 +170,7 @@ class TTkButton(TTkWidget): self._checkable = ch self.update() - def isChecked(self): + def isChecked(self) -> bool: ''' This property holds whether the button is checked Only checkable buttons can be checked. By default, the button is unchecked. @@ -172,7 +179,7 @@ class TTkButton(TTkWidget): ''' return self._checked - def setChecked(self, ch): + def setChecked(self, ch:bool) -> None: ''' Set the checked status :param ch: Checked @@ -182,14 +189,14 @@ class TTkButton(TTkWidget): self.toggled.emit(self._checked) self.update() - def text(self): + def text(self) -> TTkString: ''' This property holds the text shown on the button :return: :py:class:`TTkString` ''' return TTkString('\n').join(self._text) - def setText(self, text): + def setText(self, text:TTkString) -> None: ''' This property holds the text shown on the button :param text: @@ -205,12 +212,13 @@ class TTkButton(TTkWidget): self.setMaximumHeight(len(self._text)) self.update() - def mousePressEvent(self, evt): + + def mousePressEvent(self, evt: TTkMouseEvent) -> bool: # TTkLog.debug(f"{self._text} Test Mouse {evt}") self.update() return True - def mouseReleaseEvent(self, evt): + def mouseReleaseEvent(self, evt: TTkMouseEvent) -> bool: # TTkLog.debug(f"{self._text} Test Mouse {evt}") if self._checkable: self._checked = not self._checked @@ -219,7 +227,7 @@ class TTkButton(TTkWidget): self.clicked.emit() return True - def keyEvent(self, evt): + def keyEvent(self, evt: TTkKeyEvent) -> bool: if ( evt.type == TTkK.Character and evt.key==" " ) or \ ( evt.type == TTkK.SpecialKey and evt.key == TTkK.Key_Enter ): if self._checkable: @@ -230,7 +238,7 @@ class TTkButton(TTkWidget): return True return False - def paintEvent(self, canvas): + def paintEvent(self, canvas: TTkCanvas) -> None: if self.isEnabled() and self._checkable: if self._checked: style = self.style()['checked'] diff --git a/TermTk/TTkWidgets/checkbox.py b/TermTk/TTkWidgets/checkbox.py index bfe34167..68d7e135 100644 --- a/TermTk/TTkWidgets/checkbox.py +++ b/TermTk/TTkWidgets/checkbox.py @@ -25,8 +25,11 @@ __all__ = ['TTkCheckbox'] from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.canvas import TTkCanvas from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot +from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent +from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent from TermTk.TTkWidgets.widget import * class TTkCheckbox(TTkWidget): @@ -112,7 +115,7 @@ class TTkCheckbox(TTkWidget): self.setMaximumHeight(1) self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) - def text(self): + def text(self) -> TTkString: ''' This property holds the text shown on the checkhox :return: :py:class:`TTkString` @@ -120,7 +123,7 @@ class TTkCheckbox(TTkWidget): return self._text pyTTkSlot(str) - def setText(self, text): + def setText(self, text:TTkString) -> None: ''' This property holds the text shown on the checkhox :param text: @@ -131,14 +134,14 @@ class TTkCheckbox(TTkWidget): self.setMinimumSize(3 + len(self._text), 1) self.update() - def isTristate(self): + def isTristate(self) -> bool: ''' This property holds whether the checkbox is a tri-state checkbox :return: bool ''' return self._tristate - def setTristate(self, tristate): + def setTristate(self, tristate:bool): ''' Enable/Disable the tristate property :param tristate: @@ -148,7 +151,7 @@ class TTkCheckbox(TTkWidget): self._tristate = tristate self.update() - def isChecked(self): + def isChecked(self) -> bool: ''' This property holds whether the checkbox is checked :return: bool - True if :py:class:`~TermTk.TTkCore.constant.TTkConstant.CheckState.Checked` or :py:class:`~TermTk.TTkCore.constant.TTkConstant.CheckState.PartiallyChecked` @@ -156,7 +159,7 @@ class TTkCheckbox(TTkWidget): return self._checkStatus != TTkK.Unchecked @pyTTkSlot(bool) - def setChecked(self, state): + def setChecked(self, state:bool) -> None: ''' Set the check status :param state: @@ -164,7 +167,7 @@ class TTkCheckbox(TTkWidget): ''' self.setCheckState(TTkK.Checked if state else TTkK.Unchecked) - def checkState(self): + def checkState(self) -> TTkK.CheckState: ''' Retrieve the state of the checkbox :return: :py:class:`TTkConstant.CheckState` : the checkbox status @@ -172,7 +175,7 @@ class TTkCheckbox(TTkWidget): return self._checkStatus @pyTTkSlot(TTkK.CheckState) - def setCheckState(self, state): + def setCheckState(self, state:TTkK.CheckState) -> None: ''' Sets the checkbox's check state. :param state: state of the checkbox @@ -183,7 +186,7 @@ class TTkCheckbox(TTkWidget): self._checkStatus = state self.update() - def paintEvent(self, canvas): + def paintEvent(self, canvas: TTkCanvas) -> None: style = self.currentStyle() borderColor = style['borderColor'] @@ -198,7 +201,7 @@ class TTkCheckbox(TTkWidget): TTkK.PartiallyChecked: "/"}.get(self._checkStatus, " ") canvas.drawText(pos=(1,0), color=xColor ,text=text) - def _pressEvent(self): + def _pressEvent(self) -> bool: self._checkStatus = { TTkK.Unchecked: TTkK.PartiallyChecked, TTkK.PartiallyChecked: TTkK.Checked, @@ -212,11 +215,11 @@ class TTkCheckbox(TTkWidget): self.update() return True - def mousePressEvent(self, evt): + def mousePressEvent(self, evt: TTkMouseEvent) -> bool: self._pressEvent() return True - def keyEvent(self, evt): + def keyEvent(self, evt: TTkKeyEvent) -> bool: if ( evt.type == TTkK.Character and evt.key==" " ) or \ ( evt.type == TTkK.SpecialKey and evt.key == TTkK.Key_Enter ): self._pressEvent() diff --git a/TermTk/TTkWidgets/combobox.py b/TermTk/TTkWidgets/combobox.py index adb901e4..f80ea65d 100644 --- a/TermTk/TTkWidgets/combobox.py +++ b/TermTk/TTkWidgets/combobox.py @@ -29,6 +29,9 @@ from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.canvas import TTkCanvas +from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent +from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent from TermTk.TTkLayouts.gridlayout import TTkGridLayout from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkWidgets.container import TTkContainer @@ -92,7 +95,7 @@ class TTkComboBox(TTkContainer): self.setMinimumSize(5, 1) self.setMaximumHeight(1) - def _lineEditChanged(self): + def _lineEditChanged(self) -> None: text = self._lineEdit.text() self._id=-1 if text in self._list: @@ -119,14 +122,14 @@ class TTkComboBox(TTkContainer): self.currentTextChanged.emit(text) self.editTextChanged.emit(text) - def textAlign(self): + def textAlign(self) -> TTkK.Alignment: '''his property holds the displayed text alignment :return: :py:class:`TTkConstant.Alignment` ''' return self._textAlign - def setTextAlign(self, align): + def setTextAlign(self, align:TTkK.Alignment) -> None: '''This property holds the displayed text alignment :param align: @@ -136,35 +139,50 @@ class TTkComboBox(TTkContainer): self._textAlign = align self.update() - def addItem(self, text, userData=None): - '''addItem + def addItem(self, text:TTkString): + ''' + Adds an item to the combobox with the given text. + + The item is appended to the list of existing items. - Adds an item to the combobox with the given text, and containing the specified userData (stored in the Qt::UserRole). The item is appended to the list of existing items. + :param text: + :type text: :py:class:`TTkString` ''' self._list.append(text) self.update() - def addItems(self,items): - '''addItems''' + def addItems(self, items:list[TTkString]) -> None: + ''' + Adds a list of items to the combobox with the given text. + + The items are appended to the list of existing items. + + :param items: + :type items: list[:py:class:`TTkString`] + ''' for item in items: self.addItem(item) pyTTkSlot() - def clear(self): - '''clear''' + def clear(self) -> None: + '''Remove all the items.''' self._lineEdit.setText("") self._list = [] self._id = -1 self.update() - def lineEdit(self): + def lineEdit(self) -> TTkLineEdit|None: + ''' + Returns the :py:class:`TTkLineEdit` widget used if the editable flag is enabled + + :return: :py:class:`TTkLineEdit` + ''' return self._lineEdit if self._editable else None - def resizeEvent(self, w: int, h: int): - w,h = self.size() - self._lineEdit.setGeometry(1,0,w-4,h) + def resizeEvent(self, width: int, height: int) -> None: + self._lineEdit.setGeometry(1,0,width-4,height) - def paintEvent(self, canvas): + def paintEvent(self, canvas: TTkCanvas) -> None: style = self.currentStyle() color = style['color'] @@ -183,21 +201,34 @@ class TTkComboBox(TTkContainer): else: canvas.drawText(pos=(w-2,0), text="^]", color=borderColor) - def currentText(self): - '''currentText''' + def currentText(self) -> TTkString: + ''' + Returns the selected text + + :return: :py:class:`TTkString` + ''' if self._editable: return self._lineEdit.text() elif self._id >= 0: return self._list[self._id] return "" - def currentIndex(self): - '''currentIndex''' + def currentIndex(self) -> int: + ''' + Return the current seleccted index. + + :return: int + ''' return self._id @pyTTkSlot(int) - def setCurrentIndex(self, index): - '''setCurrentIndex''' + def setCurrentIndex(self, index:int) -> None: + ''' + Set the selected index + + :param index: + :type index: int + ''' if index < 0: return if index > len(self._list)-1: return if self._id == index: return @@ -210,8 +241,13 @@ class TTkComboBox(TTkContainer): self.update() @pyTTkSlot(str) - def setCurrentText(self, text): - '''setCurrentText''' + def setCurrentText(self, text:TTkString) -> None: + ''' + Set the selected Text + + :param text: + :type text: :py:class:`TTkString` + ''' if self._editable: self.setEditText(text) else: @@ -222,25 +258,48 @@ class TTkComboBox(TTkContainer): self.setCurrentIndex(id) @pyTTkSlot(str) - def setEditText(self, text): - '''setEditText''' + def setEditText(self, text:TTkString) -> None: + ''' + Set the text in the :py:class:`TTkLineEdit` widget + + :param text: + :type text: :py:class:`TTkString` + ''' if self._editable: self._lineEdit.setText(text) - def insertPolicy(self): - '''insertPolicy''' + def insertPolicy(self) -> TTkK.InsertPolicy: + ''' + Retrieve the insert policy used when a new item is added if the combobox editable flag is true. + + :return: :py:class:`TTkK.InsertPolicy` + ''' return self._insertPolicy - def setInsertPolicy(self, ip): - '''setInsertPolicy''' - self._insertPolicy = ip + def setInsertPolicy(self, policy:TTkK.InsertPolicy) -> None: + ''' + Define the insert policy used when a new item is inserted when the widget is editable. + + :param policy: + :type policy: :py:class:`TTkK.InsertPolicy` + ''' + self._insertPolicy = policy + + def isEditable(self) -> bool: + ''' + This field holds the editable status of this widget. - def isEditable(self): - '''isEditable''' + :return: bool + ''' return self._editable - def setEditable(self, editable): - '''setEditable''' + def setEditable(self, editable:bool) -> None: + ''' + Set the editable status of this widget. + + :param editable: + :type editable: bool + ''' self._editable = editable if editable: self._lineEdit.show() @@ -250,7 +309,7 @@ class TTkComboBox(TTkContainer): self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) @pyTTkSlot(str) - def _callback(self, label): + def _callback(self, label:TTkString) -> None: if self._editable: self._lineEdit.setText(label) self.setCurrentIndex(self._list.index(label)) @@ -259,7 +318,7 @@ class TTkComboBox(TTkContainer): self.setFocus() self.update() - def _pressEvent(self): + def _pressEvent(self) -> bool: frameHeight = len(self._list) + 2 frameWidth = self.width() if frameHeight > 20: frameHeight = 20 @@ -278,24 +337,24 @@ class TTkComboBox(TTkContainer): self.update() return True - def wheelEvent(self, evt): + def wheelEvent(self, evt: TTkMouseEvent) -> bool: if evt.evt == TTkK.WHEEL_Up: self.setCurrentIndex(self._id-1) else: self.setCurrentIndex(self._id+1) return True - def mousePressEvent(self, evt): + def mousePressEvent(self, evt: TTkMouseEvent) -> bool: self._pressEvent() return True - def keyEvent(self, evt): + def keyEvent(self, evt: TTkKeyEvent) -> bool: if ( evt.type == TTkK.Character and evt.key==" " ) or \ ( evt.type == TTkK.SpecialKey and evt.key in [TTkK.Key_Enter,TTkK.Key_Down] ): self._pressEvent() return True return False - def focusInEvent(self): + def focusInEvent(self) -> None: if self._editable: self._lineEdit.setFocus() diff --git a/TermTk/TTkWidgets/container.py b/TermTk/TTkWidgets/container.py index ae7f8f6b..b54d8137 100644 --- a/TermTk/TTkWidgets/container.py +++ b/TermTk/TTkWidgets/container.py @@ -20,44 +20,99 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -__all__ = ['TTkContainer'] +__all__ = ['TTkContainer', 'TTkPadding'] + +from typing import NamedTuple from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot +from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent from TermTk.TTkLayouts.layout import TTkLayout from TermTk.TTkWidgets.widget import TTkWidget +class TTkPadding(NamedTuple): + top : int = 0 + bottom : int = 0 + left : int = 0 + right : int = 0 + class TTkContainer(TTkWidget): - ''' TTkContainer Layout sizes: + ''' + :py:class:`TTkContainer` include the layout management through :py:class:`TTkLayout` or one of its implementations. + + It is the base class of all the composite widgets like :py:class:`TTkSpinBox` + where a :py:class:`TTkLineEdit` is placed and shown when edited, or in the :py:class:`TTkFrame` and + its extension :py:class:`TTkWindow` where the :py:class:`TTkLayout` is used for the child + widgets placements or the :py:class:`TTkMenuBarLayout`, + the main root widget :py:class:`TTk` inherit the layout capabilities of :py:class:`TTkContainer` for any widget placed in the `pyTermTk `__ app. + + Main widgets subclassing :py:class:`TTkContainer`: + + #. :py:class:`TTk` + #. :py:class:`TTkFrame` + #. :py:class:`TTkWindow` + #. :py:class:`TTkSplitter` + #. :py:class:`TTkAppTemplate` + #. :py:class:`TTkTabWidget` + #. :py:class:`TTkTreeWidget` + + More info and examples are available in the :ref:`Tutorial ` + + .. _Container-Layout-Topology: + + :py:class:`TTkContainer` Layout topology: :: Terminal area (i.e. XTerm) = TTk - ┌─────────────────────────────────────────┐ - │ │ - │ TTkContainer width │ - │ (x,y)┌─────────────────────────┐ │ - │ │ padt (Top Padding) │ │ - │ │ ┌───────────────┐ │ height │ - │ │padl│ Layout/child │padr│ │ - │ │ └───────────────┘ │ │ - │ │ padb (Bottom Pad.) │ │ - │ └─────────────────────────┘ │ - └─────────────────────────────────────────┘ - - :param bool forwardStyle: any change of style will reflect the children, defaults to False - :type forwardStyle: bool + ┌────────────────────────────────────────────┐ + │ │ + │ TTkContainer │ + │ < -------- width -----> │ + │ (x,y)╔═════════════════════════╗ ^ │ + │ ║ padt (Top Padding) ║ | │ + │ ║ ┌───────────────┐ ║ | height │ + │ ║padl│ Layout │padr║ | │ + │ ║ └───────────────┘ ║ | │ + │ ║ padb (Bottom Pad.) ║ | │ + │ ╚═════════════════════════╝ v │ + └────────────────────────────────────────────┘ + + Example: + + .. code-block:: python - :param int padding: the padding (top, bottom, left, right) of the widget, defaults to 0 - :param int paddingTop: the Top padding, override Top padding if already defined, optional, default=padding - :param int paddingBottom: the Bottom padding, override Bottom padding if already defined, optional, default=padding - :param int paddingLeft: the Left padding, override Left padding if already defined, optional, default=padding - :param int paddingRight: the Right padding, override Right padding if already defined, optional, default=padding + from TermTk import TTk, TTkButton, TTkHBoxLayout + + # The class TTk, that could be referred as the terminal layout, is a subclass of TTkContainer + root = TTk( layout=TTkHBoxLayout(), padding=(1,2,4,8)) + + TTkButton(parent=root, border=True, text="Btn 1!") + TTkButton(parent=root, border=True, text="Btn 2!") + TTkButton(parent=root, border=True, text="Btn 3!") + TTkButton(parent=root, border=True, text="Btn 4!") + + root.mainloop() :param layout: the layout of this widget, optional, defaults to :py:class:`TTkLayout` :type layout: :mod:`TermTk.TTkLayouts` + + :param padding: the padding (top, bottom, left, right) of the widget, defaults to (0,0,0,0) + :type padding: :py:class:`TTkPadding` + + :param paddingTop: the Top padding, override Top padding if already defined, optional, default=0 if padding is not defined + :type paddingTop: int + :param paddingBottom: the Bottom padding, override Bottom padding if already defined, optional, default=0 if padding is not defined + :type paddingBottom: int + :param paddingLeft: the Left padding, override Left padding if already defined, optional, default=0 if padding is not defined + :type paddingLeft: int + :param paddingRight: the Right padding, override Right padding if already defined, optional, default=0 if padding is not defined + :type paddingRight: int + + :param bool forwardStyle: [**Experimental**] any change of style will reflect the children, defaults to False + :type forwardStyle: bool ''' __slots__ = ( @@ -65,29 +120,28 @@ class TTkContainer(TTkWidget): '_forwardStyle', '_layout') - def __init__(self, *, padding=(0,0,0,0), forwardStyle=False,**kwargs): + def __init__(self, *, layout:TTkLayout=None, padding:TTkPadding=(0,0,0,0), forwardStyle=False, **kwargs): self._forwardStyle = forwardStyle - padding = kwargs.get('padding', 0 ) - self._padt = kwargs.get('paddingTop', padding ) - self._padb = kwargs.get('paddingBottom', padding ) - self._padl = kwargs.get('paddingLeft', padding ) - self._padr = kwargs.get('paddingRight', padding ) + self._padt = kwargs.get('paddingTop', padding[0] ) + self._padb = kwargs.get('paddingBottom', padding[1] ) + self._padl = kwargs.get('paddingLeft', padding[2] ) + self._padr = kwargs.get('paddingRight', padding[3] ) self._layout = TTkLayout() # root layout - self._layout.addItem(kwargs.get('layout',TTkLayout())) # main layout + self._layout.addItem(layout if layout else TTkLayout()) # main layout super().__init__(**kwargs) self._layout.setParent(self) self.update(updateLayout=True) - def addWidget(self, widget): + def addWidget(self, widget:TTkWidget) -> None: ''' .. warning:: Method Deprecated, - use :py:class:`TTkWidget` -> :py:class:`TTkWidget.layout` -> :py:class:`TTkLayout.addWidget` + use :py:class:`TTkContainer` -> :py:meth:`layout` -> :py:class:`TTkLayout.addWidget` i.e. @@ -98,12 +152,12 @@ class TTkContainer(TTkWidget): TTkLog.error(".addWidget(...) is deprecated, use .layout().addWidget(...)") if self.layout(): self.layout().addWidget(widget) - def removeWidget(self, widget): + def removeWidget(self, widget:TTkWidget) -> None: ''' .. warning:: Method Deprecated, - use :py:class:`TTkWidget` -> :py:class:`TTkWidget.layout` -> :py:class:`TTkLayout.removeWidget` + use :py:class:`TTkContainer` -> :py:meth:`layout` -> :py:class:`TTkLayout.removeWidget` i.e. @@ -118,7 +172,7 @@ class TTkContainer(TTkWidget): # widget._currentStyle |= self._currentStyle # widget.update() - def _processForwardStyle(self): + def _processForwardStyle(self) -> None: if not self._forwardStyle: return def _getChildren(): for w in self.rootLayout().iterWidgets(onlyVisible=True, recurse=False): @@ -131,7 +185,7 @@ class TTkContainer(TTkWidget): if issubclass(type(w),TTkContainer): w._processForwardStyle() - def setCurrentStyle(self, *args, **kwargs): + def setCurrentStyle(self, *args, **kwargs) -> None: super().setCurrentStyle(*args, **kwargs) self._processForwardStyle() @@ -167,19 +221,19 @@ class TTkContainer(TTkWidget): bh = min(iy+ih,ly+lh)-by TTkContainer._paintChildCanvas(canvas, child, (bx,by,bw,bh), (ix+iox,iy+ioy)) - def paintChildCanvas(self): + def paintChildCanvas(self) -> None: ''' .. caution:: Don't touch this! ''' TTkContainer._paintChildCanvas(self._canvas, self.rootLayout(), self.rootLayout().geometry(), self.rootLayout().offset()) - def getPadding(self) -> (int, int, int, int): - ''' Retrieve the widget padding sizes + def getPadding(self) -> TTkPadding: + ''' Retrieve the :py:class:`TTkContainer`'s paddings sizes as shown in :ref:`Layout Topology ` - :return: list[top, bottom, left, right]: the 4 padding sizes + :return: :py:class:`TTkPadding` the NamedTuple representing a 4 int values tuple (top,bottom,left,right) ''' - return self._padt, self._padb, self._padl, self._padr + return TTkPadding(self._padt, self._padb, self._padl, self._padr) - def setPadding(self, top: int, bottom: int, left: int, right: int): - ''' Set the padding of the widget + def setPadding(self, top: int, bottom: int, left: int, right: int) -> None: + ''' Set the :py:class:`TTkContainer`'s paddings sizes as shown in :ref:`Layout Topology ` :param int top: top padding :param int bottom: bottom padding @@ -226,7 +280,7 @@ class TTkContainer(TTkWidget): _mouseOver = None _mouseOverTmp = None _mouseOverProcessed = False - def mouseEvent(self, evt): + def mouseEvent(self, evt: TTkMouseEvent) -> bool: ''' .. caution:: Don't touch this! ''' if not self._enabled: return False @@ -325,29 +379,44 @@ class TTkContainer(TTkWidget): return False - def setLayout(self, layout): + def setLayout(self, layout:TTkLayout) -> None: + ''' + Set the :ref:`Layout ` used by this widget to place all the child widgets. + + :param layout: + :type layout: :py:class:`TTkLayout` + ''' self._layout.replaceItem(layout, 0) #self.layout().setParent(self) self.update(repaint=True, updateLayout=True) - def layout(self): - ''' Get the layout + def layout(self) -> TTkLayout: + ''' + Get the :ref:`Layout ` - :return: The layout used + :return: The :ref:`Layout ` used :rtype: :py:class:`TTkLayout` or derived ''' return self._layout.itemAt(0) - def rootLayout(self): return self._layout + def rootLayout(self) -> TTkLayout: + ''' + This is a root layout mainly used to place items that are not supposed to be inside the main layout (i.e. the menu elements) + + .. caution:: Use this layout only if you know what you are doing + + :return: :py:class:``TTkLayout` + ''' + return self._layout - def maximumHeight(self): + def maximumHeight(self) -> int: wMaxH = self._maxh if self.layout() is not None: lMaxH = self.layout().maximumHeight() + self._padt + self._padb if lMaxH < wMaxH: return lMaxH return wMaxH - def maximumWidth(self): + def maximumWidth(self) -> int: wMaxW = self._maxw if self.layout() is not None: lMaxW = self.layout().maximumWidth() + self._padl + self._padr @@ -355,21 +424,14 @@ class TTkContainer(TTkWidget): return lMaxW return wMaxW - def minimumSize(self): - return self.minimumWidth(), self.minimumHeight() - def minDimension(self, orientation) -> int: - if orientation == TTkK.HORIZONTAL: - return self.minimumWidth() - else: - return self.minimumHeight() - def minimumHeight(self): + def minimumHeight(self) -> int: wMinH = self._minh if self.layout() is not None: lMinH = self.layout().minimumHeight() + self._padt + self._padb if lMinH > wMinH: return lMinH return wMinH - def minimumWidth(self): + def minimumWidth(self) -> int: wMinW = self._minw if self.layout() is not None: lMinW = self.layout().minimumWidth() + self._padl + self._padr @@ -378,8 +440,7 @@ class TTkContainer(TTkWidget): return wMinW @pyTTkSlot() - def show(self): - '''show''' + def show(self) -> None: if self._visible: return self._visible = True self._canvas.show() @@ -388,14 +449,13 @@ class TTkContainer(TTkWidget): w.update() @pyTTkSlot() - def hide(self): - '''hide''' + def hide(self) -> None: if not self._visible: return self._visible = False self._canvas.hide() self.update(repaint=False, updateParent=True) - def update(self, repaint: bool =True, updateLayout: bool =False, updateParent: bool =False): + def update(self, repaint: bool = True, updateLayout: bool = False, updateParent: bool = False) -> None: super().update(repaint=repaint, updateLayout=updateLayout, updateParent=updateParent) if updateLayout and self.rootLayout() is not None and self.size() != (0,0): self.rootLayout().setGeometry(0,0,self._width,self._height) @@ -405,7 +465,15 @@ class TTkContainer(TTkWidget): self._height - self._padt - self._padb) self.rootLayout().update() - def getWidgetByName(self, name: str): + def getWidgetByName(self, name: str) -> TTkWidget: + ''' + Return the widget from its name. + + :param name: the widget's name, normally defined in the constructor or through :py:meth:`TTkWidget.setName` + :type name: + + :return: :py:class:`TTkWidget` + ''' if name == self._name: return self for w in self.rootLayout().iterWidgets(onlyVisible=False, recurse=True): diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index bb981a55..5f60aa87 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -224,40 +224,76 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): '''name''' return self._name - def setName(self, name:str): - '''setName''' + def setName(self, name:str) -> None: + ''' + Set the name of this Instance + + :param name: the name to be set + :type name: str + ''' self._name = name - def widgetItem(self): return self._widgetItem + def widgetItem(self) -> TTkWidgetItem: + return self._widgetItem - def paintEvent(self, canvas:TTkCanvas): + def paintEvent(self, canvas:TTkCanvas) -> None: ''' Paint Event callback, this need to be overridden in the widget. + + .. note:: Override this method to handle this event + + :param canvas: the canvas where the content need to be drawn + :type canvas: :py:class:`TTkCanvas` ''' pass def getPixmap(self) -> TTkCanvas: + ''' + Convenience function which return a pixmap representing the current widget status + + :return: :py:class:`TTkCanvas` + ''' self.paintEvent(self._canvas) self.paintChildCanvas() return self._canvas.copy() @staticmethod - def _paintChildCanvas(canvas, item, geometry, offset): + def _paintChildCanvas(canvas, item, geometry, offset) -> None: pass - def paintChildCanvas(self): + def paintChildCanvas(self) -> None: pass - def moveEvent(self, x: int, y: int): - ''' Event Callback triggered after a successful move''' + def moveEvent(self, x: int, y: int) -> None: + ''' + Convenience function, + Event Callback triggered after a successful move + + .. note:: Override this method to handle this event + + :param x: the new horizontal position + :type x: int + :param y: the new vertical position + :type y: int + ''' pass - @pyTTkSlot(int,int) - def resizeEvent(self, w: int, h: int): - ''' Event Callback triggered after a successful resize''' + + def resizeEvent(self, width: int, height: int) -> None: + ''' + Convenience function, + Event Callback triggered after a successful resize + + .. note:: Override this method to handle this event + + :param width: the new width + :type width: int + :param height: the new height + :type height: int + ''' pass - def setDefaultSize(self, arg, width: int, height: int): + def setDefaultSize(self, arg, width: int, height: int) -> None: if ( 'size' in arg or 'width' in arg or 'height' in arg ): @@ -265,11 +301,13 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): arg['width'] = width arg['height'] = height - def move(self, x: int, y: int): + def move(self, x: int, y: int) -> None: ''' Move the widget - :param int x: x position - :param int y: y position + :param x: the horizontal position + :type x: int + :param y: the vertical position + :type y: int ''' if x==self._x and y==self._y: return self._x = x @@ -277,40 +315,54 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self.update(repaint=False, updateLayout=False) self.moveEvent(x,y) - def resize(self, w: int, h: int): + def resize(self, width: int, height: int) -> None: ''' Resize the widget - :param int w: the new width - :param int h: the new height + :param width: the new width + :type width: int + :param height: the new height + :type height: int ''' # TTkLog.debug(f"resize: {w,h} {self._name}") - if w!=self._width or h!=self._height: - self._width = w - self._height = h + if width!=self._width or height!=self._height: + self._width = width + self._height = height self._canvas.resize(self._width, self._height) self.update(repaint=True, updateLayout=True) - self.resizeEvent(w,h) - self.sizeChanged.emit(w,h) + self.resizeEvent(width,height) + self.sizeChanged.emit(width,height) - def setGeometry(self, x: int, y: int, w: int, h: int): + def setGeometry(self, x: int, y: int, width: int, height: int): ''' Resize and move the widget - :param int x: x position - :param int y: y position - :param int w: the new width - :param int h: the new height + :param x: the horizontal position + :type x: int + :param y: the vertical position + :type y: int + :param width: the new width + :type width: int + :param height: the new height + :type height: int ''' - self.resize(w, h) + self.resize(width, height) self.move(x, y) - def pasteEvent(self, txt:str): + def pasteEvent(self, txt:str) -> None: + ''' + Callback triggered when a paste event is forwarded to this widget. + + .. note:: Reimplement this function to handle this event + + :param txt: the paste object + :type txt: str + ''' return False _mouseOver = None _mouseOverTmp = None _mouseOverProcessed = False - def mouseEvent(self, evt): - ''' .. caution:: Don't touch this! ''' + def mouseEvent(self, evt:TTkMouseEvent) -> bool: + ''' .. caution:: Don Not touch this! ''' if not self._enabled: return False # Saving self in this global variable @@ -408,42 +460,42 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): return False - def setParent(self, parent): + def setParent(self, parent) -> None: self._parent = parent def parentWidget(self): return self._parent - def x(self): return self._x - def y(self): return self._y - def width(self): return self._width - def height(self): return self._height + def x(self) -> int: return self._x + def y(self) -> int: return self._y + def width(self) -> int: return self._width + def height(self) -> int: return self._height - def pos(self): return self._x, self._y - def size(self): return self._width, self._height - def geometry(self): return self._x, self._y, self._width, self._height + def pos(self) -> tuple[int,int]: return self._x, self._y + def size(self) -> tuple[int,int]: return self._width, self._height + def geometry(self) -> tuple[int,int,int,int]: return self._x, self._y, self._width, self._height - def maximumSize(self): + def maximumSize(self) -> tuple[int,int]: return self.maximumWidth(), self.maximumHeight() - def maxDimension(self, orientation) -> int: + def maxDimension(self, orientation:TTkK.Direction) -> int: if orientation == TTkK.HORIZONTAL: return self.maximumWidth() else: return self.maximumHeight() - def maximumHeight(self): + def maximumHeight(self) -> int: return self._maxh - def maximumWidth(self): + def maximumWidth(self) -> int: return self._maxw - def minimumSize(self): + def minimumSize(self) -> tuple[int,int]: return self.minimumWidth(), self.minimumHeight() - def minDimension(self, orientation) -> int: + def minDimension(self, orientation:TTkK.Direction) -> int: if orientation == TTkK.HORIZONTAL: return self.minimumWidth() else: return self.minimumHeight() - def minimumHeight(self): + def minimumHeight(self) -> int: return self._minh - def minimumWidth(self): + def minimumWidth(self) -> int: return self._minw def setMaximumSize(self, maxw: int, maxh: int): @@ -471,24 +523,24 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self.update(updateLayout=True, updateParent=True) @pyTTkSlot() - def show(self): - '''show''' + def show(self) -> None: + '''show the widget''' if self._visible: return self._visible = True self._canvas.show() self.update(updateLayout=True, updateParent=True) @pyTTkSlot() - def hide(self): - '''hide''' + def hide(self) -> None: + '''hide the widget''' if not self._visible: return self._visible = False self._canvas.hide() self.update(repaint=False, updateParent=True) @pyTTkSlot() - def raiseWidget(self, raiseParent=True): - '''raiseWidget''' + def raiseWidget(self, raiseParent:bool=True) -> None: + '''Raise the Widget above its relatives''' if self._parent is not None and \ self._parent.rootLayout() is not None: if raiseParent: @@ -496,16 +548,16 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._parent.rootLayout().raiseWidget(self) @pyTTkSlot() - def lowerWidget(self): - '''lowerWidget''' + def lowerWidget(self) -> None: + '''Lower the Widget below its relatives''' if self._parent is not None and \ self._parent.rootLayout() is not None: self._parent.lowerWidget() self._parent.rootLayout().lowerWidget(self) @pyTTkSlot() - def close(self): - '''close''' + def close(self) -> None: + '''Close (Destroy/Remove) the widget''' if _p := self._parent: if _rl := _p.rootLayout(): _rl.removeWidget(self) @@ -516,20 +568,57 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self.closed.emit(self) @pyTTkSlot(bool) - def setVisible(self, visible: bool): - '''setVisible''' + def setVisible(self, visible: bool) -> None: + ''' + Set the visibility status of this widget + + :param visible: status + :type visible: bool: + ''' if visible: self.show() else: self.hide() - def isVisibleAndParent(self): + def isVisibleAndParent(self) -> bool: return ( self._visible and ( self._parent is not None ) and self._parent.isVisibleAndParent() ) - def isVisible(self): + def isVisible(self) -> bool: + ''' + Retrieve the visibility status of this widget + + :return: bool + ''' return self._visible - def update(self, repaint: bool =True, updateLayout: bool =False, updateParent: bool =False): + @pyTTkSlot() + def update(self, repaint: bool =True, updateLayout: bool =False, updateParent: bool =False) -> None: + ''' + Notify the drawing routine that the widget changed and needs to draw its new content. + + It is important to call this method anytime a canvas update is required after a a status update. + + Once :py:meth:`update` is called, the :py:meth:`paintEvent` is executed during the next screen refresh. + + i.e. + + .. code-block:: python + + class NewLabel(TTkWidget): + def __init__(self,**kwargs): + self.text = "" + super().__init__(**kwargs) + + def setText(self, text:str) -> None: + self.text = text + # Notify the runtime that un update + # is required will trigger the paintEvent + # at the next screen (terminal) refresh + self.update() + + def paintEvent(self, canvas:TTkCanvas) -> None: + canvas.drawText(pos=(0,0), text=self.text) + ''' if repaint: TTkHelper.addUpdateBuffer(self) TTkHelper.addUpdateWidget(self) @@ -537,8 +626,8 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._parent.update(updateLayout=True) @pyTTkSlot() - def setFocus(self): - '''setFocus''' + def setFocus(self) -> None: + '''Focus the widget''' # TTkLog.debug(f"setFocus: {self._name} - {self._focus}") if self._focus and self == TTkHelper.getFocus(): return tmp = TTkHelper.getFocus() @@ -553,7 +642,8 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._pushWidgetCursor() self._processStyleEvent(TTkWidget._S_DEFAULT) - def clearFocus(self): + def clearFocus(self) -> None: + '''Remove the Focus state of this widget''' # TTkLog.debug(f"clearFocus: {self._name} - {self._focus}") if not self._focus and self != TTkHelper.getFocus(): return TTkHelper.clearFocus() @@ -563,44 +653,96 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._processStyleEvent(TTkWidget._S_DEFAULT) self.update(repaint=True, updateLayout=False) - def hasFocus(self): + def hasFocus(self) -> bool: + ''' + This property holds the focus status of this widget + + :return: bool + ''' return self._focus def getCanvas(self) -> TTkCanvas: return self._canvas - def focusPolicy(self): + def focusPolicy(self) -> TTkK.FocusPolicy: return self._focus_policy - def setFocusPolicy(self, policy): + def setFocusPolicy(self, policy:TTkK.FocusPolicy) -> None: + ''' + This property holds the way the widget accepts keyboard focus + + The policy is :py:class:`TTkK.FocusPolicy.TabFocus` if the widget accepts keyboard focus by tabbing, + :py:class:`TTkK.FocusPolicy.ClickFocus` if the widget accepts focus by clicking, + :py:class:`TTkK.FocusPolicy.StrongFocus` if it accepts both, + and :py:class:`TTkK.FocusPolicy.NoFocus` (the default) if it does not accept focus at all. + + You must enable keyboard focus for a widget if it processes keyboard events. + This is normally done from the widget's constructor. For instance, + the :py:class:`TTkLineEdit` constructor calls :py:meth:`setFocusPolicy` with :py:class:`TTkK.FocusPolicy.StrongFocus`. + + If the widget has a focus proxy, then the focus policy will be propagated to it. + + :param policy: the focus policy + :type policy: :py:class:`TTkK.FocusPolicy` + ''' self._focus_policy = policy - def focusInEvent(self): pass - def focusOutEvent(self): pass + def focusInEvent(self) -> None: pass + def focusOutEvent(self) -> None: pass - def isEntered(self): + def isEntered(self) -> bool: return self._mouseOver == self - def isEnabled(self): + def isEnabled(self) -> bool: + ''' + This property holds whether the widget is enabled + + use :py:meth:`setEnabled` or :py:meth:`setDisabled` to change this property + + :return: bool + ''' return self._enabled @pyTTkSlot(bool) - def setEnabled(self, enabled: bool=True): - '''setEnabled''' + def setEnabled(self, enabled:bool=True) -> None: + ''' + This property holds whether the widget is enabled + + In general an enabled widget handles keyboard and mouse events; + a disabled widget does not. + + Some widgets display themselves differently when they are disabled. + For example a button might draw its label grayed out. + If your widget needs to know when it becomes enabled or disabled. + + Disabling a widget implicitly disables all its children. + Enabling respectively enables all child widgets unless they have been explicitly disabled. + + By default, this property is true. + + :param enabled: the enabled status, defaults to True + :type enabled: bool + ''' if self._enabled == enabled: return self._enabled = enabled self._processStyleEvent(TTkWidget._S_DEFAULT if enabled else TTkWidget._S_DISABLED) self.update() @pyTTkSlot(bool) - def setDisabled(self, disabled=True): - '''setDisabled''' + def setDisabled(self, disabled:bool=True) -> None: + '''This property holds whether the widget is disnabled + + This is a convenience function wrapped around :py:meth:`setEnabled` where (not disabled) is used + + :param disabled: the disabled status, defaults to True + :type disabled: bool + ''' self.setEnabled(not disabled) - def toolTip(self): + def toolTip(self) -> TTkString: return self._toolTip - def setToolTip(self, toolTip: TTkString): + def setToolTip(self, toolTip: TTkString) -> None: self._toolTip = TTkString(toolTip) def getWidgetByName(self, name: str): @@ -619,19 +761,19 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): _S_PRESSED = 0x20 _S_RELEASED = 0x40 - def style(self): + def style(self) -> dict: return self._style.copy() - def currentStyle(self): + def currentStyle(self) -> dict: return self._currentStyle - def setCurrentStyle(self, style): + def setCurrentStyle(self, style) -> dict: if style == self._currentStyle: return self._currentStyle = style self.currentStyleChanged.emit(style) self.update() - def setStyle(self, style=None): + def setStyle(self, style=None) -> None: if not style: style = self.classStyle.copy() if 'default' not in style: @@ -649,7 +791,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._style = mergeStyle self._processStyleEvent(TTkWidget._S_DEFAULT) - def mergeStyle(self, style): + def mergeStyle(self, style) -> None: cs = None # for field in style: # if field in self._style: @@ -664,7 +806,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): if cs: self.setCurrentStyle(self._style[cs]) - def _processStyleEvent(self, evt=_S_DEFAULT): + def _processStyleEvent(self, evt=_S_DEFAULT) -> bool: if not self._style: return False if not self._enabled and 'disabled' in self._style: self.setCurrentStyle(self._style['disabled']) @@ -694,15 +836,15 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): return False # Widget Cursor Helpers - def enableWidgetCursor(self, enable=True): + def enableWidgetCursor(self, enable:bool=True) -> None: self._widgetCursorEnabled = enable self._pushWidgetCursor() - def disableWidgetCursor(self, disable=True): + def disableWidgetCursor(self, disable:bool=True) -> None: self._widgetCursorEnabled = not disable self._pushWidgetCursor() - def setWidgetCursor(self, pos=None, type=None): + def setWidgetCursor(self, pos=None, type=None) -> None: self._widgetCursor = pos if pos else self._widgetCursor self._widgetCursorType = type if type else self._widgetCursorType self._pushWidgetCursor() diff --git a/docs/source/sphinx_modules/sphinx_ext_autosummary_reworked.py b/docs/source/sphinx_modules/sphinx_ext_autosummary_reworked.py index 6561248b..270e6d59 100644 --- a/docs/source/sphinx_modules/sphinx_ext_autosummary_reworked.py +++ b/docs/source/sphinx_modules/sphinx_ext_autosummary_reworked.py @@ -135,7 +135,12 @@ def setup(app: Sphinx) -> ExtensionMetadata: for cc in _obj.__mro__: if cc==_obj: continue # if hasattr(cc,'_ttkProperties'): - if issubclass(cc, ttk.TTkWidget) or issubclass(cc, ttk.TTkLayout): + if ( + issubclass(cc, ttk.TTkTemplates.dragevents.TDragEvents) or + issubclass(cc, ttk.TTkTemplates.mouseevents.TMouseEvents) or + issubclass(cc, ttk.TTkTemplates.keyevents.TKeyEvents) or + issubclass(cc, ttk.TTkWidget) or + issubclass(cc, ttk.TTkLayout) ) : ccName = cc.__name__ ret.append(ccName) # print(ccName) @@ -238,6 +243,7 @@ def setup(app: Sphinx) -> ExtensionMetadata: ttkSlots = [] ttkSlotsInherited = {} ttkMethods = [] + ttkMethodsInherited = {} ttkInheritedMethods = [] ttkSubClasses = modSorted.get(name,[]) ttkSubModules = modSubSorted.get(name,[]) @@ -249,6 +255,13 @@ def setup(app: Sphinx) -> ExtensionMetadata: ttkSlots, ttkSlotsInherited = _get_slots_in_obj(qualname) + def _get_methods_in_obj(_name): + _methodsInherited = {_sub : sorted(ttkAllMethods.get(_sub,[])) for _sub in ttkInherited.get(_name,[])} + _methods = set(ttkAllMethods.get(_name,[])) - set([_sl for _subSl in _methodsInherited.values() for _sl in _subSl]) + return sorted(list(_methods)), _methodsInherited + + ttkMethods, ttkInheritedMethods = _get_methods_in_obj(qualname) + def _get_attributes_in_obj(_obj,_name): _allMethods = ttkAllMethods.get(_name,[]) _slots = ttkAllSlots.get(_name,[]) @@ -270,15 +283,16 @@ def setup(app: Sphinx) -> ExtensionMetadata: ttkAttributes = sorted(set(_get_simple_attributes(obj)) - set(ttkAllSignals.get(qualname,''))) context |= { - 'TTkAttributes':ttkAttributes, - 'TTkClasses':_get_classes(obj,ttkMethods+ttkSlots+ttkAllSignals.get(qualname,[])), - 'TTkStyle':modStyles.get(qualname,''), - 'TTkSignals':ttkAllSignals.get(qualname,''), - 'TTkSubClasses': ttkSubClasses, - 'TTkSubModules': ttkSubModules, - 'TTkMethods':ttkAllMethods.get(qualname,''), - 'TTkSlots':ttkSlots, - 'TTkSlotsInherited':ttkSlotsInherited, + 'TTkAttributes': sorted( ttkAttributes ), + 'TTkClasses': sorted( _get_classes(obj,ttkMethods+ttkSlots+ttkAllSignals.get(qualname,[])) ), + 'TTkSignals': sorted( ttkAllSignals.get(qualname,'') ), + 'TTkSubClasses': sorted( ttkSubClasses ), + 'TTkSubModules': sorted( ttkSubModules ), + 'TTkSlots': sorted( ttkSlots ), + 'TTkMethods': sorted( ttkMethods ), + 'TTkStyle': modStyles.get(qualname,'') , + 'TTkSlotsInherited': ttkSlotsInherited , + 'TTkMethodsInherited': ttkInheritedMethods , } print('\n'.join([f" * {x}={context[x]}" for x in context])) diff --git a/docs/source/templates/custom-class-template.01.rst b/docs/source/templates/custom-class-template.01.rst index e5063d83..516dbcaa 100644 --- a/docs/source/templates/custom-class-template.01.rst +++ b/docs/source/templates/custom-class-template.01.rst @@ -1,7 +1,5 @@ {{ objname | escape | underline }} -Pippo CUSTOM_CLASS_TEMPLATE.001 - .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} @@ -45,16 +43,17 @@ Pippo CUSTOM_CLASS_TEMPLATE.001 {% if TTkSlotsInherited %} {% for name in TTkSlotsInherited %} - Inherited from: :py:class:`{{ name }}` + {% if TTkSlotsInherited[name] %} + Slots Inherited from: :py:class:`{{ name }}` .. autosummary:: {% for item in TTkSlotsInherited[name] %} {{ item }} - {%- endfor %} {%- endfor %} - + {% endif %} + {%- endfor %} {% endif %} {% if TTkSignals %} @@ -66,9 +65,12 @@ Pippo CUSTOM_CLASS_TEMPLATE.001 {%- endfor %} {% endif %} - {% if TTkMethods %} + {% if TTkMethods or TTkMethodsInherited %} Methods ------- + {% endif %} + + {% if TTkMethods %} {% for item in TTkMethods %} .. automethod:: {{ item }} @@ -76,6 +78,21 @@ Pippo CUSTOM_CLASS_TEMPLATE.001 {% endif %} + {% if TTkMethodsInherited %} + {% for name in TTkMethodsInherited %} + {% if TTkMethodsInherited[name] %} + Methods Inherited from: :py:class:`{{ name }}` + + .. autosummary:: + + {% for item in TTkMethodsInherited[name] %} + {{ item }} + + {%- endfor %} + {% endif %} + {%- endfor %} + {% endif %} + {% if TTkClasses %} diff --git a/tests/timeit/02.array.07.namedTuple.py b/tests/timeit/02.array.07.namedTuple.py new file mode 100755 index 00000000..fa67595b --- /dev/null +++ b/tests/timeit/02.array.07.namedTuple.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2024 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. + + +import os +import sys +import argparse +import operator +import json +import timeit +import random + +from typing import NamedTuple + + +class TTkPadding(NamedTuple): + top : int = 0 + bottom : int = 0 + left : int = 0 + right : int = 0 + +def test_ti_1_00(): + return len([[x,x,x,x] for x in range(1000)]) + +def test_ti_2_00(): + return len([(x,x,x,x) for x in range(1000)]) + +def test_ti_2_01(): + return len([(1,1,1,1) for x in range(1000)]) + +def test_ti_3_00(): + return len([TTkPadding(x,x,x,x) for x in range(1000)]) + + +loop = 500 + +a = {} + +for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") diff --git a/tutorial/002-layout.rst b/tutorial/002-layout.rst index 14b902b0..b6a4e73b 100644 --- a/tutorial/002-layout.rst +++ b/tutorial/002-layout.rst @@ -25,6 +25,8 @@ pyTermTk_ - Layouts ============================================================================= +.. _Layout-Tutorial_Intro: + Intro =====