You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

307 lines
12 KiB

#!/usr/bin/env python3
# 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://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/002-layout.md)
'''
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.constant import TTkK
class TTkLayoutItem:
''' :class:`~TTkLayoutItem` is the base class of layout Items inherited by :class:`~TTkLayout`, :class:`~TTkWidgetItem`, and all the derived layout managers.
:param int row: (used only in the :class:`~TermTk.TTkLayouts.gridlayout.TTkGridLayout`), the row of the grid, optional, defaults to None
:param int col: (used only in the :class:`~TermTk.TTkLayouts.gridlayout.TTkGridLayout`), the col of the grid, optional, defaults to None
:param int rowspan: (used only in the :class:`~TermTk.TTkLayouts.gridlayout.TTkGridLayout`), the rows used by this, optional, defaults to 1
:param int colspan: (used only in the :class:`~TermTk.TTkLayouts.gridlayout.TTkGridLayout`), the cols used by this, optional, defaults to 1
:param layoutItemType: The Type of this class, optional, defaults to TTkK.NONE
:type layoutItemType: :class:`~TermTk.TTkCore.constant.TTkConstant.LayoutItemTypes`
:param alignment: The alignment of this item in the layout (not yet used)
:type alignment: :class:`~TermTk.TTkCore.constant.TTkConstant.Alignment`
'''
__slots__ = (
'_x', '_y', '_z', '_w', '_h',
'_row','_col',
'_rowspan', '_colspan',
'_sMax', '_sMaxVal',
'_sMin', '_sMinVal',
'_alignment',
'_layoutItemType')
def __init__(self, *args, **kwargs):
self._x, self._y = 0, 0
self._z = kwargs.get('z',0)
self._row = kwargs.get('row', 0)
self._col = kwargs.get('col', 0)
self._rowspan = kwargs.get('rowspan', 1)
self._colspan = kwargs.get('colspan', 1)
self._layoutItemType = kwargs.get('layoutItemType', TTkK.NONE)
self._alignment = kwargs.get('alignment', TTkK.NONE)
self._w, self._h = 0, 0
self._sMax, self._sMin = False, False
self._sMaxVal, self._sMinVal = 0, 0
def minimumSize(self):
return self.minimumWidth(), self.minimumHeight()
def minDimension(self,o)-> int: return 0
def minimumHeight(self) -> int: return 0
def minimumWidth(self) -> int: return 0
def maximumSize(self):
return self.maximumWidth(), self.maximumHeight()
def maxDimension(self,o)-> int: return 0x1000
def maximumHeight(self) -> int: return 0x10000
def maximumWidth(self) -> int: return 0x10000
@staticmethod
def _calcSpanValue(value, pos, curpos, span):
if pos==curpos:
return value - (value//span) * (span-1)
else:
return value//span
def minimumHeightSpan(self,pos) -> int:
return TTkLayoutItem._calcSpanValue(self.minimumHeight(),pos,self._row,self._rowspan)
def minimumWidthSpan(self,pos) -> int:
return TTkLayoutItem._calcSpanValue(self.minimumWidth(), pos,self._col,self._colspan)
def maximumHeightSpan(self,pos) -> int:
return TTkLayoutItem._calcSpanValue(self.maximumHeight(),pos,self._row,self._rowspan)
def maximumWidthSpan(self,pos) -> int:
return TTkLayoutItem._calcSpanValue(self.maximumWidth(), pos,self._col,self._colspan)
def geometry(self):
return self._x, self._y, self._w, self._h
def setGeometry(self, x, y, w, h):
self._x = x
self._y = y
self._w = w
self._h = h
@property
def z(self): return self._z
@z.setter
def z(self, z): self._z = z
@property
def layoutItemType(self): return self._layoutItemType
@layoutItemType.setter
def layoutItemType(self, t): self._layoutItemType = t
class TTkLayout(TTkLayoutItem):
''' The :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 :class:`~TermTk.TTkWidgets.window.TTkWindow` because the widgets are not automatically rearranged after a layout event
```
╔════════════════════════════╗
║ pos(4,2) ║
║ ┌───────┐ pos(16,4) ║
║ │Widget1│ ┌─────────┐ ║
║ │ │ │ Widget2 │ ║
║ │ │ └─────────┘ ║
║ │ │ ║
║ └───────┘ ║
║ ║
╚════════════════════════════╝
```
'''
__slots__ = ('_items', '_zSortedItems', '_parent')
def __init__(self, *args, **kwargs):
TTkLayoutItem.__init__(self, args, kwargs)
self._items = []
self._zSortedItems = []
self._parent = None
self.layoutItemType = TTkK.LayoutItem
def children(self):
return self._items
def count(self):
return len(self._items)
def itemAt(self, index):
if index < len(self._items):
return self._items[index]
return 0
def setParent(self, parent):
if isinstance(parent, TTkLayoutItem):
self._parent = parent
else:
self._parent = TTkWidgetItem(widget=parent)
for item in self._items:
if item.layoutItemType == TTkK.LayoutItem:
item.setParent(self)
else:
item.widget().setParent(self.parentWidget())
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()
def _zSortItems(self):
self._zSortedItems = sorted(self._items, key=lambda item: item.z)
@property
def zSortedItems(self): return self._zSortedItems
def replaceItem(self, item, index):
self._items[index] = item
self._zSortItems()
self.update()
if item.layoutItemType == TTkK.LayoutItem:
item.setParent(self)
else:
item.widget().setParent(self.parentWidget())
if self.parentWidget():
self.parentWidget().update(repaint=True, updateLayout=True)
def addItem(self, item):
self._items.append(item)
self._zSortItems()
self.update()
if item.layoutItemType == TTkK.LayoutItem:
item.setParent(self)
else:
item.widget().setParent(self.parentWidget())
if self.parentWidget():
self.parentWidget().update(repaint=True, updateLayout=True)
def addWidget(self, widget):
if widget.parentWidget() is not None:
widget.parentWidget().removeWidget(self)
self.addItem(TTkWidgetItem(widget=widget))
def removeItem(self, item):
if item in self._items:
self._items.remove(item)
self._zSortItems()
def removeWidget(self, widget):
for item in self._items:
if item.layoutItemType == TTkK.WidgetItem and \
item.widget() == widget:
self.removeItem(item)
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
def raiseWidget(self, widget):
maxz = 0
item = self.findBranchWidget(widget)
for i in self._items:
maxz=max(i.z+1,maxz)
item.z = maxz
if item.layoutItemType == TTkK.LayoutItem:
item.raiseWidget(widget)
self._zSortItems()
def lowerWidget(self, widget):
minz = 0
item = self.findBranchWidget(widget)
for i in self._items:
minz=min(i.z-1,minz)
item.z = minz
if item.layoutItemType == TTkK.LayoutItem:
item.lowerWidget(widget)
self._zSortItems()
def setGeometry(self, x, y, w, h):
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)
def groupMoveTo(self, x, y):
ox,oy,_,_ = self.fullWidgetAreaGeometry()
dx = x-ox
dy = y-oy
for item in self._items:
x,y,w,h = item.geometry()
item.setGeometry(x+dx,y+dy,w,h)
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
def update(self, *args, **kwargs):
ret = False
for i in self.children():
if i.layoutItemType == TTkK.WidgetItem and not i.isEmpty():
ret = ret or i.widget().update(*args, **kwargs)
# TODO: Have a look at this:
# i.getCanvas().top()
elif i.layoutItemType == TTkK.LayoutItem:
ret= ret or i.update(*args, **kwargs)
return ret
class TTkWidgetItem(TTkLayoutItem):
slots = ('_widget')
def __init__(self, *args, **kwargs):
TTkLayoutItem.__init__(self, *args, **kwargs)
self._widget = kwargs.get('widget', None)
self.layoutItemType = TTkK.WidgetItem
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 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):
# self.widget().update(*args, **kwargs)