# 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** [:ref:`Tutorial <Layout-Tutorial_Intro>`]
'''
__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())
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 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()
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 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 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)