diff --git a/TermTk/TTkAbstract/abstractscrollarea.py b/TermTk/TTkAbstract/abstractscrollarea.py index aca09aa1..310d50ea 100644 --- a/TermTk/TTkAbstract/abstractscrollarea.py +++ b/TermTk/TTkAbstract/abstractscrollarea.py @@ -24,11 +24,12 @@ from TermTk.TTkCore.constant import TTkK # from TermTk.TTkCore.log import TTkLog 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.gridlayout import TTkGridLayout from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollViewInterface -class TTkAbstractScrollArea(TTkWidget): +class TTkAbstractScrollArea(TTkContainer): __slots__ = ( '_processing', # this flag is required to avoid unnecessary loop on edge cases '_viewport', diff --git a/TermTk/TTkAbstract/abstractscrollview.py b/TermTk/TTkAbstract/abstractscrollview.py index d3f6831c..45bd65c2 100644 --- a/TermTk/TTkAbstract/abstractscrollview.py +++ b/TermTk/TTkAbstract/abstractscrollview.py @@ -26,6 +26,7 @@ from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkLayouts.gridlayout import TTkGridLayout class TTkAbstractScrollViewInterface(): @@ -44,7 +45,7 @@ class TTkAbstractScrollViewInterface(): def getViewOffsets(self): return self._viewOffsetX, self._viewOffsetY -class TTkAbstractScrollView(TTkWidget, TTkAbstractScrollViewInterface): +class TTkAbstractScrollView(TTkContainer, TTkAbstractScrollViewInterface): __slots__ = ( '_viewOffsetX', '_viewOffsetY', # Signals @@ -55,7 +56,7 @@ class TTkAbstractScrollView(TTkWidget, TTkAbstractScrollViewInterface): self.viewMovedTo = pyTTkSignal(int, int) # x, y self.viewSizeChanged = pyTTkSignal(int, int) # w, h self.viewChanged = pyTTkSignal() - TTkWidget.__init__(self, *args, **kwargs) + TTkContainer.__init__(self, *args, **kwargs) self._viewOffsetX = 0 self._viewOffsetY = 0 diff --git a/TermTk/TTkCore/ttk.py b/TermTk/TTkCore/ttk.py index caf2d8fc..d0deea23 100644 --- a/TermTk/TTkCore/ttk.py +++ b/TermTk/TTkCore/ttk.py @@ -42,8 +42,9 @@ from TermTk.TTkCore.timer import TTkTimer from TermTk.TTkCore.color import TTkColor from TermTk.TTkTheme.theme import TTkTheme from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer -class TTk(TTkWidget): +class TTk(TTkContainer): class _mouseCursor(TTkWidget): __slots__ = ('_cursor','_color') def __init__(self, input): diff --git a/TermTk/TTkLayouts/layout.py b/TermTk/TTkLayouts/layout.py index a34299f1..73d87abc 100644 --- a/TermTk/TTkLayouts/layout.py +++ b/TermTk/TTkLayouts/layout.py @@ -213,8 +213,8 @@ class TTkLayout(TTkLayoutItem): if child._layoutItemType == TTkK.WidgetItem: if onlyVisible and not child.widget().isVisible(): continue yield child.widget() - if recurse: - yield from child.widget().rootLayout().iterWidgets() + if recurse and hasattr(cw:=child.widget(),'rootLayout'): + yield from cw.rootLayout().iterWidgets() if child._layoutItemType == TTkK.LayoutItem and recurse: yield from child.iterWidgets() diff --git a/TermTk/TTkTestWidgets/__init__.py b/TermTk/TTkTestWidgets/__init__.py index 1aca0fd9..ece1caaf 100644 --- a/TermTk/TTkTestWidgets/__init__.py +++ b/TermTk/TTkTestWidgets/__init__.py @@ -3,4 +3,4 @@ from .testwidget import TTkTestWidget from .testwidgetsizes import TTkTestWidgetSizes from .testabstractscroll import TTkTestAbstractScrollWidget from .keypressview import TTkKeyPressView -from .tominspector import TTkTomInspector +# from .tominspector import TTkTomInspector diff --git a/TermTk/TTkTestWidgets/tominspector.py b/TermTk/TTkTestWidgets/tominspector.py index 15354238..01ee94ff 100644 --- a/TermTk/TTkTestWidgets/tominspector.py +++ b/TermTk/TTkTestWidgets/tominspector.py @@ -37,7 +37,7 @@ from TermTk.TTkWidgets.lineedit import TTkLineEdit from TermTk.TTkWidgets.combobox import TTkComboBox from TermTk.TTkWidgets.checkbox import TTkCheckbox from TermTk.TTkWidgets.spinbox import TTkSpinBox -from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.splitter import TTkSplitter from TermTk.TTkWidgets.frame import TTkFrame from TermTk.TTkWidgets.button import TTkButton @@ -142,7 +142,7 @@ class _TTkDomTreeWidgetItem(TTkTreeWidgetItem): def domWidget(self): return self._domWidget -class TTkTomInspector(TTkWidget): +class TTkTomInspector(TTkContainer): __slots__ = ('_domTree','_detail','_splitter') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/TermTk/TTkUiTools/properties/__init__.py b/TermTk/TTkUiTools/properties/__init__.py index 78474643..352f1845 100644 --- a/TermTk/TTkUiTools/properties/__init__.py +++ b/TermTk/TTkUiTools/properties/__init__.py @@ -2,6 +2,7 @@ from .button import TTkButtonProperties from .checkbox import TTkCheckboxProperties from .combobox import TTkComboBoxProperties +from .container import TTkContainerProperties from .frame import TTkFrameProperties # from .graph import # from .image import diff --git a/TermTk/TTkUiTools/properties/container.py b/TermTk/TTkUiTools/properties/container.py new file mode 100644 index 00000000..320476c1 --- /dev/null +++ b/TermTk/TTkUiTools/properties/container.py @@ -0,0 +1,47 @@ +# MIT License +# +# Copyright (c) 2023 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. + +from TermTk.TTkCore.string import TTkString +from TermTk.TTkLayouts.layout import TTkLayout +from TermTk.TTkWidgets.container import TTkContainer + +TTkContainerProperties = { + 'properties' : { + 'Padding': { + 'get': { 'cb':TTkContainer.getPadding, 'type': [ + { 'name': 'top', 'type':int } , + { 'name': 'bottom', 'type':int } , + { 'name': 'left', 'type':int } , + { 'name': 'right', 'type':int } ] }, + 'set': { 'cb':TTkContainer.setPadding, 'type': [ + { 'name': 'top', 'type':int } , + { 'name': 'bottom', 'type':int } , + { 'name': 'left', 'type':int } , + { 'name': 'right', 'type':int } ] } }, + 'Layout' : { + 'init': {'name':'layout', 'type':TTkLayout} , + 'get': { 'cb':TTkContainer.layout, 'type':TTkLayout} , + 'set': { 'cb':TTkContainer.setLayout, 'type':TTkLayout} }, + },'signals' : { + },'slots' : { + } +} diff --git a/TermTk/TTkUiTools/properties/widget.py b/TermTk/TTkUiTools/properties/widget.py index 121629e0..b07c9d69 100644 --- a/TermTk/TTkUiTools/properties/widget.py +++ b/TermTk/TTkUiTools/properties/widget.py @@ -78,21 +78,6 @@ TTkWidgetProperties = { 'init': {'name':'maxHeight', 'type':int } , 'get': { 'cb':TTkWidget.maximumHeight, 'type':int } , 'set': { 'cb':TTkWidget.setMaximumHeight,'type':int } }, - 'Padding': { - 'get': { 'cb':TTkWidget.getPadding, 'type': [ - { 'name': 'top', 'type':int } , - { 'name': 'bottom', 'type':int } , - { 'name': 'left', 'type':int } , - { 'name': 'right', 'type':int } ] }, - 'set': { 'cb':TTkWidget.setPadding, 'type': [ - { 'name': 'top', 'type':int } , - { 'name': 'bottom', 'type':int } , - { 'name': 'left', 'type':int } , - { 'name': 'right', 'type':int } ] } }, - 'Layout' : { - 'init': {'name':'layout', 'type':TTkLayout} , - 'get': { 'cb':TTkWidget.layout, 'type':TTkLayout} , - 'set': { 'cb':TTkWidget.setLayout, 'type':TTkLayout} }, 'Visible' : { 'init': {'name':'visible', 'type':bool } , 'get': { 'cb':TTkWidget.isVisible, 'type':bool } , diff --git a/TermTk/TTkUiTools/uiproperties.py b/TermTk/TTkUiTools/uiproperties.py index 9e5ba22b..116adbda 100644 --- a/TermTk/TTkUiTools/uiproperties.py +++ b/TermTk/TTkUiTools/uiproperties.py @@ -28,6 +28,7 @@ TTkUiProperties = { # Widgets TTkButton.__name__: TTkButtonProperties, TTkCheckbox.__name__: TTkCheckboxProperties, + TTkContainer.__name__: TTkContainerProperties, TTkComboBox.__name__: TTkComboBoxProperties, TTkFrame.__name__: TTkFrameProperties, TTkLabel.__name__: TTkLabelProperties, diff --git a/TermTk/TTkWidgets/TTkModelView/treewidget.py b/TermTk/TTkWidgets/TTkModelView/treewidget.py index 322e425f..3b8a813a 100644 --- a/TermTk/TTkWidgets/TTkModelView/treewidget.py +++ b/TermTk/TTkWidgets/TTkModelView/treewidget.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi diff --git a/TermTk/TTkWidgets/__init__.py b/TermTk/TTkWidgets/__init__.py index 5cb7a80d..4fc4b600 100644 --- a/TermTk/TTkWidgets/__init__.py +++ b/TermTk/TTkWidgets/__init__.py @@ -1,9 +1,16 @@ +# Base widhgets from .widget import TTkWidget +from .container import TTkContainer + +# Containerised widgets from .frame import TTkFrame from .resizableframe import TTkResizableFrame -from .button import TTkButton +from .window import TTkWindow +from .splitter import TTkSplitter +# Everything else from .about import TTkAbout +from .button import TTkButton from .checkbox import TTkCheckbox from .combobox import TTkComboBox from .Fancy import * @@ -21,10 +28,8 @@ from .scrollarea import TTkScrollArea from .scrollbar import TTkScrollBar from .spacer import TTkSpacer from .spinbox import TTkSpinBox -from .splitter import TTkSplitter from .tabwidget import TTkTabWidget, TTkTabButton, TTkTabBar from .kodetab import TTkKodeTab from .texedit import TTkTextEdit, TTkTextEditView from .TTkModelView import * from .TTkPickers import * -from .window import TTkWindow diff --git a/TermTk/TTkWidgets/container.py b/TermTk/TTkWidgets/container.py new file mode 100644 index 00000000..7b423778 --- /dev/null +++ b/TermTk/TTkWidgets/container.py @@ -0,0 +1,423 @@ +# MIT License +# +# Copyright (c) 2023 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. + +from TermTk.TTkCore.cfg import TTkCfg, TTkGlbl +from TermTk.TTkCore.constant import TTkK +from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.helper import TTkHelper +from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.string import TTkString +from TermTk.TTkCore.canvas import TTkCanvas +from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot +from TermTk.TTkTemplates.lookandfeel import TTkLookAndFeel +from TermTk.TTkTemplates.dragevents import TDragEvents +from TermTk.TTkTemplates.mouseevents import TMouseEvents +from TermTk.TTkTemplates.keyevents import TKeyEvents +from TermTk.TTkLayouts.layout import TTkLayout, TTkWidgetItem +from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent +from TermTk.TTkWidgets.widget import TTkWidget + +class TTkContainer(TTkWidget): + ''' Widget Layout sizes: + + :: + + Terminal area (i.e. XTerm) + ┌─────────────────────────────────────────┐ + │ │ + │ TTkWidget width │ + │ (x,y)┌─────────────────────────┐ │ + │ │ padt (Top Padding) │ │ + │ │ ┌───────────────┐ │ height │ + │ │padl│ Layout/child │padr│ │ + │ │ └───────────────┘ │ │ + │ │ padb (Bottom Pad.) │ │ + │ └─────────────────────────┘ │ + └─────────────────────────────────────────┘ + + The TTkWidget class is the base class of all user interface objects + + :param name: the name of the widget, defaults to "" + :type name: str, optional + :param parent: the parent widget, defaults to None + :type parent: :class:`TTkWidget`, optional + + :param int x: the x position, defaults to 0 + :param int y: the y position, defaults to 0 + :param [int,int] pos: the [x,y] position (override the previously defined x, y), optional, default=[0,0] + + :param int width: the width of the widget, defaults to 0 + :param int height: the height of the widget, defaults to 0 + :param [int,int] size: the size [width, height] of the widget (override the previously defined sizes), optional, default=[0,0] + + :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 + :param int maxWidth: the maxWidth of the widget, optional, defaults to 0x10000 + :param int maxHeight: the maxHeight of the widget, optional, defaults to 0x10000 + :param [int,int] maxSize: the max [width,height] of the widget, optional + :param int minWidth: the minWidth of the widget, defaults to 0 + :param int minHeight: the minHeight of the widget, defaults to 0 + :param [int,int] minSize: the minSize [width,height] of the widget, optional + + :param toolTip: This property holds the widget's tooltip + :type toolTip: :class:`~TermTk.TTkCore.string.TTkString` + + :param lookAndFeel: the style helper to be used for any customization + :type lookAndFeel: :class:`~TermTk.TTkTemplates.lookandfeel.TTkTTkLookAndFeel` + + :param bool,optional visible: the visibility, optional, defaults to True + :param bool,optional enabled: the ability to handle input events, optional, defaults to True + :param layout: the layout of this widget, optional, defaults to :class:`~TermTk.TTkLayouts.layout.TTkLayout` + :type layout: :mod:`TermTk.TTkLayouts` + ''' + + __slots__ = ( + '_padt', '_padb', '_padl', '_padr', + '_layout') + + def __init__(self, *args, **kwargs): + + 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._layout = TTkLayout() # root layout + self._layout.addItem(kwargs.get('layout',TTkLayout())) # main layout + + super().__init__(**kwargs) + + self._layout.setParent(self) + + def addWidget(self, widget): + ''' + .. warning:: + Method Deprecated, + + use :class:`TTkWidget` -> :class:`~TermTk.TTkWidgets.widget.TTkWidget.layout` -> :class:`~TermTk.TTkLayouts.layout.TTkLayout.addWidget` + + i.e. + + .. code:: python + + parentWidget.layout().addWidget(childWidget) + ''' + TTkLog.error(".addWidget(...) is deprecated, use .layout().addWidget(...)") + if self.layout(): self.layout().addWidget(widget) + + def removeWidget(self, widget): + ''' + .. warning:: + Method Deprecated, + + use :class:`TTkWidget` -> :class:`~TermTk.TTkWidgets.widget.TTkWidget.layout` -> :class:`~TermTk.TTkLayouts.layout.TTkLayout.removeWidget` + + i.e. + + .. code:: python + + parentWidget.layout().removeWidget(childWidget) + ''' + TTkLog.error(".removeWidget(...) is deprecated, use .layout().removeWidget(...)") + if self.layout(): self.layout().removeWidget(widget) + + @staticmethod + def _paintChildCanvas(canvas, item, geometry, offset): + ''' .. caution:: Don't touch this! ''' + lx,ly,lw,lh = geometry + ox, oy = offset + if item.layoutItemType() == TTkK.WidgetItem and not item.isEmpty(): + child = item.widget() + cx,cy,cw,ch = child.geometry() + canvas.paintCanvas( + child.getCanvas(), + (cx+ox, cy+oy, cw, ch), # geometry + ( 0, 0, cw, ch), # slice + ( lx, ly, lw, lh)) # bound + else: + for child in item.zSortedItems: + # The Parent Layout Geometry (lx,ly,lw,lh) include the padding of the layout + igx, igy, igw, igh = item.geometry() + iox, ioy = item.offset() + # Moved Layout to the new geometry (ix,iy,iw,ih) + ix = igx+ox # + iox + iy = igy+oy # + ioy + iw = igw # -iox + ih = igh # -ioy + # return if Child outside the bound + if ix+iw < lx and ix > lx+lw and iy+ih < ly and iy > ly+lh: continue + # Crop the Layout based on the Parent Layout Geometry + bx = max(ix,lx) + by = max(iy,ly) + bw = min(ix+iw,lx+lw)-bx + bh = min(iy+ih,ly+lh)-by + TTkContainer._paintChildCanvas(canvas, child, (bx,by,bw,bh), (ix+iox,iy+ioy)) + + def paintChildCanvas(self): + ''' .. 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 + + :return: list[top, bottom, left, right]: the 4 padding sizes + ''' + return 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 + + :param int top: top padding + :param int bottom: bottom padding + :param int left: left padding + :param int right: right padding + ''' + if self._padt == top and self._padb == bottom and \ + self._padl == left and self._padr == right: return + self._padt = top + self._padb = bottom + self._padl = left + self._padr = right + self.update(repaint=True, updateLayout=True) + + @staticmethod + def _mouseEventLayoutHandle(evt, layout): + ''' .. caution:: Don't touch this! ''' + x, y = evt.x, evt.y + lx,ly,lw,lh =layout.geometry() + lox, loy = layout.offset() + lx,ly,lw,lh = lx+lox, ly+loy, lw-lox, lh-loy + # opt of bounds + if x=lx+lw or y=lh+ly: + return False + x-=lx + y-=ly + for item in reversed(layout.zSortedItems): + # for item in layout.zSortedItems: + if item.layoutItemType() == TTkK.WidgetItem and not item.isEmpty(): + widget = item.widget() + if not widget._visible: continue + wx,wy,ww,wh = widget.geometry() + # Skip the mouse event if outside this widget + if not (wx <= x < wx+ww and wy <= y < wy+wh): continue + wevt = evt.clone(pos=(x-wx, y-wy)) + if widget.mouseEvent(wevt): + return True + elif item.layoutItemType() == TTkK.LayoutItem: + levt = evt.clone(pos=(x, y)) + if TTkContainer._mouseEventLayoutHandle(levt, item): + return True + return False + + _mouseOver = None + _mouseOverTmp = None + _mouseOverProcessed = False + def mouseEvent(self, evt): + ''' .. caution:: Don't touch this! ''' + if not self._enabled: return False + + # Saving self in this global variable + # So that after the "_mouseEventLayoutHandle" + # this tmp value will hold the last widget below the mouse + TTkWidget._mouseOverTmp = self + + # Mouse Drag has priority because it + # should be handled by the focused widget and + # not pushed to the unfocused childs + # unless there is a Drag and Drop event ongoing + if evt.evt == TTkK.Drag and not TTkHelper.isDnD(): + if self.mouseDragEvent(evt): + return True + + if self.rootLayout() is not None: + if TTkContainer._mouseEventLayoutHandle(evt, self.rootLayout()): + return True + + # If there is an overlay and it is modal, + # return False if this widget is not part of any + # of the widgets above the modal + if not TTkHelper.checkModalOverlay(self): + return False + + # Handle Drag and Drop Events + if TTkHelper.isDnD(): + ret = False + if evt.evt == TTkK.Drag: + dndw = TTkHelper.dndWidget() + if dndw == self: + if self.dragMoveEvent(TTkHelper.dndGetDrag().getDragMoveEvent(evt)): + return True + else: + if self.dragEnterEvent(TTkHelper.dndGetDrag().getDragEnterEvent(evt)): + if dndw: + ret = dndw.dragLeaveEvent(TTkHelper.dndGetDrag().getDragLeaveEvent(evt)) + TTkHelper.dndEnter(self) + return True + if evt.evt == TTkK.Release: + if self.dropEvent(TTkHelper.dndGetDrag().getDropEvent(evt)): + return True + return ret + + # handle Enter/Leave Events + # _mouseOverTmp hold the top widget under the mouse + # if different than self it means that it is a child + if evt.evt == TTkK.Move: + if not TTkWidget._mouseOverProcessed: + if TTkWidget._mouseOver != TTkWidget._mouseOverTmp == self: + if TTkWidget._mouseOver: + # TTkLog.debug(f"Leave: {TTkWidget._mouseOver._name}") + TTkWidget._mouseOver.leaveEvent(evt) + TTkWidget._mouseOver = self + # TTkLog.debug(f"Enter: {TTkWidget._mouseOver._name}") + TTkHelper.toolTipClose() + if self._toolTip and self._toolTip != '': + TTkHelper.toolTipTrigger(self._toolTip) + # TTkHelper.triggerToolTip(self._name) + TTkWidget._mouseOver.enterEvent(evt) + TTkWidget._mouseOverProcessed = True + if self.mouseMoveEvent(evt): + return True + else: + TTkHelper.toolTipClose() + + if evt.evt == TTkK.Release: + self._pendingMouseRelease = False + self._processStyleEvent(TTkWidget._S_NONE) + if self.mouseReleaseEvent(evt): + return True + + if evt.evt == TTkK.Press: + # in case of parent focus, check the parent that can accept the focus + w = self + while w._parent and (w.focusPolicy() & TTkK.ParentFocus) == TTkK.ParentFocus: + w = w._parent + if w.focusPolicy() & TTkK.ClickFocus == TTkK.ClickFocus: + w.setFocus() + w.raiseWidget() + self._processStyleEvent(TTkWidget._S_PRESSED) + if evt.tap == 2 and self.mouseDoubleClickEvent(evt): + #self._pendingMouseRelease = True + return True + if evt.tap > 1 and self.mouseTapEvent(evt): + return True + if evt.tap == 1 and self.mousePressEvent(evt): + # TTkLog.debug(f"Click {self._name}") + self._pendingMouseRelease = True + return True + + if evt.key == TTkK.Wheel: + if self.wheelEvent(evt): + return True + + return False + + def setLayout(self, layout): + self._layout.replaceItem(layout, 0) + #self.layout().setParent(self) + self.update(repaint=True, updateLayout=True) + + def layout(self): + ''' Get the layout + + :return: The layout used + :rtype: :class:`TTkLayout` or derived + ''' + return self._layout.itemAt(0) + + def rootLayout(self): return self._layout + + def maximumHeight(self): + 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): + wMaxW = self._maxw + if self.layout() is not None: + lMaxW = self.layout().maximumWidth() + self._padl + self._padr + if lMaxW < wMaxW: + 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): + 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): + wMinW = self._minw + if self.layout() is not None: + lMinW = self.layout().minimumWidth() + self._padl + self._padr + if lMinW > wMinW: + return lMinW + return wMinW + + @pyTTkSlot() + def show(self): + '''show''' + if self._visible: return + self._visible = True + self._canvas.show() + self.update(updateLayout=True, updateParent=True) + for w in self.rootLayout().iterWidgets(onlyVisible=True): + w.update() + + @pyTTkSlot() + def hide(self): + '''hide''' + 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): + super().update(repaint=repaint, updateLayout=updateLayout, updateParent=updateParent) + if updateLayout and self.rootLayout() is not None: + self.rootLayout().setGeometry(0,0,self._width,self._height) + self.layout().setGeometry( + self._padl, self._padt, + self._width - self._padl - self._padr, + self._height - self._padt - self._padb) + self.rootLayout().update() + + def getWidgetByName(self, name: str): + if name == self._name: + return self + for w in self.rootLayout().iterWidgets(onlyVisible=False, recurse=True): + if w._name == name: + return w + return None diff --git a/TermTk/TTkWidgets/frame.py b/TermTk/TTkWidgets/frame.py index 422a04d8..6813c621 100644 --- a/TermTk/TTkWidgets/frame.py +++ b/TermTk/TTkWidgets/frame.py @@ -24,9 +24,9 @@ from TermTk.TTkCore.cfg import * from TermTk.TTkCore.string import TTkString -from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer -class TTkFrame(TTkWidget): +class TTkFrame(TTkContainer): ''' :: @@ -62,10 +62,26 @@ class TTkFrame(TTkWidget): self._menubarBottomPosition = 0 self._menubarTop = None self._menubarBottom = None - TTkWidget.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.setBorder(self._border) def newMenubarTop(self): + '''newMenubarTop + + .. warning:: + Method Deprecated, + + use :class:`~TermTk.TTkWidgets.frame.setMenuBar` instead + + i.e. + + .. code:: python + + menuBar = TTkMenuBarLayout() + frame.setMenuBar(menuBar) + menuBar.addMenu("File") + + ''' if not self._menubarTop: from TermTk.TTkWidgets.menubar import TTkMenuBarLayout self._menubarTop = TTkMenuBarLayout(borderColor=self._borderColor) diff --git a/TermTk/TTkWidgets/spinbox.py b/TermTk/TTkWidgets/spinbox.py index 5acdf286..3e2af7ed 100644 --- a/TermTk/TTkWidgets/spinbox.py +++ b/TermTk/TTkWidgets/spinbox.py @@ -27,11 +27,11 @@ from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkLayouts import TTkGridLayout -from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.lineedit import TTkLineEdit -class TTkSpinBox(TTkWidget): +class TTkSpinBox(TTkContainer): '''TTkSpinBox''' __slots__= ( '_lineEdit', '_value', '_maximum', '_minimum', @@ -41,7 +41,7 @@ class TTkSpinBox(TTkWidget): def __init__(self, *args, **kwargs): # Signals self.valueChanged=pyTTkSignal(int) - TTkWidget.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self._value = kwargs.get("value",0) self._maximum = kwargs.get("maximum",99) self._minimum = kwargs.get("minimum",0) diff --git a/TermTk/TTkWidgets/splitter.py b/TermTk/TTkWidgets/splitter.py index 427b43a6..5eb1fa9d 100644 --- a/TermTk/TTkWidgets/splitter.py +++ b/TermTk/TTkWidgets/splitter.py @@ -27,9 +27,9 @@ from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.string import TTkString from TermTk.TTkLayouts.layout import TTkLayout from TermTk.TTkWidgets.widget import TTkWidget -from TermTk.TTkWidgets.frame import TTkFrame +from TermTk.TTkWidgets.container import TTkContainer -class TTkSplitter(TTkWidget): +class TTkSplitter(TTkContainer): '''TTkSplitter''' __slots__ = ( '_orientation', '_separators', '_refSizes', diff --git a/TermTk/TTkWidgets/tabwidget.py b/TermTk/TTkWidgets/tabwidget.py index 077f46e8..9b267fe6 100644 --- a/TermTk/TTkWidgets/tabwidget.py +++ b/TermTk/TTkWidgets/tabwidget.py @@ -31,6 +31,7 @@ from TermTk.TTkCore.string import TTkString from TermTk.TTkGui.drag import TTkDrag from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.spacer import TTkSpacer from TermTk.TTkWidgets.frame import TTkFrame from TermTk.TTkWidgets.button import TTkButton @@ -214,7 +215,7 @@ _labels= │◀│La│Label1║Label2║Label3│Label4│▶│ leftscroller rightScroller ''' -class TTkTabBar(TTkWidget): +class TTkTabBar(TTkContainer): '''TTkTabBar''' __slots__ = ( '_tabButtons', '_tabData', '_tabMovable', '_small', @@ -241,7 +242,7 @@ class TTkTabBar(TTkWidget): self._leftScroller.clicked.connect( self._moveToTheLeft) self._rightScroller.clicked.connect(self._andMoveToTheRight) - TTkWidget.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) self.focusChanged.connect(self._focusChanged) diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index ae4b0ea6..52ca9c45 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -105,10 +105,9 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): __slots__ = ( '_name', '_parent', '_x', '_y', '_width', '_height', - '_padt', '_padb', '_padl', '_padr', '_maxw', '_maxh', '_minw', '_minh', '_focus','_focus_policy', - '_layout', '_canvas', '_widgetItem', + '_canvas', '_widgetItem', '_visible', '_transparent', '_pendingMouseRelease', '_enabled', @@ -144,12 +143,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._height = kwargs.get('height', 0 ) self._width, self._height = kwargs.get('size', (self._width, self._height)) - 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._maxw = kwargs.get('maxWidth', 0x10000) self._maxh = kwargs.get('maxHeight', 0x10000) self._maxw, self._maxh = kwargs.get('maxSize', (self._maxw, self._maxh)) @@ -169,17 +162,14 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._widgetItem = TTkWidgetItem(widget=self) - self._layout = TTkLayout() # root layout - self._layout.setParent(self) - self._layout.addItem(kwargs.get('layout',TTkLayout())) # main layout - self._canvas = TTkCanvas( widget = self, width = self._width , height = self._height ) - - if self._parent and self._parent.layout(): + # TODO: Check this, + # The parent should always have a layout + if hasattr(self._parent,'layout') and self._parent.layout(): self._parent.layout().addWidget(self) self._parent.update(repaint=True, updateLayout=True) @@ -194,7 +184,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): # att = self.__getattribute__(an) # # TODO: TBD, I need to find the time to do this - if self._parent and self._parent.layout(): + if hasattr(self._parent,'layout') and self._parent.layout(): self._parent.layout().removeWidget(self) self._parent = None @@ -206,41 +196,8 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): '''setName''' self._name = name - def widgetItem(self): return self._widgetItem - def addWidget(self, widget): - ''' - .. warning:: - Method Deprecated, - - use :class:`TTkWidget` -> :class:`~TermTk.TTkWidgets.widget.TTkWidget.layout` -> :class:`~TermTk.TTkLayouts.layout.TTkLayout.addWidget` - - i.e. - - .. code:: python - - parentWidget.layout().addWidget(childWidget) - ''' - TTkLog.error(".addWidget(...) is deprecated, use .layout().addWidget(...)") - if self.layout(): self.layout().addWidget(widget) - - def removeWidget(self, widget): - ''' - .. warning:: - Method Deprecated, - - use :class:`TTkWidget` -> :class:`~TermTk.TTkWidgets.widget.TTkWidget.layout` -> :class:`~TermTk.TTkLayouts.layout.TTkLayout.removeWidget` - - i.e. - - .. code:: python - - parentWidget.layout().removeWidget(childWidget) - ''' - TTkLog.error(".removeWidget(...) is deprecated, use .layout().removeWidget(...)") - if self.layout(): self.layout().removeWidget(widget) - def paintEvent(self, canvas:TTkCanvas): ''' Paint Event callback, @@ -255,39 +212,10 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): @staticmethod def _paintChildCanvas(canvas, item, geometry, offset): - ''' .. caution:: Don't touch this! ''' - lx,ly,lw,lh = geometry - ox, oy = offset - if item.layoutItemType() == TTkK.WidgetItem and not item.isEmpty(): - child = item.widget() - cx,cy,cw,ch = child.geometry() - canvas.paintCanvas( - child.getCanvas(), - (cx+ox, cy+oy, cw, ch), # geometry - ( 0, 0, cw, ch), # slice - ( lx, ly, lw, lh)) # bound - else: - for child in item.zSortedItems: - # The Parent Layout Geometry (lx,ly,lw,lh) include the padding of the layout - igx, igy, igw, igh = item.geometry() - iox, ioy = item.offset() - # Moved Layout to the new geometry (ix,iy,iw,ih) - ix = igx+ox # + iox - iy = igy+oy # + ioy - iw = igw # -iox - ih = igh # -ioy - # return if Child outside the bound - if ix+iw < lx and ix > lx+lw and iy+ih < ly and iy > ly+lh: continue - # Crop the Layout based on the Parent Layout Geometry - bx = max(ix,lx) - by = max(iy,ly) - bw = min(ix+iw,lx+lw)-bx - bh = min(iy+ih,ly+lh)-by - TTkWidget._paintChildCanvas(canvas, child, (bx,by,bw,bh), (ix+iox,iy+ioy)) + pass def paintChildCanvas(self): - ''' .. caution:: Don't touch this! ''' - TTkWidget._paintChildCanvas(self._canvas, self.rootLayout(), self.rootLayout().geometry(), self.rootLayout().offset()) + pass def moveEvent(self, x: int, y: int): ''' Event Callback triggered after a successful move''' @@ -343,58 +271,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self.resize(w, h) self.move(x, y) - def getPadding(self) -> (int, int, int, int): - ''' Retrieve the widget padding sizes - - :return: list[top, bottom, left, right]: the 4 padding sizes - ''' - return 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 - - :param int top: top padding - :param int bottom: bottom padding - :param int left: left padding - :param int right: right padding - ''' - if self._padt == top and self._padb == bottom and \ - self._padl == left and self._padr == right: return - self._padt = top - self._padb = bottom - self._padl = left - self._padr = right - self.update(repaint=True, updateLayout=True) - - @staticmethod - def _mouseEventLayoutHandle(evt, layout): - ''' .. caution:: Don't touch this! ''' - x, y = evt.x, evt.y - lx,ly,lw,lh =layout.geometry() - lox, loy = layout.offset() - lx,ly,lw,lh = lx+lox, ly+loy, lw-lox, lh-loy - # opt of bounds - if x=lx+lw or y=lh+ly: - return False - x-=lx - y-=ly - for item in reversed(layout.zSortedItems): - # for item in layout.zSortedItems: - if item.layoutItemType() == TTkK.WidgetItem and not item.isEmpty(): - widget = item.widget() - if not widget._visible: continue - wx,wy,ww,wh = widget.geometry() - # Skip the mouse event if outside this widget - if not (wx <= x < wx+ww and wy <= y < wy+wh): continue - wevt = evt.clone(pos=(x-wx, y-wy)) - if widget.mouseEvent(wevt): - return True - elif item.layoutItemType() == TTkK.LayoutItem: - levt = evt.clone(pos=(x, y)) - if TTkWidget._mouseEventLayoutHandle(levt, item): - return True - return False - def pasteEvent(self, txt:str): return False @@ -418,9 +294,9 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): if self.mouseDragEvent(evt): return True - if self.rootLayout() is not None: - if TTkWidget._mouseEventLayoutHandle(evt, self.rootLayout()): - return True + # if self.rootLayout() is not None: + # if TTkWidget._mouseEventLayoutHandle(evt, self.rootLayout()): + # return True # If there is an overlay and it is modal, # return False if this widget is not part of any @@ -500,20 +376,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): return False - def setLayout(self, layout): - self._layout.replaceItem(layout, 0) - #self.layout().setParent(self) - self.update(repaint=True, updateLayout=True) - - def layout(self): - ''' Get the layout - - :return: The layout used - :rtype: :class:`TTkLayout` or derived - ''' - return self._layout.itemAt(0) - def rootLayout(self): return self._layout - def setParent(self, parent): self._parent = parent def parentWidget(self): @@ -536,19 +398,9 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): else: return self.maximumHeight() def maximumHeight(self): - wMaxH = self._maxh - if self.layout() is not None: - lMaxH = self.layout().maximumHeight() + self._padt + self._padb - if lMaxH < wMaxH: - return lMaxH - return wMaxH + return self._maxh def maximumWidth(self): - wMaxW = self._maxw - if self.layout() is not None: - lMaxW = self.layout().maximumWidth() + self._padl + self._padr - if lMaxW < wMaxW: - return lMaxW - return wMaxW + return self._maxw def minimumSize(self): return self.minimumWidth(), self.minimumHeight() @@ -558,19 +410,9 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): else: return self.minimumHeight() def minimumHeight(self): - wMinH = self._minh - if self.layout() is not None: - lMinH = self.layout().minimumHeight() + self._padt + self._padb - if lMinH > wMinH: - return lMinH - return wMinH + return self._minh def minimumWidth(self): - wMinW = self._minw - if self.layout() is not None: - lMinW = self.layout().minimumWidth() + self._padl + self._padr - if lMinW > wMinW: - return lMinW - return wMinW + return self._minw def setMaximumSize(self, maxw: int, maxh: int): self.setMaximumWidth(maxw) @@ -603,8 +445,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._visible = True self._canvas.show() self.update(updateLayout=True, updateParent=True) - for w in self.rootLayout().iterWidgets(onlyVisible=True): - w.update() @pyTTkSlot() def hide(self): @@ -659,16 +499,8 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): if repaint: TTkHelper.addUpdateBuffer(self) TTkHelper.addUpdateWidget(self) - if updateLayout and self.rootLayout() is not None: - self.rootLayout().setGeometry(0,0,self._width,self._height) - self.layout().setGeometry( - self._padl, self._padt, - self._width - self._padl - self._padr, - self._height - self._padt - self._padb) if updateParent and self._parent is not None: self._parent.update(updateLayout=True) - if updateLayout and self.rootLayout() is not None: - self.rootLayout().update() @pyTTkSlot() def setFocus(self): @@ -749,9 +581,6 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): def getWidgetByName(self, name: str): if name == self._name: return self - for w in self.rootLayout().iterWidgets(onlyVisible=False, recurse=True): - if w._name == name: - return w return None _BASE_STYLE = {'default' : {'color': TTkColor.RST}} diff --git a/demo/demo.py b/demo/demo.py index 8a755738..2a86b02f 100755 --- a/demo/demo.py +++ b/demo/demo.py @@ -105,9 +105,9 @@ def demoShowcase(root=None, border=True): logInput = ttk.TTkKeyPressView(visible=False, maxHeight=3, minHeight=3) root.layout().addWidget(logInput, 1, 0) - domTree = ttk.TTkFrame(title="Tom Inspector", border=True, visible=False, layout=ttk.TTkGridLayout()) - ttk.TTkTomInspector(parent=domTree) - root.layout().addWidget(domTree, 0, 1) + # domTree = ttk.TTkFrame(title="Tom Inspector", border=True, visible=False, layout=ttk.TTkGridLayout()) + # ttk.TTkTomInspector(parent=domTree) + # root.layout().addWidget(domTree, 0, 1) leftFrame = ttk.TTkFrame(parent=splitter, layout=ttk.TTkGridLayout(), border=False) @@ -116,7 +116,7 @@ def demoShowcase(root=None, border=True): logInputToggler = ttk.TTkCheckbox(text='ShowInput') logInputToggler.stateChanged.connect(lambda x: logInput.setVisible(x==ttk.TTkK.Checked)) tomTreeToggler = ttk.TTkCheckbox(text='Tom View', enabled=False) - tomTreeToggler.stateChanged.connect(lambda x: domTree.setVisible(x==ttk.TTkK.Checked)) + # tomTreeToggler.stateChanged.connect(lambda x: domTree.setVisible(x==ttk.TTkK.Checked)) mouseToggler = ttk.TTkCheckbox(text='Mouse 🐀', checked=True) mouseToggler.stateChanged.connect(lambda x: ttk.TTkTerm.push(ttk.TTkTerm.Mouse.ON if x==ttk.TTkK.Checked else ttk.TTkTerm.Mouse.OFF)) quitButton = ttk.TTkButton(text="Quit", border=True, maxHeight=3)