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.
 
 
 
 
 

339 lines
13 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.
'''
### Grid Layout
[Tutorial](https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/002-layout.md)
The grid layout allows an automatic place all the widgets in a grid
the empty rows/cols are resized to the "columnMinHeight,columnMinWidth" parameters
TTkGridLayout ┌┐ columnMinWidth
╔═════════╤═════════╤╤═════════╗
║ Widget1 │ Widget2 ││ Widget3 ║
║ (0,0) │ (0,1) ││ (0,3) ║
╟─────────┼─────────┼┼─────────╢ ┐ columnMinHeight
╟─────────┼─────────┼┼─────────╢ ┘
║ Widget4 │ ││ ║
║ (2,0) │ ││ ║
╟─────────┼─────────┼┼─────────╢
║ │ ││ Widget5 ║
║ │ ││ (3,3) ║
╚═════════╧═════════╧╧═════════╝
'''
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkLayouts.layout import TTkLayout, TTkWidgetItem
class TTkGridLayout(TTkLayout):
__slots__ = ('_gridItems','_columnMinWidth','_columnMinHeight')
def __init__(self, *args, **kwargs):
'''
TTkGridLayout constructor
Args:
columnMinWidth (int, optional, default=0): the minimum width of the column
columnMinHeight (int, optional, default=0): the minimum height of the column
'''
TTkLayout.__init__(self, *args, **kwargs)
self._gridItems = [[]]
self._columnMinWidth = kwargs.get('columnMinWidth',0)
self._columnMinHeight = kwargs.get('columnMinHeight',0)
def _gridUsedsize(self):
rows = 1
cols = 0
for gridRow in range(len(self._gridItems)):
if rows < gridRow:
rows = gridRow
for gridCol in range(len(self._gridItems[0])):
if self._gridItems[gridRow][gridCol] is not None:
if cols < gridCol:
cols = gridCol
return (rows+1, cols+1)
def _reshapeGrid(self, size):
rows = size[0]
cols = size[1]
# remove extra rows
if rows < len(self._gridItems):
self._gridItems = self._gridItems[:rows]
elif rows > len(self._gridItems):
self._gridItems += [None]*(rows-len(self._gridItems))
# remove extra cols
for gridRow in range(len(self._gridItems)):
if self._gridItems[gridRow] is None:
self._gridItems[gridRow] = [None]*(cols)
continue
sizeRow = len(self._gridItems[gridRow])
if cols < sizeRow:
self._gridItems[gridRow] = self._gridItems[gridRow][:cols]
elif cols > sizeRow:
self._gridItems[gridRow] += [None]*(cols-sizeRow)
# addWidget(self, widget, row, col)
def addWidget(self, *args, **kwargs):
widget = args[0]
self.removeWidget(widget)
item = TTkWidgetItem(widget=widget)
if len(args) == 3:
TTkGridLayout.addItem(self, item, args[1], args[2])
else:
TTkGridLayout.addItem(self, item)
widget.update()
def replaceItem(self, item, index): pass
def addItem(self, *args, **kwargs):
item = args[0]
self.removeItem(item)
if len(args) == 3:
row = args[1]
col = args[2]
else:
# Append The widget at the end
row = 0
col = len(self._gridItems[0])
#retrieve the max col/rows to reshape the grid
maxrow = row
maxcol = col
for child in self.children():
if maxrow < child._row: maxrow = child._row
if maxcol < child._col: maxcol = child._col
# reshape the gridItems
maxrow += 1
maxcol += 1
# TODO: This is RUBBISH!!!
self._reshapeGrid(size=(maxrow,maxcol))
if self._gridItems[row][col] is not None:
# TODO: Handle the LayoutItem
self.removeItem(self._gridItems[row][col])
self._reshapeGrid(size=(maxrow,maxcol))
item._row = row
item._col = col
self._gridItems[row][col] = item
TTkLayout.addItem(self, item)
def removeItem(self, item):
TTkLayout.removeItem(self, item)
for gridRow in range(len(self._gridItems)):
for gridCol in range(len(self._gridItems[0])):
if self._gridItems[gridRow][gridCol] == item:
self._gridItems[gridRow][gridCol] = None
self._reshapeGrid(self._gridUsedsize())
def removeWidget(self, widget):
TTkLayout.removeWidget(self, widget)
for gridRow in range(len(self._gridItems)):
for gridCol in range(len(self._gridItems[0])):
if self._gridItems[gridRow][gridCol] is not None and \
self._gridItems[gridRow][gridCol].layoutItemType == TTkK.WidgetItem and \
self._gridItems[gridRow][gridCol].widget() == widget:
self._gridItems[gridRow][gridCol] = None
self._reshapeGrid(self._gridUsedsize())
def itemAtPosition(self, row: int, col: int):
if row >= len(self._gridItems) or \
col >= len(self._gridItems[0]):
return None
return self._gridItems[row][col]
def minimumColWidth(self, gridCol: int) -> int:
colw = 0
anyItem = False
for gridRow in range(len(self._gridItems)):
item = self._gridItems[gridRow][gridCol]
if item is not None and \
( item.layoutItemType == TTkK.LayoutItem or item.isVisible() ):
anyItem = True
w = item.minimumWidth()
if colw < w:
colw = w
if not anyItem:
return self._columnMinWidth
return colw
def minimumRowHeight(self, gridRow: int):
rowh = 0
anyItem = False
for item in self._gridItems[gridRow]:
if item is not None and \
( item.layoutItemType == TTkK.LayoutItem or item.isVisible() ):
anyItem = True
h = item.minimumHeight()
if rowh < h:
rowh = h
if not anyItem:
return self._columnMinHeight
return rowh
def maximumColWidth(self, gridCol: int) -> int:
colw = 0x10000
anyItem = False
for gridRow in range(len(self._gridItems)):
item = self._gridItems[gridRow][gridCol]
if item is not None and \
( item.layoutItemType == TTkK.LayoutItem or item.isVisible() ):
anyItem = True
w = item.maximumWidth()
if colw > w:
colw = w
if not anyItem:
return self._columnMinWidth
return colw
def maximumRowHeight(self, gridRow: int):
rowh = 0x10000
anyItem = False
for item in self._gridItems[gridRow]:
if item is not None and \
( item.layoutItemType == TTkK.LayoutItem or item.isVisible() ):
anyItem = True
h = item.maximumHeight()
if rowh > h:
rowh = h
if not anyItem:
return self._columnMinHeight
return rowh
def minimumWidth(self) -> int:
''' process the widgets and get the min size '''
minw = 0
for gridCol in range(len(self._gridItems[0])):
minw += self.minimumColWidth(gridCol)
return minw
def minimumHeight(self) -> int:
''' process the widgets and get the min size '''
minh = 0
for gridRow in range(len(self._gridItems)):
minh += self.minimumRowHeight(gridRow)
return minh
def maximumWidth(self) -> int:
''' process the widgets and get the min size '''
if not self._gridItems[0]:
return 0x1000
maxw = 0
for gridCol in range(len(self._gridItems[0])):
maxw += self.maximumColWidth(gridCol)
return maxw
def maximumHeight(self) -> int:
''' process the widgets and get the min size '''
if not self._gridItems[0]:
return 0x1000
maxh = 0
for gridRow in range(len(self._gridItems)):
maxh += self.maximumRowHeight(gridRow)
return maxh
def update(self, *args, **kwargs):
x, y, w, h = self.geometry()
newx, newy = x, y
# Sorted List of minimum heights
# min max val
# content IDs 0 1 2 3
sortedHeights = [ [i, self.minimumRowHeight(i), self.maximumRowHeight(i), -1] for i in range(len(self._gridItems)) ]
sortedWidths = [ [i, self.minimumColWidth(i), self.maximumColWidth(i), -1] for i in range(len(self._gridItems[0])) ]
sortedHeights = sorted(sortedHeights, key=lambda h: h[1])
sortedWidths = sorted(sortedWidths, key=lambda w: w[1])
minWidth = 0
minHeight = 0
for i in sortedWidths: minWidth += i[1]
for i in sortedHeights: minHeight += i[1]
if h < minHeight: h = minHeight
if w < minWidth: w = minWidth
#TTkLog.debug(f"w,h:({w,h}) mh:{minHeight} sh:{sortedHeights}")
#TTkLog.debug(f"w,h:({w,h}) mw:{minWidth} sw:{sortedWidths}")
def parseSizes(sizes, space, out):
iterate = True
freeSpace = space
leftSlots = len(sizes)
while iterate and leftSlots > 0:
iterate = False
for item in sizes:
if item[3] != -1: continue
if freeSpace < 0: freeSpace=0
sliceSize = freeSpace//leftSlots
mins = item[1]
maxs = item[2]
if sliceSize >= maxs:
iterate = True
freeSpace -= maxs
leftSlots -= 1
item[3] = maxs
elif sliceSize < mins:
iterate = True
freeSpace -= mins
leftSlots -= 1
item[3] = mins
# Push the sizes
for item in sizes:
out[item[0]] = [0,item[3]]
if item[3] == -1:
sliceSize = freeSpace//leftSlots
out[item[0]] = [0,sliceSize]
freeSpace -= sliceSize
leftSlots -= 1
vertSizes = [None]*len(sortedHeights)
horSizes = [None]*len(sortedWidths)
parseSizes(sortedHeights,h, vertSizes)
parseSizes(sortedWidths, w, horSizes)
for i in horSizes:
i[0] = newx
newx += i[1]
for i in vertSizes:
i[0] = newy
newy += i[1]
#TTkLog.debug(f"h:{horSizes} v:{vertSizes}")
# loop and set the geometry of any item
for item in self.children():
col = item._col
row = item._row
item.setGeometry(
horSizes[col][0], vertSizes[row][0] ,
horSizes[col][1], vertSizes[row][1] )
#TTkLog.debug(f"Children: {item.geometry()}")
if item.layoutItemType == TTkK.WidgetItem and not item.isEmpty():
#TTkLog.debug(f"Children name: {item.widget()._name}")
item.widget().update(*args, **kwargs)
elif item.layoutItemType == TTkK.LayoutItem:
item.update(*args, **kwargs)
return True