From 8f568f55aa9643eec81939b093f6a61568c54a51 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Fri, 5 Mar 2021 14:16:26 +0000 Subject: [PATCH 1/3] Initial reworking of layout reimplementation --- TermTk/TTkCore/constant.py | 4 ++ TermTk/TTkCore/ttk.py | 4 +- TermTk/TTkLayouts/gridlayout.py | 5 +- TermTk/TTkLayouts/layout.py | 95 ++++++++++++++++--------- TermTk/TTkWidgets/combobox.py | 22 +++--- TermTk/TTkWidgets/frame.py | 11 ++- TermTk/TTkWidgets/resizableframe.py | 4 +- TermTk/TTkWidgets/widget.py | 105 ++++++++++++++++------------ 8 files changed, 156 insertions(+), 94 deletions(-) diff --git a/TermTk/TTkCore/constant.py b/TermTk/TTkCore/constant.py index bce7ac1e..a8b4a418 100644 --- a/TermTk/TTkCore/constant.py +++ b/TermTk/TTkCore/constant.py @@ -109,6 +109,10 @@ class TTkConstant: CENTER_ALIGN = 0x0003 JUSTIFY = 0x0004 + # LayoutItem Types + LayoutItem = 0x01 + WidgetItem = 0x02 + Character = 0x0001 SpecialKey = 0x0002 diff --git a/TermTk/TTkCore/ttk.py b/TermTk/TTkCore/ttk.py index 3ced19c0..f24f78f5 100644 --- a/TermTk/TTkCore/ttk.py +++ b/TermTk/TTkCore/ttk.py @@ -95,7 +95,9 @@ class TTk(TTkWidget): mevt = self.mouse_events.get() focusWidget = TTkHelper.getFocus() overlayWidget = TTkHelper.getOverlay() - if focusWidget is not None and mevt.evt != TTkK.Press: + if focusWidget is not None and \ + mevt.evt != TTkK.Press and \ + mevt.key != TTkK.Wheel: x,y = TTkHelper.absPos(focusWidget) nmevt = mevt.clone(pos=(mevt.x-x, mevt.y-y)) focusWidget.mouseEvent(nmevt) diff --git a/TermTk/TTkLayouts/gridlayout.py b/TermTk/TTkLayouts/gridlayout.py index db0b9526..09d1552b 100644 --- a/TermTk/TTkLayouts/gridlayout.py +++ b/TermTk/TTkLayouts/gridlayout.py @@ -26,6 +26,7 @@ Layout System ''' +from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkLayouts.layout import TTkLayout, TTkWidgetItem @@ -286,8 +287,8 @@ class TTkGridLayout(TTkLayout): item.setGeometry( horSizes[col][0], vertSizes[row][0] , horSizes[col][1], vertSizes[row][1] ) - if isinstance(item, TTkWidgetItem) and not item.isEmpty(): + if item.layoutItemType == TTkK.WidgetItem and not item.isEmpty(): item.widget().update() - elif isinstance(item, TTkLayout): + elif item.layoutItemType == TTkK.LayoutItem: item.update() return True diff --git a/TermTk/TTkLayouts/layout.py b/TermTk/TTkLayouts/layout.py index e674aa9c..e37763e2 100644 --- a/TermTk/TTkLayouts/layout.py +++ b/TermTk/TTkLayouts/layout.py @@ -27,15 +27,18 @@ ''' from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.constant import TTkK class TTkLayoutItem: - __slots__ = ('_x', '_y', '_w', '_h', '_sMax', '_sMaxVal', '_sMin', '_sMinVal') + __slots__ = ('_x', '_y', '_z', '_w', '_h', '_sMax', '_sMaxVal', '_sMin', '_sMinVal', '_layoutItemType') def __init__(self, *args, **kwargs): self._x, self._y = 0, 0 + self._z = kwargs.get('z',0) + self._layoutItemType = kwargs.get('layoutItemType', TTkK.NONE) self._w, self._h = 0, 0 self._sMax, self._sMin = False, False self._sMaxVal, self._sMinVal = 0, 0 - pass + def minimumSize(self): return self.minimumWidth(), self.minimumHeight() def minDimension(self,o)-> int: return 0 @@ -57,6 +60,16 @@ class TTkLayoutItem: 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): __slots__ = ('_items', '_zSortedItems', '_parent') @@ -65,7 +78,7 @@ class TTkLayout(TTkLayoutItem): self._items = [] self._zSortedItems = [] self._parent = None - pass + self.layoutItemType = TTkK.LayoutItem def children(self): return self._items @@ -79,10 +92,17 @@ class TTkLayout(TTkLayoutItem): return 0 def setParent(self, parent): - self._parent = parent + if isinstance(parent, TTkLayoutItem): + self._parent = parent + else: + self._parent = TTkWidgetItem(parent) def parentWidget(self): - return self._parent + 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) @@ -90,6 +110,10 @@ class TTkLayout(TTkLayoutItem): @property def zSortedItems(self): return self._zSortedItems + def replaceItem(self, item, index): + self._items[index] = item + self._zSortItems() + def addItem(self, item): self._items.append(item) self._zSortItems() @@ -99,34 +123,43 @@ class TTkLayout(TTkLayoutItem): widget.parentWidget().removeWidget(self) self.addItem(TTkWidgetItem(widget)) - def removeWidget(self, widget): - for i in self._items: - if i.widget() == widget: - self._items.remove(i) - return + def removeItem(self, item): + self._items.remove(item) self._zSortItems() + def removeWidget(self, widget): + for item in self._items: + if 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 = None + item = self.findBranchWidget(widget) for i in self._items: - if i.widget() == widget: - item = i - elif i.z >= maxz: - maxz=i.z+1 + 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 = None + item = self.findBranchWidget(widget) for i in self._items: - if i.widget() == widget: - item = i - elif i.z <= minz: - minz=i.z-1 + 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): @@ -154,23 +187,23 @@ class TTkLayout(TTkLayoutItem): maxy = max(maxy,y+h) return minx, miny, maxx-minx, maxy-miny - def update(self): + def update(self, *args, **kwargs): ret = False for i in self.children(): - if isinstance(i, TTkWidgetItem) and not i.isEmpty(): - ret = ret or i.widget().update() + 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 isinstance(i, TTkLayout): - ret= ret or i.update() + elif i.layoutItemType == TTkK.LayoutItem: + ret= ret or i.update(*args, **kwargs) return ret class TTkWidgetItem(TTkLayoutItem): - slots = ('_widget','_z') + slots = ('_widget') def __init__(self, widget, z=0): TTkLayoutItem.__init__(self) self._widget = widget - self.z = z + self.layoutItemType = TTkK.WidgetItem def widget(self): return self._widget @@ -193,7 +226,5 @@ class TTkWidgetItem(TTkLayoutItem): def setGeometry(self, x, y, w, h): self._widget.setGeometry(x, y, w, h) - @property - def z(self): return self._z - @z.setter - def z(self, z): self._z = z + #def update(self, *args, **kwargs): + # self.widget().update(*args, **kwargs) diff --git a/TermTk/TTkWidgets/combobox.py b/TermTk/TTkWidgets/combobox.py index eb7401f8..d7d89654 100644 --- a/TermTk/TTkWidgets/combobox.py +++ b/TermTk/TTkWidgets/combobox.py @@ -27,10 +27,12 @@ from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.color import TTkColor -from TermTk.TTkWidgets.widget import * -from TermTk.TTkWidgets.button import * -from TermTk.TTkWidgets.table import * -from TermTk.TTkWidgets.resizableframe import * +from TermTk.TTkCore.helper import TTkHelper +from TermTk.TTkLayouts.gridlayout import TTkGridLayout +from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.button import TTkButton +from TermTk.TTkWidgets.list import TTkList +from TermTk.TTkWidgets.resizableframe import TTkResizableFrame class TTkComboBox(TTkWidget): __slots__ = ('_list', '_id', ) @@ -58,9 +60,9 @@ class TTkComboBox(TTkWidget): self._canvas.drawText(pos=(0,0), text="[", color=borderColor) self._canvas.drawText(pos=(w-2,0), text="^]", color=borderColor) - @pyTTkSlot(int) - def _callback(self, val): - self._id = val + @pyTTkSlot(str) + def _callback(self, label): + self._id = self._list.index(label) #TTkHelper.removeOverlay() self.setFocus() self.update() @@ -72,11 +74,11 @@ class TTkComboBox(TTkWidget): if frameWidth < 20: frameWidth = 20 frame = TTkResizableFrame(layout=TTkGridLayout(), size=(frameWidth,frameHeight)) - table = TTkTable(parent=frame, showHeader=False) - table.activated.connect(self._callback) + listw = TTkList(parent=frame) + listw.textClicked.connect(self._callback) TTkLog.debug(f"{self._list}") for item in self._list: - table.appendItem([item]) + listw.addItem(item) TTkHelper.overlay(self, frame, 0, 0) self.update() return True diff --git a/TermTk/TTkWidgets/frame.py b/TermTk/TTkWidgets/frame.py index cedd06cb..c5c8a986 100644 --- a/TermTk/TTkWidgets/frame.py +++ b/TermTk/TTkWidgets/frame.py @@ -26,8 +26,17 @@ from TermTk.TTkCore.cfg import * from TermTk.TTkCore.log import TTkLog from TermTk.TTkWidgets.widget import TTkWidget + +class _TTkMenuBar(): + __slots__ = ('_itemsLeft', '_itemsCenter', '_itemsRight') + def __init__(self, *args, **kwargs): + pass + + def addMenu(self): + pass + class TTkFrame(TTkWidget): - __slots__ = ('_border','_title', '_titleColor', '_borderColor') + __slots__ = ('_border','_title', '_titleColor', '_borderColor', '_menubarTop', '_menubarBottom') def __init__(self, *args, **kwargs): TTkWidget.__init__(self, *args, **kwargs) self._name = kwargs.get('name' , 'TTkFrame' ) diff --git a/TermTk/TTkWidgets/resizableframe.py b/TermTk/TTkWidgets/resizableframe.py index 23adcf2a..7e10f97a 100644 --- a/TermTk/TTkWidgets/resizableframe.py +++ b/TermTk/TTkWidgets/resizableframe.py @@ -53,8 +53,8 @@ class TTkResizableFrame(TTkFrame): elif y==h-1: self._resizable |= TTkK.BOTTOM # TTkLog.debug(f"{(x,y)} - {self._resizable}") - return self._resizable != TTkK.NONE - #return True + #return self._resizable != TTkK.NONE + return True def mouseDragEvent(self, evt): if self._resizable: diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index 3ecaf37c..4c8d1515 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -22,12 +22,14 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from TermTk.TTkCore.cfg import * -from TermTk.TTkCore.constant import * -import TermTk.libbpytop as lbt -from TermTk.TTkCore.canvas import * -from TermTk.TTkCore.signal import * +from TermTk.TTkCore.cfg import TTkCfg, TTkGlbl +from TermTk.TTkCore.constant import TTkK +from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.helper import TTkHelper +from TermTk.TTkCore.canvas import TTkCanvas +from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkLayouts.layout import TTkLayout, TTkWidgetItem +import TermTk.libbpytop as lbt class TTkWidget: @@ -57,6 +59,8 @@ class TTkWidget: self._name = kwargs.get('name', 'TTkWidget' ) self._parent = kwargs.get('parent', None ) + self._layout = TTkLayout() # root layout + self._layout.addItem(TTkLayout()) # main layout self._x = kwargs.get('x', 0 ) self._y = kwargs.get('y', 0 ) self._x, self._y = kwargs.get('pos', (self._x, self._y)) @@ -101,23 +105,23 @@ class TTkWidget: def addWidget(self, widget): widget._parent = self - if self._layout is not None: - self._layout.addWidget(widget) + if self.layout() is not None: + self.layout().addWidget(widget) self.update(repaint=True, updateLayout=True) # widget.show() def removeWidget(self, widget): - if self._layout is not None: - self._layout.removeWidget(widget) + if self.layout() is not None: + self.layout().removeWidget(widget) self.update(repaint=True, updateLayout=True) def paintEvent(self): pass def paintChildCanvas(self): # paint over child canvas - lx,ly,lw,lh = self._layout.geometry() - for item in self._layout.zSortedItems: - if isinstance(item, TTkWidgetItem) and not item.isEmpty(): + lx,ly,lw,lh = self.layout().geometry() + for item in self.layout().zSortedItems: + if item.layoutItemType == TTkK.WidgetItem and not item.isEmpty(): child = item.widget() cx,cy,cw,ch = child.geometry() self._canvas.paintCanvas( @@ -188,7 +192,7 @@ class TTkWidget: return False for item in reversed(layout.zSortedItems): # for item in layout.zSortedItems: - if isinstance(item, TTkWidgetItem) and not item.isEmpty(): + if item.layoutItemType == TTkK.WidgetItem and not item.isEmpty(): widget = item.widget() if not widget._visible: continue wevt = None @@ -216,7 +220,7 @@ class TTkWidget: #if widget.event(evt): # return True - elif isinstance(item, TTkLayout): + elif item.layoutItemType == TTkK.LayoutItem: levt = evt.clone(pos=(x, y)) if TTkWidget._mouseEventLayoutHandle(levt, item): return True @@ -225,26 +229,26 @@ class TTkWidget: def mouseEvent(self, evt): # Mouse Drag has priority because it # should be handled by the focussed widget - if evt.evt == lbt.MouseEvent.Drag: + if evt.evt == TTkK.Drag: if self.mouseDragEvent(evt): return True - if self._layout is not None: - if TTkWidget._mouseEventLayoutHandle(evt, self._layout): + if self.rootLayout() is not None: + if TTkWidget._mouseEventLayoutHandle(evt, self.rootLayout()): return True # handle own events - if evt.evt == lbt.MouseEvent.Move: + if evt.evt == TTkK.Move: if self.mouseMoveEvent(evt): return True - if evt.evt == lbt.MouseEvent.Release: + if evt.evt == TTkK.Release: #if self.hasFocus(): # self.clearFocus() if self.mouseReleaseEvent(evt): return True - if evt.evt == lbt.MouseEvent.Press: + if evt.evt == TTkK.Press: if self.focusPolicy() & TTkK.ClickFocus == TTkK.ClickFocus: self.setFocus() self.raiseWidget() @@ -252,7 +256,7 @@ class TTkWidget: # TTkLog.debug(f"Click {self._name}") return True - if evt.key == lbt.MouseEvent.Wheel: + if evt.key == TTkK.Wheel: if self.wheelEvent(evt): return True #if self.focusPolicy() & CuT.WheelFocus == CuT.WheelFocus: @@ -288,15 +292,16 @@ class TTkWidget: # elif evt.type() == CuEvent.KeyRelease: # self.keyReleaseEvent(evt) # # Trigger this event to the childs -# if self._layout is not None: -# return CuWidget._eventLayoutHandle(evt, self._layout) +# if self.layout() is not None: +# return CuWidget._eventLayoutHandle(evt, self.layout()) def setLayout(self, layout): - self._layout = layout - self._layout.setParent(self) + self._layout.replaceItem(layout, 0) + self.layout().setParent(self) self.update(repaint=True, updateLayout=True) - def layout(self): return self._layout + def layout(self): return self._layout.itemAt(0) + def rootLayout(self): return self._layout def setParent(self, parent): self._parent = parent @@ -321,15 +326,15 @@ class TTkWidget: return self.maximumHeight() def maximumHeight(self): wMaxH = self._maxh - if self._layout is not None: - lMaxH = self._layout.maximumHeight() + self._padt + self._padb + if self.layout() is not None: + lMaxH = self.layout().maximumHeight() + self._padt + self._padb if lMaxH < wMaxH: return lMaxH return wMaxH def maximumWidth(self): wMaxW = self._maxw - if self._layout is not None: - lMaxW = self._layout.maximumWidth() + self._padl + self._padr + if self.layout() is not None: + lMaxW = self.layout().maximumWidth() + self._padl + self._padr if lMaxW < wMaxW: return lMaxW return wMaxW @@ -343,15 +348,15 @@ class TTkWidget: return self.minimumHeight() def minimumHeight(self): wMinH = self._minh - if self._layout is not None: - lMinH = self._layout.minimumHeight() + self._padt + self._padb + if self.layout() is not None: + lMinH = self.layout().minimumHeight() + self._padt + self._padb if lMinH > wMinH: return lMinH return wMinH def minimumWidth(self): wMinW = self._minw - if self._layout is not None: - lMinW = self._layout.minimumWidth() + self._padl + self._padr + if self.layout() is not None: + lMinW = self.layout().minimumWidth() + self._padl + self._padr if lMinW > wMinW: return lMinW return wMinW @@ -389,13 +394,20 @@ class TTkWidget: # elif isinstance(item, CuLayout): # CuWidget._showHandle(item) + @staticmethod + def _propagateShowToLayout(layout): + if layout is None: return + for item in layout.zSortedItems: + if item.layoutItemType == TTkK.WidgetItem and not item.isEmpty(): + child = item.widget() + child._propagateShow() + else: + TTkWidget._propagateShowToLayout(item) + def _propagateShow(self): if not self._visible: return self.update(updateLayout=True, updateParent=True) - for item in self._layout.zSortedItems: - if isinstance(item, TTkWidgetItem) and not item.isEmpty(): - child = item.widget() - child._propagateShow() + TTkWidget._propagateShowToLayout(self.rootLayout()) @pyTTkSlot() def show(self): @@ -422,15 +434,15 @@ class TTkWidget: def raiseWidget(self): if self._parent is not None and \ - self._parent._layout is not None: + self._parent.rootLayout() is not None: self._parent.raiseWidget() - self._parent._layout.raiseWidget(self) + self._parent.rootLayout().raiseWidget(self) def lowerWidget(self): if self._parent is not None and \ - self._parent._layout is not None: + self._parent.rootLayout() is not None: self._parent.lowerWidget() - self._parent._layout.lowerWidget(self) + self._parent.rootLayout().lowerWidget(self) def close(self): pass @@ -448,15 +460,16 @@ class TTkWidget: if repaint: TTkHelper.addUpdateBuffer(self) TTkHelper.addUpdateWidget(self) - if updateLayout and self._layout is not None: - self._layout.setGeometry( + if updateLayout and self.layout() is not None: + self.rootLayout().setGeometry(0,0,self._width,self._height) + self.layout().setGeometry( self._padl, self._padt, self._width - self._padl - self._padr, self._height - self._padt - self._padb) if updateParent and self._parent is not None: self._parent.update(updateLayout=True) - if updateLayout and self._layout is not None: - if self._layout.update(): + if updateLayout and self.layout() is not None: + if self.layout().update(): self.layoutUpdated() def setFocus(self): From 5fd1142731306a3195e810437ee90e65dc714ce0 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Fri, 5 Mar 2021 15:37:46 +0000 Subject: [PATCH 2/3] reimplemented remove/add widget/item in the grid layout to support nested items --- TermTk/TTkLayouts/boxlayout.py | 2 + TermTk/TTkLayouts/gridlayout.py | 86 +++++++++++++++++++-------------- TermTk/TTkLayouts/layout.py | 20 +++++--- tests/showcase/layoutnested.py | 71 +++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 43 deletions(-) create mode 100755 tests/showcase/layoutnested.py diff --git a/TermTk/TTkLayouts/boxlayout.py b/TermTk/TTkLayouts/boxlayout.py index f5a15f9b..f3ad9d8c 100644 --- a/TermTk/TTkLayouts/boxlayout.py +++ b/TermTk/TTkLayouts/boxlayout.py @@ -33,5 +33,7 @@ class TTkHBoxLayout(TTkGridLayout): pass class TTkVBoxLayout(TTkGridLayout): + def addItem(self, item): + TTkGridLayout.addItem(self, item, self.count(), 0) def addWidget(self, widget): TTkGridLayout.addWidget(self, widget, self.count(), 0) \ No newline at end of file diff --git a/TermTk/TTkLayouts/gridlayout.py b/TermTk/TTkLayouts/gridlayout.py index 09d1552b..d8d2d49c 100644 --- a/TermTk/TTkLayouts/gridlayout.py +++ b/TermTk/TTkLayouts/gridlayout.py @@ -30,17 +30,10 @@ from TermTk.TTkCore.constant import TTkK 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) + TTkLayout.__init__(self, *args, **kwargs) self._gridItems = [[]] self._columnMinWidth = kwargs.get('columnMinWidth',0) self._columnMinHeight = kwargs.get('columnMinHeight',0) @@ -82,6 +75,14 @@ class TTkGridLayout(TTkLayout): widget = args[0] self.removeWidget(widget) widget._parent = self.parentWidget() + item = TTkWidgetItem(widget=widget) + self.addItem(*[item], **kwargs) + widget.update(updateParent=True) + + 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] @@ -93,9 +94,9 @@ class TTkGridLayout(TTkLayout): #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 + 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 @@ -103,18 +104,29 @@ class TTkGridLayout(TTkLayout): if self._gridItems[row][col] is not None: # TODO: Handle the LayoutItem - self.removeWidget(self._gridItems[row][col]) + self.removeItem(self._gridItems[row][col]) - item = TTkGridWidgetItem(widget, row=row, col=col) + item._row = row + item._col = col self._gridItems[row][col] = item - self.addItem(item) - widget.update(updateParent=True) + TTkLayout.addItem(self, item) + if self.parentWidget(): + self.parentWidget().update() + + 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()) @@ -130,11 +142,12 @@ class TTkGridLayout(TTkLayout): 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 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 @@ -143,11 +156,12 @@ class TTkGridLayout(TTkLayout): 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 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 @@ -157,11 +171,12 @@ class TTkGridLayout(TTkLayout): 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 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 @@ -170,11 +185,12 @@ class TTkGridLayout(TTkLayout): 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 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 diff --git a/TermTk/TTkLayouts/layout.py b/TermTk/TTkLayouts/layout.py index e37763e2..fb5bfc9b 100644 --- a/TermTk/TTkLayouts/layout.py +++ b/TermTk/TTkLayouts/layout.py @@ -30,10 +30,12 @@ from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.constant import TTkK class TTkLayoutItem: - __slots__ = ('_x', '_y', '_z', '_w', '_h', '_sMax', '_sMaxVal', '_sMin', '_sMinVal', '_layoutItemType') + __slots__ = ('_x', '_y', '_z', '_w', '_h', '_row','_col', '_sMax', '_sMaxVal', '_sMin', '_sMinVal', '_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._layoutItemType = kwargs.get('layoutItemType', TTkK.NONE) self._w, self._h = 0, 0 self._sMax, self._sMin = False, False @@ -95,7 +97,7 @@ class TTkLayout(TTkLayoutItem): if isinstance(parent, TTkLayoutItem): self._parent = parent else: - self._parent = TTkWidgetItem(parent) + self._parent = TTkWidgetItem(widget=parent) def parentWidget(self): if self._parent is None: return None @@ -121,15 +123,17 @@ class TTkLayout(TTkLayoutItem): def addWidget(self, widget): if widget.parentWidget() is not None: widget.parentWidget().removeWidget(self) - self.addItem(TTkWidgetItem(widget)) + self.addItem(TTkWidgetItem(widget=widget)) def removeItem(self, item): - self._items.remove(item) + if item in self._items: + self._items.remove(item) self._zSortItems() def removeWidget(self, widget): for item in self._items: - if item.widget() == widget: + if item.layoutItemType == TTkK.WidgetItem and \ + item.widget() == widget: self.removeItem(item) def findBranchWidget(self, widget): @@ -200,9 +204,9 @@ class TTkLayout(TTkLayoutItem): class TTkWidgetItem(TTkLayoutItem): slots = ('_widget') - def __init__(self, widget, z=0): - TTkLayoutItem.__init__(self) - self._widget = widget + def __init__(self, *args, **kwargs): + TTkLayoutItem.__init__(self, *args, **kwargs) + self._widget = kwargs.get('widget', None) self.layoutItemType = TTkK.WidgetItem def widget(self): diff --git a/tests/showcase/layoutnested.py b/tests/showcase/layoutnested.py new file mode 100755 index 00000000..1c75a25b --- /dev/null +++ b/tests/showcase/layoutnested.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# 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. + +import sys, os, argparse + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + + +def demoLayoutNested(root=None): + frame = ttk.TTkFrame(parent=root, border=False) + mainLayout = ttk.TTkVBoxLayout() + frame.setLayout(mainLayout) + + gridLayout = ttk.TTkGridLayout() + mainLayout.addItem(gridLayout) + + mainLayout.addWidget(ttk.TTkFrame(border=True,title="Frame1")) + mainLayout.addWidget(ttk.TTkFrame(border=True,title="Frame2")) + + gridLayout.addWidget(ttk.TTkButton(border=True, text="Button1"),0,0) + gridLayout.addWidget(ttk.TTkButton(border=True, text="Button2"),1,1) + gridLayout.addWidget(ttk.TTkButton(border=True, text="Button3"),2,2) + + gridLayout.addWidget(ttk.TTkFrame(border=True,title="Frame3"),0,1) + gridLayout.addWidget(ttk.TTkFrame(border=True,title="Frame4"),2,3) + + return frame + + + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-f', help='Full Screen', action='store_true') + args = parser.parse_args() + + ttk.TTkLog.use_default_file_logging() + + root = ttk.TTk() + if args.f: + rootLayout = root + root.setLayout(ttk.TTkGridLayout()) + else: + rootLayout = ttk.TTkWindow(title="Test Layout", parent=root,pos=(1,1), size=(100,40), border=True, layout=ttk.TTkGridLayout()) + demoLayoutNested(rootLayout) + root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file From 4a51867094e3aec4d846c403ca38b42a2875a511 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Fri, 5 Mar 2021 18:26:10 +0000 Subject: [PATCH 3/3] Finalised changes to have nested layouts --- TermTk/TTkCore/canvas.py | 8 ++++++++ TermTk/TTkLayouts/gridlayout.py | 25 +++++++++++++---------- TermTk/TTkLayouts/layout.py | 14 ++++++++++++- TermTk/TTkWidgets/widget.py | 35 ++++++++++++++++++++++----------- tests/showcase/layoutnested.py | 6 ++++++ tests/test.showcase.001.py | 22 +++++++++++---------- 6 files changed, 78 insertions(+), 32 deletions(-) diff --git a/TermTk/TTkCore/canvas.py b/TermTk/TTkCore/canvas.py index 78fd0295..46af4c99 100644 --- a/TermTk/TTkCore/canvas.py +++ b/TermTk/TTkCore/canvas.py @@ -422,6 +422,14 @@ class TTkCanvas: ''' geom = (x,y,w,h) bound = (x,y,w,h) + + x x+w + canvas: |xxxxxxxxxxxxxxxxxxxxxxx| + slice: |-----------| + bx bx+bw + bound: |--------| + 0 self._width + self._canvas: |----|xxxxxx|----------| ''' def paintCanvas(self, canvas, geom, slice, bound): # TTkLog.debug(f"PaintCanvas:{(x,y,w,h)}") diff --git a/TermTk/TTkLayouts/gridlayout.py b/TermTk/TTkLayouts/gridlayout.py index d8d2d49c..8e2fdcaa 100644 --- a/TermTk/TTkLayouts/gridlayout.py +++ b/TermTk/TTkLayouts/gridlayout.py @@ -62,7 +62,7 @@ class TTkGridLayout(TTkLayout): # remove extra cols for gridRow in range(len(self._gridItems)): if self._gridItems[gridRow] is None: - self._gridItems[gridRow] = [None]*(cols+1) + self._gridItems[gridRow] = [None]*(cols) continue sizeRow = len(self._gridItems[gridRow]) if cols < sizeRow: @@ -74,12 +74,15 @@ class TTkGridLayout(TTkLayout): def addWidget(self, *args, **kwargs): widget = args[0] self.removeWidget(widget) - widget._parent = self.parentWidget() item = TTkWidgetItem(widget=widget) - self.addItem(*[item], **kwargs) - widget.update(updateParent=True) + 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) @@ -100,18 +103,18 @@ class TTkGridLayout(TTkLayout): # reshape the gridItems maxrow += 1 maxcol += 1 - self._reshapeGrid(size=(maxrow,maxcol)) + # 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) - if self.parentWidget(): - self.parentWidget().update() def removeItem(self, item): TTkLayout.removeItem(self, item) @@ -228,7 +231,7 @@ class TTkGridLayout(TTkLayout): return maxh - def update(self): + def update(self, *args, **kwargs): x, y, w, h = self.geometry() newx, newy = x, y @@ -303,8 +306,10 @@ class TTkGridLayout(TTkLayout): 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(): - item.widget().update() + #TTkLog.debug(f"Children name: {item.widget()._name}") + item.widget().update(*args, **kwargs) elif item.layoutItemType == TTkK.LayoutItem: - item.update() + item.update(*args, **kwargs) return True diff --git a/TermTk/TTkLayouts/layout.py b/TermTk/TTkLayouts/layout.py index fb5bfc9b..28b494d5 100644 --- a/TermTk/TTkLayouts/layout.py +++ b/TermTk/TTkLayouts/layout.py @@ -98,6 +98,11 @@ class TTkLayout(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 @@ -119,6 +124,13 @@ class TTkLayout(TTkLayoutItem): 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: @@ -170,7 +182,7 @@ class TTkLayout(TTkLayoutItem): 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() + self.update(repaint=True, updateLayout=True) def groupMoveTo(self, x, y): ox,oy,_,_ = self.fullWidgetAreaGeometry() diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index 4c8d1515..62d51f00 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -117,18 +117,31 @@ class TTkWidget: def paintEvent(self): pass + @staticmethod + def _paintChildCanvas(canvas, item, geometry): + lx,ly,lw,lh = geometry + if item.layoutItemType == TTkK.WidgetItem and not item.isEmpty(): + child = item.widget() + cx,cy,cw,ch = child.geometry() + canvas.paintCanvas( + child.getCanvas(), + (cx, cy, cw, ch), # geometry + (0,0,cw,ch), # slice + (lx, ly, lw, lh)) # bound + else: + for child in item.zSortedItems: + ix, iy, iw, ih = item.geometry() + # child outside the bound + if ix+iw < lx and ix > lx+lw and iy+ih < ly and y > ly+lh: continue + # Reduce the bound to the minimum visible + bx = max(ix,lx) + by = max(iy,ly) + bw = min(ix+iw,lx+lw)-bx + bh = min(iy+ih,ly+lh)-by + TTkWidget._paintChildCanvas(canvas, child, (bx,by,bw,bh)) + def paintChildCanvas(self): - # paint over child canvas - lx,ly,lw,lh = self.layout().geometry() - for item in self.layout().zSortedItems: - if item.layoutItemType == TTkK.WidgetItem and not item.isEmpty(): - child = item.widget() - cx,cy,cw,ch = child.geometry() - self._canvas.paintCanvas( - child.getCanvas(), - (cx, cy, cw, ch), - (0,0,cw,ch), - (lx, ly, lw, lh)) + TTkWidget._paintChildCanvas(self._canvas, self.layout(), self.layout().geometry()) def paintNotifyParent(self): parent = self._parent diff --git a/tests/showcase/layoutnested.py b/tests/showcase/layoutnested.py index 1c75a25b..2caefaac 100755 --- a/tests/showcase/layoutnested.py +++ b/tests/showcase/layoutnested.py @@ -36,6 +36,9 @@ def demoLayoutNested(root=None): gridLayout = ttk.TTkGridLayout() mainLayout.addItem(gridLayout) + nestedLayout = ttk.TTkGridLayout() + gridLayout.addItem(nestedLayout,1,2) + mainLayout.addWidget(ttk.TTkFrame(border=True,title="Frame1")) mainLayout.addWidget(ttk.TTkFrame(border=True,title="Frame2")) @@ -46,6 +49,9 @@ def demoLayoutNested(root=None): gridLayout.addWidget(ttk.TTkFrame(border=True,title="Frame3"),0,1) gridLayout.addWidget(ttk.TTkFrame(border=True,title="Frame4"),2,3) + nestedLayout.addWidget(ttk.TTkButton(border=True, text="Button4"),0,1) + nestedLayout.addWidget(ttk.TTkButton(border=True, text="Button5"),1,0) + return frame diff --git a/tests/test.showcase.001.py b/tests/test.showcase.001.py index 36bd0101..7cd43894 100755 --- a/tests/test.showcase.001.py +++ b/tests/test.showcase.001.py @@ -28,22 +28,24 @@ import random sys.path.append(os.path.join(sys.path[0],'..')) import TermTk as ttk -from showcase.layout import demoLayout -from showcase.table import demoTable -from showcase.tab import demoTab -from showcase.tree import demoTree -from showcase.graph import demoGraph -from showcase.splitter import demoSplitter -from showcase.windows import demoWindows -from showcase.formwidgets import demoFormWidgets -from showcase.scrollarea import demoScrollArea -from showcase.list import demoList +from showcase.layout import demoLayout +from showcase.layoutnested import demoLayoutNested +from showcase.table import demoTable +from showcase.tab import demoTab +from showcase.tree import demoTree +from showcase.graph import demoGraph +from showcase.splitter import demoSplitter +from showcase.windows import demoWindows +from showcase.formwidgets import demoFormWidgets +from showcase.scrollarea import demoScrollArea +from showcase.list import demoList def demoShowcase(root= None, border=True): tabWidget1 = ttk.TTkTabWidget(parent=root, border=border) tabWidget1.addTab(ttk.TTkTestWidgetSizes(border=True, title="Frame1.1"), " Label 1.1 ") tabWidget1.addTab(ttk.TTkTestWidget(border=True, title="Frame1.2"), " Label Test 1.2 ") tabWidget1.addTab(demoLayout(), " Layout Test ") + tabWidget1.addTab(demoLayoutNested()," Nested Layout Test ") tabWidget1.addTab(demoFormWidgets(), " Form Test ") tabWidget1.addTab(demoList(), " List Test ") tabWidget1.addTab(demoGraph(), " Graph Test ")