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.
293 lines
10 KiB
293 lines
10 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 System |
|
''' |
|
|
|
from TermTk.TTkCore.log import TTkLog |
|
from TermTk.TTkLayouts.layout import TTkLayout, TTkWidgetItem |
|
|
|
class TTkGridWidgetItem(TTkWidgetItem): |
|
__slots__ = ('_row','_col') |
|
def __init__(self, *args, **kwargs): |
|
TTkWidgetItem.__init__(self, args[0]) |
|
self._row = kwargs.get('row') |
|
self._col = kwargs.get('col') |
|
|
|
class TTkGridLayout(TTkLayout): |
|
__slots__ = ('_gridItems','_columnMinWidth','_columnMinHeight') |
|
def __init__(self, *args, **kwargs): |
|
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+1) |
|
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) |
|
widget._parent = self.parentWidget() |
|
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 item in self.children(): |
|
if maxrow < item._row: maxrow = item._row |
|
if maxcol < item._col: maxcol = item._col |
|
# reshape the gridItems |
|
maxrow += 1 |
|
maxcol += 1 |
|
self._reshapeGrid(size=(maxrow,maxcol)) |
|
|
|
if self._gridItems[row][col] is not None: |
|
# TODO: Handle the LayoutItem |
|
self.removeWidget(self._gridItems[row][col]) |
|
|
|
item = TTkGridWidgetItem(widget, row=row, col=col) |
|
self._gridItems[row][col] = item |
|
self.addItem(item) |
|
widget.update(updateParent=True) |
|
|
|
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].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.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.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.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.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): |
|
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] ) |
|
if isinstance(item, TTkWidgetItem) and not item.isEmpty(): |
|
item.widget().update() |
|
elif isinstance(item, TTkLayout): |
|
item.update() |
|
return True
|
|
|