Source code for TermTk.TTkWidgets.splitter

# 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.

__all__ = ['TTkSplitter']

from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.cfg import TTkCfg
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkCore.string import TTkString
from TermTk.TTkLayouts.layout import TTkLayout
from TermTk.TTkWidgets.widget import TTkWidget
from TermTk.TTkWidgets.container import TTkContainer

[docs] class TTkSplitter(TTkContainer): '''TTkSplitter''' classStyle = { 'default': {'color': TTkColor.fg("#dddddd")+TTkColor.bg("#222222"), 'borderColor': TTkColor.RST}, 'disabled': {'color': TTkColor.fg('#888888'), 'borderColor':TTkColor.fg('#888888')}, 'focus': {'color': TTkColor.fg("#ffddff")+TTkColor.bg("#222222"), 'borderColor': TTkColor.fg("#ffffaa")} } __slots__ = ( '_orientation', '_separators', '_refSizes', '_items', '_titles', '_separatorSelected', '_border') def __init__(self, *, border:bool=False, orientation:TTkK.Direction=TTkK.HORIZONTAL, **kwargs) -> None: self._items = [] self._titles = [] self._separators = [] self._refSizes = [] self._border = border self._orientation = orientation self._separatorSelected = None super().__init__(**kwargs) self.setBorder(border) self.setFocusPolicy(TTkK.ClickFocus) class _SplitterLayout(TTkLayout): def insertWidget(_, index, widget): self.insertWidget(index, widget) def addWidget(_, widget): self.addWidget(widget) def inserItem(_, item): self.inserItem(item) def addItem(_, item): self.addItem(item) self.setLayout(_SplitterLayout())
[docs] def setBorder(self, border): '''setBorder''' self._border = border if border: self.setPadding(1,1,1,1) else: self.setPadding(0,0,0,0) self.update()
[docs] def border(self): '''border''' return self._border
[docs] def orientation(self): '''orientation''' return self._orientation
[docs] def setOrientation(self, orientation): if orientation == self._orientation: return if orientation not in (TTkK.HORIZONTAL, TTkK.VERTICAL): return self._orientation = orientation self._updateGeometries()
[docs] def clean(self): for i in reversed(self._items): if issubclass(type(i),TTkWidget): self.removeWidget(i) else: self.removeItem(i)
[docs] def count(self): '''count''' return len(self._items)
[docs] def indexOf(self, widget): '''indexOf''' return self._items.index(widget)
[docs] def widget(self, index): '''widget''' return self._items[index]
[docs] def replaceItem(self, index, item, title=None): '''replaceItem''' if index >= len(self._items): return self.addItem(item, title=title) TTkLayout.removeItem(self.layout(), self._items[index]) TTkLayout.insertItem(self.layout(), index, item) self._items[index] = item self._titles[index] = TTkString(title) if title else None w,h = self.size() b = 2 if self._border else 0 self._processRefSizes(w-b,h-b) self._updateGeometries()
[docs] def replaceWidget(self, index, widget, title=None): '''replaceWidget''' if index >= len(self._items): return self.addWidget(widget, title=title) TTkLayout.removeWidget(self.layout(), self._items[index]) TTkLayout.insertWidget(self.layout(), index, widget) self._items[index] = widget self._titles[index] = TTkString(title) if title else None w,h = self.size() b = 2 if self._border else 0 self._processRefSizes(w-b,h-b) self._updateGeometries()
[docs] def removeItem(self, item): '''removeItem''' index = self.indexOf(item) self._items.pop(index) self._refSizes.pop(index) self._separators.pop(index) TTkLayout.removeItem(self.layout(), item) w,h = self.size() b = 2 if self._border else 0 self._processRefSizes(w-b,h-b) self._updateGeometries()
def removeWidget(self, widget): '''removeWidget''' index = self.indexOf(widget) self._items.pop(index) self._refSizes.pop(index) self._separators.pop(index) TTkLayout.removeWidget(self.layout(), widget) w,h = self.size() b = 2 if self._border else 0 self._processRefSizes(w-b,h-b) self._updateGeometries()
[docs] def addItem(self, item, size=None, title=None): '''addItem''' self.insertItem(len(self._items), item, size=size, title=title)
[docs] def insertItem(self, index, item, size=None, title=None): '''insertItem''' TTkLayout.insertItem(self.layout(), index, item) self._insertWidgetItem(index, item, size=size, title=title)
def addWidget(self, widget, size=None, title=None): '''addWidget''' self.insertWidget(len(self._items), widget, size=size, title=title)
[docs] def insertWidget(self, index, widget, size=None, title=None): '''insertWidget''' TTkLayout.insertWidget(self.layout(), index, widget) self._insertWidgetItem(index, widget, size=size, title=title)
def _insertWidgetItem(self, index, widgetItem, size=None, title=None): self._items.insert(index, widgetItem) self._titles.insert(index, TTkString(title) if title else None) # assign the same slice to all the widgets self._refSizes.insert(index, size) w,h = self.size() b = 2 if self._border else 0 self._processRefSizes(w-b,h-b) self._updateGeometries() if self.parentWidget(): self.parentWidget().update(repaint=True, updateLayout=True)
[docs] def setSizes(self, sizes): '''setSizes''' ls = len(self._separators) sizes=sizes[:ls]+[None]*max(0,ls-len(sizes)) self._refSizes = sizes.copy() w,h = self.size() b = 2 if self._border else 0 self._processRefSizes(w-b,h-b) self._updateGeometries()
def _minMaxSizeBefore(self, index): if self._separatorSelected is None: return 0, 0x1000 # this is because there is a hidden splitter at position -1 minsize = -1 maxsize = -1 for i in range(self._separatorSelected+1): item = self._items[i] minsize += item.minDimension(self._orientation)+1 maxsize += item.maxDimension(self._orientation)+1 return minsize, maxsize def _minMaxSizeAfter(self, index): if self._separatorSelected is None: return 0, 0x1000 minsize = 0x0 maxsize = 0x0 for i in range(self._separatorSelected+1, len(self._separators)): item = self._items[i] minsize += item.minDimension(self._orientation)+1 maxsize += item.maxDimension(self._orientation)+1 return minsize, maxsize def _updateGeometries(self, resized=False): if not self.isVisible() or not self._items: return w,h = self.size() if w==h==0: return sep = self._separators = self._separators[0:len(self._items)] if self._border: w-=2 h-=2 def _processGeometry(index, forward): item = self._items[index] pa = -1 if index==0 else sep[index-1] pb = sep[index] if self._orientation == TTkK.HORIZONTAL: newPos = pa+1 size = w-newPos else: newPos = pa+1 size = h-newPos if i<=len(sep)-2: # this is not the last widget size = pb-newPos maxsize = item.maxDimension(self._orientation) minsize = item.minDimension(self._orientation) if size > maxsize: size = maxsize elif size < minsize: size = minsize if forward: sep[index]=pa+size+1 elif i>0 : sep[index-1]=pa=pb-size-1 if self._orientation == TTkK.HORIZONTAL: item.setGeometry(pa+1,0,size,h) else: item.setGeometry(0,pa+1,w,size) pass selected = 0 if self._orientation == TTkK.HORIZONTAL: size = w else: size = h if self._separatorSelected is not None: selected = self._separatorSelected sepPos = sep[selected] minsize,maxsize = self._minMaxSizeBefore(selected) # TTkLog.debug(f"before:{minsize,maxsize}") if sepPos > maxsize: sep[selected] = maxsize if sepPos < minsize: sep[selected] = minsize minsize,maxsize = self._minMaxSizeAfter(selected) # TTkLog.debug(f"after:{minsize,maxsize}") if sepPos < size-maxsize: sep[selected] = size-maxsize if sepPos > size-minsize: sep[selected] = size-minsize if resized: l = len(sep) for i in reversed(range(l)): _processGeometry(i, False) for i in range(l): _processGeometry(i, True) else: for i in reversed(range(selected+1)): _processGeometry(i, False) for i in range(selected+1, len(sep)): _processGeometry(i, True) if self._separatorSelected is not None: s = [ b-a for a,b in zip([0]+self._separators,self._separators)] self._refSizes = s self.update() def _processRefSizes(self, w, h): self._separatorSelected = None if self._orientation == TTkK.HORIZONTAL: sizeRef = w else: sizeRef = h if sizeRef==0: self._separators = [0]*len(self._items) return # get the sum of the fixed sizes if None in self._refSizes: fixSize = sum(filter(None, self._refSizes)) numVarSizes = len([x for x in self._refSizes if x is None]) avalSize = sizeRef-fixSize varSize = avalSize//numVarSizes sizes = [] for s in self._refSizes: if not s: avalSize -= varSize s = varSize + avalSize if avalSize<varSize else 0 sizes.append(s) sizes = [varSize if s is None else s for s in self._refSizes] else: sizes = self._refSizes sizeRef = sum(sizes) self._separators = [sum(sizes[:i+1]) for i in range(len(sizes))] # Adjust separators to the new size; if sizeRef > 0: if self._orientation == TTkK.HORIZONTAL: diff = w/sizeRef else: diff = h/sizeRef self._separators = [int(i*diff) for i in self._separators] def resizeEvent(self, w, h): b = 2 if self._border else 0 self._processRefSizes(w-b,h-b) self._updateGeometries(resized=True) def mousePressEvent(self, evt): self._separatorSelected = None x,y = evt.x, evt.y if self._border: x-=1 ; y-=1 # TTkLog.debug(f"{self._separators} {evt}") for i, val in enumerate(self._separators): if self._orientation == TTkK.HORIZONTAL: if x == val: self._separatorSelected = i self._updateGeometries() else: if y == val: self._separatorSelected = i self._updateGeometries() return self._separatorSelected is not None def mouseDragEvent(self, evt): if self._separatorSelected is not None: x,y = evt.x, evt.y if self._border: x-=1 ; y-=1 if self._orientation == TTkK.HORIZONTAL: self._separators[self._separatorSelected] = x else: self._separators[self._separatorSelected] = y self._updateGeometries() return True return False def focusOutEvent(self): self._separatorSelected = None def minimumHeight(self) -> int: ret = b = 2 if self._border else 0 if not self._items: return ret if self._orientation == TTkK.VERTICAL: for item in self._items: ret+=item.minimumHeight()+1 ret = max(0,ret-1) else: for item in self._items: ret = max(ret,item.minimumHeight()+b) return ret def minimumWidth(self) -> int: ret = b = 2 if self._border else 0 if not self._items: return ret if self._orientation == TTkK.HORIZONTAL: for item in self._items: ret+=item.minimumWidth()+1 ret = max(0,ret-1) else: for item in self._items: ret = max(ret,item.minimumWidth()+b) return ret def maximumHeight(self) -> int: b = 2 if self._border else 0 if not self._items: return 0x10000 if self._orientation == TTkK.VERTICAL: ret = b for item in self._items: ret+=item.maximumHeight()+1 ret = max(b,ret-1) else: ret = 0x10000 for item in self._items: ret = min(ret,item.maximumHeight()+b) return ret def maximumWidth(self) -> int: b = 2 if self._border else 0 if not self._items: return 0x10000 if self._orientation == TTkK.HORIZONTAL: ret = b for item in self._items: ret+=item.maximumWidth()+1 ret = max(b,ret-1) else: ret = 0x10000 for item in self._items: ret = min(ret,item.maximumWidth()+b) return ret def paintEvent(self, canvas): style = self.currentStyle() color = style['color'] borderColor = style['borderColor'] off = 0 w,h = self.size() if self._border: off= 1 canvas.drawBox(pos=(0,0),size=(w,h),color=borderColor) if self._orientation == TTkK.HORIZONTAL: for i in self._separators[:-1]: canvas.drawVLine(pos=(i+off,0), size=h,color=borderColor) else: for i in self._separators[:-1]: canvas.drawHLine(pos=(0,i+off), size=w,color=borderColor) if self._orientation == TTkK.HORIZONTAL and self._border: for i,t in enumerate(self._titles): if not t: continue a = (off + self._separators[i-1]) if i>0 else 0 b = off + self._separators[i] canvas.drawBoxTitle( pos=(a,0), size=(b-a+1,1), text=t, color=borderColor, colorText=color) elif self._orientation == TTkK.VERTICAL: for i,t in enumerate(self._titles): if i == 0 and not self._border: continue if not t: continue a = (off + self._separators[i-1]) if i>0 else 0 grid = 0 if i == 0 else 5 canvas.drawBoxTitle( pos=(0,a), size=(w,1), grid=grid, text=t, color=borderColor, colorText=color)