Source code for TermTk.TTkLayouts.layout

# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

'''
**Layout** [`Tutorial <https://ceccopierangiolieugenio.github.io/pyTermTk/tutorial/002-layout.html#simple-ttklayout>`__]
'''

__all__ = ['TTkLayoutItem', 'TTkLayout']

from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.constant import TTkK

[docs] class TTkLayoutItem(): ''' :py:class:`~TTkLayoutItem` is the base class of layout Items inherited by :py:class:`~TTkLayout`, :py:class:`~TTkWidgetItem`, and all the derived layout managers. :param int row: (used only in the :py:class:`TTkGridLayout`), the row of the grid, optional, defaults to None :param int col: (used only in the :py:class:`TTkGridLayout`), the col of the grid, optional, defaults to None :param int rowspan: (used only in the :py:class:`TTkGridLayout`), the rows used by this, optional, defaults to 1 :param int colspan: (used only in the :py:class:`TTkGridLayout`), the cols used by this, optional, defaults to 1 :param layoutItemType: The Type of this class, optional, defaults to TTkK.NONE :type layoutItemType: :py:class:`TTkConstant.LayoutItemTypes` :param alignment: The alignment of this item in the layout (not yet used) :type alignment: :py:class:`TTkConstant.Alignment` ''' LAYER0 = 0x00000000 LAYER1 = 0x40000000 LAYER2 = 0x80000000 LAYER3 = 0xC0000000 LAYERMASK = ~(LAYER0 | LAYER1 | LAYER2 | LAYER3) __slots__ = ( '_x', '_y', '_z', '_w', '_h', '_layer', '_xOffset', '_yOffset', '_maxw', '_maxh', '_minw', '_minh', '_row','_col', '_rowspan', '_colspan', '_sMax', '_sMaxVal', '_sMin', '_sMinVal', '_parent', '_layoutItemType') def __init__(self, *, x:int=0, y:int=0, z:int=0, pos:tuple=None, width:int=0, height:int=0, size:tuple=None, row:int=0, col:int=0, rowspan:int=1, colspan:int=1, layoutItemType:TTkK.LayoutItemTypes=TTkK.NONE, maxWidth:int=0x10000, maxHeight:int=0x10000, maxSize:tuple=None, minWidth:int=0, minHeight:int=0, minSize:tuple=None, **kwargs) -> None: if kwargs: TTkLog.warn(f"Unhandled init params {self.__class__.__name__} -> {kwargs}") self._x, self._y = pos if pos else (x,y) self._w, self._h = size if size else (width,height) self._z = z self._layer = TTkLayoutItem.LAYER0 self._xOffset = 0 self._yOffset = 0 self._row = row self._col = col self._rowspan = rowspan self._colspan = colspan self._layoutItemType = layoutItemType self._sMax, self._sMin = False, False self._sMaxVal, self._sMinVal = 0, 0 self._maxw, self._maxh = maxSize if maxSize else (maxWidth,maxHeight) self._minw, self._minh = minSize if minSize else (minWidth,minHeight) self._parent = None
[docs] def minimumSize(self): return self.minimumWidth(), self.minimumHeight()
[docs] def minDimension(self,o)-> int: return self._minh if o == TTkK.HORIZONTAL else self._minw
[docs] def minimumHeight(self) -> int: return self._minh
[docs] def minimumWidth(self) -> int: return self._minw
[docs] def maximumSize(self): return self.maximumWidth(), self.maximumHeight()
[docs] def maxDimension(self,o)-> int: return self._maxh if o == TTkK.HORIZONTAL else self._maxw
[docs] def maximumHeight(self) -> int: return self._maxh
[docs] def maximumWidth(self) -> int: return self._maxw
@staticmethod def _calcSpanValue(value, pos, curpos, span): if pos==curpos: return value - (value//span) * (span-1) else: return value//span
[docs] def minimumHeightSpan(self,pos) -> int: return TTkLayoutItem._calcSpanValue(self.minimumHeight(),pos,self._row,self._rowspan)
[docs] def minimumWidthSpan(self,pos) -> int: return TTkLayoutItem._calcSpanValue(self.minimumWidth(), pos,self._col,self._colspan)
[docs] def maximumHeightSpan(self,pos) -> int: return TTkLayoutItem._calcSpanValue(self.maximumHeight(),pos,self._row,self._rowspan)
[docs] def maximumWidthSpan(self,pos) -> int: return TTkLayoutItem._calcSpanValue(self.maximumWidth(), pos,self._col,self._colspan)
[docs] def offset(self): return self._xOffset, self._yOffset
[docs] def pos(self): return self._x, self._y
[docs] def size(self): return self._w, self._h
[docs] def geometry(self): return self._x, self._y, self._w, self._h
[docs] def setOffset(self, x, y): '''setOffset''' self._xOffset = x self._yOffset = y
[docs] def setGeometry(self, x, y, w, h): '''setGeometry''' self._x = x self._y = y self._w = w self._h = h
[docs] def parent(self): '''parent''' return self._parent
[docs] def setParent(self, parent): '''setParent''' self._parent = parent
[docs] def layer(self): '''layer''' return self._layer
[docs] def setLayer(self, layer): '''setLayer''' self._layer = layer self._z = (self._z & TTkLayoutItem.LAYERMASK) | layer
[docs] def layoutItemType(self): return self._layoutItemType
[docs] class TTkLayout(TTkLayoutItem): ''' | The :py:class:`TTkLayout` class is the base class of geometry managers. <br/> | It allows free placement of the widgets in the layout area. <br/> | Used mainly to have free range moving :py:class:`TTkWindow` because the widgets are not automatically rearranged after a layout event :: ╔════════════════════════════╗ ║ pos(4,2) ║ ║ ┌───────┐ pos(16,4) ║ ║ │Widget1│ ┌─────────┐ ║ ║ │ │ │ Widget2 │ ║ ║ │ │ └─────────┘ ║ ║ │ │ ║ ║ └───────┘ ║ ║ ║ ╚════════════════════════════╝ ''' __slots__ = ('_items', '_zSortedItems') def __init__(self, **kwargs) -> None: TTkLayoutItem.__init__(self, layoutItemType=TTkK.LayoutItemTypes.LayoutItem, **kwargs) self._items = [] self._zSortedItems = []
[docs] def children(self): return self._items
[docs] def count(self): return len(self._items)
[docs] def itemAt(self, index): if index < len(self._items): return self._items[index] return None
[docs] def setParent(self, parent): if parent is None: self._parent = parent elif isinstance(parent, TTkLayoutItem): self._parent = parent else: self._parent = parent.widgetItem() for item in self._items: item.setParent(self) if item._layoutItemType == TTkK.WidgetItem: item.widget().setParent(self.parentWidget())
[docs] def parentWidget(self): if self._parent is None: return None if self._parent._layoutItemType == TTkK.WidgetItem: return self._parent.widget() else: return self._parent.parentWidget()
[docs] def iterWidgets(self, onlyVisible=True, recurse=True): for child in self._items: if child._layoutItemType == TTkK.WidgetItem: if onlyVisible and not child.widget().isVisible(): continue yield child.widget() if recurse and hasattr(cw:=child.widget(),'rootLayout'): yield from cw.rootLayout().iterWidgets(onlyVisible, recurse) if child._layoutItemType == TTkK.LayoutItem and recurse: yield from child.iterWidgets(onlyVisible, recurse)
def _zSortItems(self): self._zSortedItems = sorted(self._items, key=lambda item: item._z) @property def zSortedItems(self): return self._zSortedItems
[docs] def replaceItem(self, item, index): self._items[index] = item self._zSortItems() self.update() item.setParent(self) if item._layoutItemType == TTkK.WidgetItem: item.widget().setParent(self.parentWidget()) if self.parentWidget(): self.parentWidget().update(repaint=True, updateLayout=True)
[docs] def addItem(self, item): self.insertItems(len(self._items),[item])
[docs] def addItems(self, items): self.insertItems(len(self._items),items)
[docs] def insertItem(self, index, item): return self.insertItems(index,[item])
[docs] def insertItems(self, index, items): for i,item in enumerate(items): if not issubclass(type(widget := item), TTkLayoutItem): if widget.parentWidget() and widget.parentWidget().layout(): widget.parentWidget().layout().removeWidget(self) item = widget.widgetItem() items[i]=item self._items[index:index] = items self._zSortItems() #self.update() for item in items: item.setParent(self) if item._layoutItemType == TTkK.WidgetItem: item.widget().setParent(self.parentWidget()) if self.parentWidget() and self.parentWidget().isVisible(): self.parentWidget().update(repaint=True, updateLayout=True) self.update()
[docs] def addWidget(self, widget): ''' Add a widget to this Layout :param widget: the widget to be added :type widget: :py:class:`TTkWidgets` ''' self.insertItems(len(self._items),[widget])
[docs] def addWidgets(self, widgets): ''' Add a list of widgets to this Layout :param widgets: the widget to be added :type widgets: list of :py:class:`TTkWidgets` ''' self.insertItems(len(self._items),widgets)
[docs] def insertWidget(self, index, widget): '''insertWidget''' self.insertItems(index, [widget])
[docs] def insertWidgets(self, index, widgets): '''insertWidgets''' self.insertItems(index, widgets)
[docs] def removeItem(self, item): '''removeItem''' self.removeItems([item])
[docs] def removeItems(self, items): '''removeItems''' for item in items: if item in self._items: self._items.remove(item) if item._layoutItemType == TTkK.WidgetItem: item.widget().setParent(None) item.setParent(None) self._zSortItems()
[docs] def removeWidget(self, widget): ''' Remove a widget from this Layout :param widget: the widget to be removed :type widget: :py:class:`TTkWidgets` ''' self.removeWidgets([widget])
[docs] def removeWidgets(self, widgets): ''' Remove a list of widget from this Layout :param widgets: the widget to be removed :type widgets: list of :py:class:`TTkWidgets` ''' for item in reversed(self._items): if item._layoutItemType == TTkK.WidgetItem: if item.widget() in widgets: self.removeItem(item) elif item._layoutItemType == TTkK.LayoutItem: item.removeWidgets(widgets)
def _findBranchWidget(self, widget): for item in self._items: if item._layoutItemType == TTkK.LayoutItem: if item._findBranchWidget(widget) is not None: return item else: if item.widget() == widget: return item return None
[docs] def raiseWidget(self, widget): '''raiseWidget''' item = self._findBranchWidget(widget) item._z = item._z if (maxz:=max(TTkLayoutItem.LAYERMASK & i._z for i in self._items))==(TTkLayoutItem.LAYERMASK & item._z)!=0 else item._layer|maxz+1 if item._layoutItemType == TTkK.LayoutItem: item.raiseWidget(widget) self._zSortItems()
[docs] def lowerWidget(self, widget): '''lowerWidget''' item = self._findBranchWidget(widget) for i in self._items: i._z+=1 item._z = item._layer if item._layoutItemType == TTkK.LayoutItem: item.lowerWidget(widget) self._zSortItems()
[docs] def setGeometry(self, x, y, w, h): '''setGeometry''' ax, ay, aw, ah = self.geometry() if ax==x and ay==y and aw==w and ah==h: return TTkLayoutItem.setGeometry(self, x, y, w, h) self.update(repaint=True, updateLayout=True)
[docs] def fullWidgetAreaGeometry(self): if not self._items: return 0,0,0,0 minx,miny,maxx,maxy = 0x10000,0x10000,-0x10000,-0x10000 for item in self._items: x,y,w,h = item.geometry() minx = min(minx,x) miny = min(miny,y) maxx = max(maxx,x+w) maxy = max(maxy,y+h) return minx, miny, maxx-minx, maxy-miny
[docs] def update(self, *args, **kwargs) -> None: ret = False for i in self._items: if i._layoutItemType == TTkK.WidgetItem and (_wid:=i._widget): ret = ret or _wid.update(*args, **kwargs) elif i._layoutItemType == TTkK.LayoutItem: ret = ret or i.update(*args, **kwargs) return ret
class TTkWidgetItem(TTkLayoutItem): __slots__ = ('_widget') def __init__(self, *, widget=None, **kwargs) -> None: TTkLayoutItem.__init__(self, layoutItemType=TTkK.LayoutItemTypes.WidgetItem, **kwargs) self._widget = widget def widget(self): return self._widget def isVisible(self): return self._widget.isVisible() def isEmpty(self): return self._widget is None def minimumSize(self) -> int: return self._widget.minimumSize() def minDimension(self,o)-> int: return self._widget.minDimension(o) def minimumHeight(self) -> int: return self._widget.minimumHeight() def minimumWidth(self) -> int: return self._widget.minimumWidth() def maximumSize(self) -> int: return self._widget.maximumSize() def maxDimension(self,o)-> int: return self._widget.maxDimension(o) def maximumHeight(self) -> int: return self._widget.maximumHeight() def maximumWidth(self) -> int: return self._widget.maximumWidth() def pos(self): return self._widget.pos() def size(self): return self._widget.size() def geometry(self): return self._widget.geometry() def setGeometry(self, x, y, w, h): self._widget.setGeometry(x, y, w, h) #def update(self, *args, **kwargs) -> None: # self.widget().update(*args, **kwargs)